diff --git a/.cirrus.yml b/.cirrus.yml index babb679d..9c280254 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -22,8 +22,21 @@ env: CFUNITS_CACHE_BUILD: "0" # Base environment conda packages to be installed MAMBA_CACHE_PACKAGES: "pip conda-lock" - # Increment the build number to force new pip cache upload. - PIP_CACHE_BUILD: "0" + + +# +# Linting +# +lint_task: + auto_cancellation: true + container: + image: python:latest + cpu: 2 + memory: 4G + name: "${CIRRUS_OS}: linting" + lint_script: + - python -m pip install --retries 3 pre-commit + - pre-commit run --all-files # diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 4f76e098..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -cf_units/_version.py export-subst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..f08cf31b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,37 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v4.0.1" + hooks: + # Prevent giant files from being committed. + - id: check-added-large-files + # Check whether files parse as valid Python. + - id: check-ast + # Check for file name conflicts on case-insensitive filesytems. + - id: check-case-conflict + # Check for files that contain merge conflict strings. + - id: check-merge-conflict + # Check for debugger imports and py37+ `breakpoint()` calls in Python source. + - id: debug-statements + # Don't commit to main branch. + - id: no-commit-to-branch +- repo: https://github.com/psf/black + rev: "21.6b0" + hooks: + - id: black + # Force black to run on whole repo, using settings from pyproject.toml + pass_filenames: false + args: ["--config=./pyproject.toml", "--check", "."] +- repo: https://github.com/PyCQA/flake8 + rev: "3.9.2" + hooks: + # Run flake8. + - id: flake8 + args: ["--config=./setup.cfg"] +- repo: https://github.com/pycqa/isort + rev: "5.8.0" + hooks: + - id: isort + name: isort + args: ["--filter-files", "--check"] diff --git a/CHANGES b/CHANGES index 4599955c..0de1676e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ This file is no longer updated and is provided for historical purposes only. -Please see docs/cf_units/src/whatsnew/ for a changelog. +Please see the Git history for a record of changes. Release 1.4 (14 June 2013) diff --git a/INSTALL b/INSTALL index 36856436..82b4b925 100644 --- a/INSTALL +++ b/INSTALL @@ -26,68 +26,38 @@ Installing from source The latest cf-units source release is available from https://github.com/SciTools/cf-units/releases. -cf-units makes use of cftime, cython, numpy, setuptools and udunits2. -These dependencies must be in place before you can successfully install -cf-units. Once you have satisfied the requirements detailed below, -extract the cf-units source package, change to the new directory, and enter:: - python setup.py install +Build and runtime requirements +============================== +See the ``requirements/`` directory for the external packages you will need to +have installed before installing and running cf-units. -For non-standard locations, additional build lib & include paths -can be provided as per-usual at build_ext phase:: +The recommended way to provision requirements is using Conda:: - python setup.py build_ext -I/path/to/include -L/path/to/lib - python setup.py install + conda env create -f requirements/cf-units.yml +All requirements except ``udunits2`` are also available individually via PyPI:: -Build and runtime requirements -============================== -These are external packages which you will need to have installed before -installing and running cf-units. + pip install numpy -Many of these packages are available in Linux package managers +Many of the packages are also available in Linux package managers such as aptitude and yum. For example, it may be possible to install Numpy using:: apt-get install python-numpy -If you are installing dependencies with a package manager on Linux, -you may need to install the development packages (look for a "-dev" -postfix) in addition to the core packages. - -python 2.7 or later (https://www.python.org/) - cf-units is compatible with Python 2 and Python 3. - -cftime 1.0.0 or later (https://unidata.github.io/cftime/) - Python package for decoding time units conforming to - Climate and Forecasting (CF) netCDF conventions. -cython (https://pypi.org/project/Cython/) - Optimising static compiler for Python and the extended Cython - programming language. +Installing cf-units +=================== +Once you have satisfied the requirements detailed above, extract the cf-units +source package, change to the new directory, and enter:: -numpy 1.14 or later (https://pypi.org/project/numpy/) - Python package for scientific computing including a powerful N-dimensional - array object. + pip install . -udunits2 2.2.26 or later (https://www.unidata.ucar.edu/downloads/udunits/index.jsp) - C library for units of physical quantities. - -setuptools (https://pypi.org/project/setuptools/) - Python package for installing/removing python packages. - - -Optional -'''''''' -These packages are required for the full cf-units test suite to run. - -pep8 1.4.6* (https://pypi.org/project/pep8/) - Python package for software testing. - -pytest (https://pypi.org/project/pytest/) - Python testing framework. +For non-standard locations, additional build lib & include paths +can be provided as per-usual at ``build_ext`` phase:: -* Those packages have been tested with a specific build. + pip install --global-option=build_ext --global-option="-I/path/to/include2 --global-option="-L/path/to/lib" . Custom site configuration diff --git a/MANIFEST.in b/MANIFEST.in index 47741d99..dd50b8e4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,3 +4,4 @@ recursive-include cf_units *.py *.pxd *.pyx *.c include cf_units/etc/site.cfg.template include CHANGES COPYING COPYING.LESSER INSTALL exclude cf_units/etc/site.cfg +graft requirements diff --git a/README.md b/README.md index 861f266e..126e1ce8 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,40 @@ -

- cf-units -

- -

-Units of measure as defined by the Climate and Forecast (CF) metadata -conventions. -

- -

- - -conda-forge downloads - -Latest version - -Commits since last release - -# contributors - -cirrus-ci - -Coverage Status - -

-
+# [cf-units](https://scitools.org.uk/cf-units/docs/latest/) + +#### Units of measure as defined by the Climate and Forecast (CF) metadata conventions. + +[comment]: # (https://shields.io/ is a good source of these) +[![Build Status](https://api.cirrus-ci.com/github/SciTools/cf-units.svg)](https://cirrus-ci.com/github/SciTools/cf-units) +[![Coverage Status](https://codecov.io/gh/SciTools/cf-units/branch/master/graph/badge.svg?token=6LlYlyTUZG)](https://codecov.io/gh/SciTools/cf-units) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/SciTools/cf-units/master.svg)](https://results.pre-commit.ci/latest/github/SciTools/cf-units/master) +, +[![conda-forge downloads](https://img.shields.io/conda/vn/conda-forge/cf-units?color=orange&label=conda-forge&logo=conda-forge&logoColor=white)](https://anaconda.org/conda-forge/cf-units) +[![PyPI](https://img.shields.io/pypi/v/cf-units?color=orange&label=pypi&logo=python&logoColor=white)](https://pypi.org/project/cf-units/) +[![Latest version](https://img.shields.io/github/tag/SciTools/cf-units)](https://github.com/SciTools/cf-units/releases) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3723086.svg)](https://doi.org/10.5281/zenodo.3723086) +, +[![Black](https://img.shields.io/badge/code%20style-black-000000)](https://github.com/psf/black) +[![Flake8](https://img.shields.io/badge/lint-flake8-lightgrey)](https://github.com/PyCQA/flake8) +[![isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) +, +[![Licence](https://img.shields.io/github/license/SciTools/cf-units)](COPYING) +[![Contributors](https://img.shields.io/github/contributors/SciTools/cf-units)](https://github.com/SciTools/cf-units/graphs/contributors) +[![Commits since last release](https://img.shields.io/github/commits-since/SciTools/cf-units/latest.svg)](https://github.com/SciTools/cf-units/commits/master) # Table of contents - - - + $ markdownlint README.md) - [Overview](#overview) - [Example](#example) - [Get in touch](#get-in-touch) - [Credits, copyright and license](#credits-copyright-and-license) - - ## Overview Units of measure as required by the Climate and Forecast (CF) metadata @@ -81,7 +62,6 @@ Documentation can be found at . [submit a GitHub issue](https://github.com/SciTools/cf-units/issues). - Suggest features: see our [contributing guide](.github/CONTRIBUTING.md). - ## Credits, copyright and license cf-units is developed collaboratively under the SciTools umbrella. diff --git a/cf_units/__init__.py b/cf_units/__init__.py index 12089112..1da01f6d 100644 --- a/cf_units/__init__.py +++ b/cf_units/__init__.py @@ -14,49 +14,56 @@ """ -from contextlib import contextmanager import copy import os.path import sys +from contextlib import contextmanager import cftime import numpy as np -from . import config -from .util import _OrderedHashable, approx_equal - from cf_units import _udunits2 as _ud -from cf_units._udunits2 import (UT_ASCII, UT_ISO_8859_1, UT_LATIN1, UT_UTF8, - UT_NAMES, UT_DEFINITION) +from cf_units._udunits2 import ( + UT_ASCII, + UT_DEFINITION, + UT_ISO_8859_1, + UT_LATIN1, + UT_NAMES, + UT_UTF8, +) +from . import config from ._version import version as __version__ # noqa: F401 +from .util import _OrderedHashable, approx_equal -__all__ = ['CALENDAR_STANDARD', - 'CALENDAR_GREGORIAN', - 'CALENDAR_PROLEPTIC_GREGORIAN', - 'CALENDAR_NO_LEAP', - 'CALENDAR_JULIAN', - 'CALENDAR_ALL_LEAP', - 'CALENDAR_365_DAY', - 'CALENDAR_366_DAY', - 'CALENDAR_360_DAY', - 'CALENDARS', - 'CALENDAR_ALIASES', - 'UT_NAMES', - 'UT_DEFINITION', - 'UT_ASCII', - 'FLOAT32', - 'FLOAT64', - 'is_time', - 'is_vertical', - 'Unit', - 'date2num', - 'decode_time', - 'encode_clock', - 'encode_date', - 'encode_time', - 'num2date', - 'suppress_errors'] +__all__ = [ + "CALENDAR_STANDARD", + "CALENDAR_GREGORIAN", + "CALENDAR_PROLEPTIC_GREGORIAN", + "CALENDAR_NO_LEAP", + "CALENDAR_JULIAN", + "CALENDAR_ALL_LEAP", + "CALENDAR_365_DAY", + "CALENDAR_366_DAY", + "CALENDAR_360_DAY", + "CALENDARS", + "CALENDAR_ALIASES", + "UT_NAMES", + "UT_DEFINITION", + "UT_ASCII", + "FLOAT32", + "FLOAT64", + "is_time", + "is_vertical", + "Unit", + "date2num", + "decode_time", + "encode_clock", + "encode_date", + "encode_time", + "num2date", + "suppress_errors", +] ######################################################################## @@ -68,52 +75,66 @@ # # default constants # -EPOCH = '1970-01-01 00:00:00' -_UNKNOWN_UNIT_STRING = 'unknown' -_UNKNOWN_UNIT_SYMBOL = '?' -_UNKNOWN_UNIT = [_UNKNOWN_UNIT_STRING, _UNKNOWN_UNIT_SYMBOL, '???', ''] -_NO_UNIT_STRING = 'no_unit' -_NO_UNIT_SYMBOL = '-' -_NO_UNIT = [_NO_UNIT_STRING, _NO_UNIT_SYMBOL, 'no unit', 'no-unit', 'nounit'] -_UNIT_DIMENSIONLESS = '1' -_OP_SINCE = ' since ' +EPOCH = "1970-01-01 00:00:00" +_UNKNOWN_UNIT_STRING = "unknown" +_UNKNOWN_UNIT_SYMBOL = "?" +_UNKNOWN_UNIT = [_UNKNOWN_UNIT_STRING, _UNKNOWN_UNIT_SYMBOL, "???", ""] +_NO_UNIT_STRING = "no_unit" +_NO_UNIT_SYMBOL = "-" +_NO_UNIT = [_NO_UNIT_STRING, _NO_UNIT_SYMBOL, "no unit", "no-unit", "nounit"] +_UNIT_DIMENSIONLESS = "1" +_OP_SINCE = " since " _CATEGORY_UNKNOWN, _CATEGORY_NO_UNIT, _CATEGORY_UDUNIT = range(3) # # libudunits2 constants # -UT_FORMATS = [UT_ASCII, UT_ISO_8859_1, UT_LATIN1, UT_UTF8, UT_NAMES, - UT_DEFINITION] +UT_FORMATS = [ + UT_ASCII, + UT_ISO_8859_1, + UT_LATIN1, + UT_UTF8, + UT_NAMES, + UT_DEFINITION, +] # # cftime constants # -CALENDAR_STANDARD = 'standard' -CALENDAR_GREGORIAN = 'gregorian' -CALENDAR_PROLEPTIC_GREGORIAN = 'proleptic_gregorian' -CALENDAR_NO_LEAP = 'noleap' -CALENDAR_JULIAN = 'julian' -CALENDAR_ALL_LEAP = 'all_leap' -CALENDAR_365_DAY = '365_day' -CALENDAR_366_DAY = '366_day' -CALENDAR_360_DAY = '360_day' +CALENDAR_STANDARD = "standard" +CALENDAR_GREGORIAN = "gregorian" +CALENDAR_PROLEPTIC_GREGORIAN = "proleptic_gregorian" +CALENDAR_NO_LEAP = "noleap" +CALENDAR_JULIAN = "julian" +CALENDAR_ALL_LEAP = "all_leap" +CALENDAR_365_DAY = "365_day" +CALENDAR_366_DAY = "366_day" +CALENDAR_360_DAY = "360_day" #: The calendars recognised by cf_units. #: These are accessible as strings, or as constants in the form #: ``cf_units.CALENDAR_{ calendar_name.upper() }``. For example, #: ``cf_units.CALENDAR_NO_LEAP`` and ``cf_units.CALENDAR_366_DAY``. -CALENDARS = [CALENDAR_STANDARD, CALENDAR_GREGORIAN, - CALENDAR_PROLEPTIC_GREGORIAN, CALENDAR_NO_LEAP, CALENDAR_JULIAN, - CALENDAR_ALL_LEAP, CALENDAR_365_DAY, CALENDAR_366_DAY, - CALENDAR_360_DAY] +CALENDARS = [ + CALENDAR_STANDARD, + CALENDAR_GREGORIAN, + CALENDAR_PROLEPTIC_GREGORIAN, + CALENDAR_NO_LEAP, + CALENDAR_JULIAN, + CALENDAR_ALL_LEAP, + CALENDAR_365_DAY, + CALENDAR_366_DAY, + CALENDAR_360_DAY, +] #: Where calendars have multiple names, we map the alias to the #: definitive form. CALENDAR_ALIASES = { CALENDAR_STANDARD: CALENDAR_GREGORIAN, CALENDAR_NO_LEAP: CALENDAR_365_DAY, - CALENDAR_ALL_LEAP: CALENDAR_366_DAY} + CALENDAR_ALL_LEAP: CALENDAR_366_DAY, +} # @@ -129,16 +150,16 @@ ######################################################################## # Convenience dictionary for the Unit convert method. -_cv_convert_scalar = {FLOAT32: _ud.convert_float, - FLOAT64: _ud.convert_double} -_cv_convert_array = {FLOAT32: _ud.convert_floats, - FLOAT64: _ud.convert_doubles} +_cv_convert_scalar = {FLOAT32: _ud.convert_float, FLOAT64: _ud.convert_double} +_cv_convert_array = {FLOAT32: _ud.convert_floats, FLOAT64: _ud.convert_doubles} # Map of ut_encodings to encoding strings -_encoding_lookup = {UT_ASCII: 'ascii', - UT_ISO_8859_1: 'iso_8859_1', - UT_LATIN1: 'latin1', - UT_UTF8: 'utf-8'} +_encoding_lookup = { + UT_ASCII: "ascii", + UT_ISO_8859_1: "iso_8859_1", + UT_LATIN1: "latin1", + UT_UTF8: "utf-8", +} @contextmanager @@ -166,16 +187,20 @@ def suppress_errors(): _ud_system = _ud.read_xml() except _ud.UdunitsError: _alt_xml_path = config.get_option( - 'System', 'udunits2_xml_path', - default=os.path.join(sys.prefix, 'share', 'udunits', - 'udunits2.xml')) + "System", + "udunits2_xml_path", + default=os.path.join( + sys.prefix, "share", "udunits", "udunits2.xml" + ), + ) try: _ud_system = _ud.read_xml(_alt_xml_path.encode()) except _ud.UdunitsError as e: - error_msg = ': "%s"' % e.error_msg() if e.errnum else '' + error_msg = ': "%s"' % e.error_msg() if e.errnum else "" raise OSError( - '[%s] Failed to open UDUNITS-2 XML unit database%s' - % (e.status_msg(), error_msg)) + "[%s] Failed to open UDUNITS-2 XML unit database%s" + % (e.status_msg(), error_msg) + ) ######################################################################## @@ -184,6 +209,7 @@ def suppress_errors(): # ######################################################################## + def encode_time(year, month, day, hour, minute, second): """ Return date/clock time encoded as a double precision value. @@ -382,9 +408,13 @@ def _discard_microsecond(date): # Create date objects of the same type returned by cftime.num2date() # (either datetime.datetime or cftime.datetime), discarding the # microseconds - dates = np.array([d and d.__class__(d.year, d.month, d.day, - d.hour, d.minute, d.second) - for d in dates]) + dates = np.array( + [ + d + and d.__class__(d.year, d.month, d.day, d.hour, d.minute, d.second) + for d in dates + ] + ) result = dates[0] if shape == () else dates.reshape(shape) return result @@ -456,11 +486,13 @@ def num2date(time_value, unit, calendar, only_use_cftime_datetimes=True): unit_string = unit_string.replace("epoch", EPOCH) unit_inst = Unit(unit_string, calendar=calendar) return unit_inst.num2date( - time_value, only_use_cftime_datetimes=only_use_cftime_datetimes) + time_value, only_use_cftime_datetimes=only_use_cftime_datetimes + ) -def _num2date_to_nearest_second(time_value, unit, - only_use_cftime_datetimes=True): +def _num2date_to_nearest_second( + time_value, unit, only_use_cftime_datetimes=True +): """ Return datetime encoding of numeric time value with respect to the given time reference units, with a resolution of 1 second. @@ -493,12 +525,15 @@ def _num2date_to_nearest_second(time_value, unit, # arbitrarily. It is probably not possible to replicate that behaviour with # later versions, if one wished to do so for the sake of consistency. cftime_unit = unit.cftime_unit - time_units = cftime_unit.split(' ')[0] - has_half_seconds = np.logical_and(time_units == 'seconds', - time_values % 1. == 0.5) + time_units = cftime_unit.split(" ")[0] + has_half_seconds = np.logical_and( + time_units == "seconds", time_values % 1.0 == 0.5 + ) num2date_kwargs = dict( - units=cftime_unit, calendar=unit.calendar, - only_use_cftime_datetimes=only_use_cftime_datetimes) + units=cftime_unit, + calendar=unit.calendar, + only_use_cftime_datetimes=only_use_cftime_datetimes, + ) dates = cftime.num2date(time_values, **num2date_kwargs) try: # We can assume all or none of the dates have a microsecond attribute @@ -508,10 +543,11 @@ def _num2date_to_nearest_second(time_value, unit, round_mask = np.logical_or(has_half_seconds, microseconds != 0) ceil_mask = np.logical_or(has_half_seconds, microseconds >= 500000) if time_values[ceil_mask].size > 0: - useconds = Unit('second') + useconds = Unit("second") second_frac = useconds.convert(0.75, time_units) dates[ceil_mask] = cftime.num2date( - time_values[ceil_mask] + second_frac, **num2date_kwargs) + time_values[ceil_mask] + second_frac, **num2date_kwargs + ) dates[round_mask] = _discard_microsecond(dates[round_mask]) result = dates[0] if shape == () else dates.reshape(shape) return result @@ -539,7 +575,7 @@ def as_unit(unit): if result is None: # Typically unit is a string, however we cater for other types of # 'unit' (e.g. iris.unit.Unit). - result = Unit(unit, calendar=getattr(unit, 'calendar', None)) + result = Unit(unit, calendar=getattr(unit, "calendar", None)) if use_cache: _CACHE[unit] = result return result @@ -601,10 +637,11 @@ def _ud_value_error(ud_err, message): ud_msg = ud_err.error_msg() if ud_msg: - message = u'{}: {}'.format(message, ud_msg) + message = "{}: {}".format(message, ud_msg) - message = u'[{status}] {message}'.format( - status=ud_err.status_msg(), message=message) + message = "[{status}] {message}".format( + status=ud_err.status_msg(), message=message + ) return ValueError(message) @@ -624,6 +661,7 @@ class Unit(_OrderedHashable): This class also supports time and calendar defintion and manipulation. """ + def _init_from_tuple(self, values): # Implements the required interface for an _OrderedHashable. # This will also ensure a Unit._init(*Unit.names) method exists. @@ -646,28 +684,30 @@ def __lt__(self, other): # Prevent attribute updates def __setattr__(self, name, value): - raise AttributeError('Instances of %s are immutable' % - type(self).__name__) + raise AttributeError( + "Instances of %s are immutable" % type(self).__name__ + ) def __delattr__(self, name): - raise AttributeError('Instances of %s are immutable' % - type(self).__name__) + raise AttributeError( + "Instances of %s are immutable" % type(self).__name__ + ) # Declare the attribute names relevant to the ordered and hashable # behaviour. - _names = ('category', 'ut_unit', 'calendar', 'origin') + _names = ("category", "ut_unit", "calendar", "origin") category = None - 'Is this an unknown unit, a no-unit, or a UDUNITS-2 unit.' + "Is this an unknown unit, a no-unit, or a UDUNITS-2 unit." ut_unit = None - 'Reference to the quantity defining the UDUNITS-2 unit.' + "Reference to the quantity defining the UDUNITS-2 unit." calendar = None - 'Represents the unit calendar name, see cf_units.CALENDARS' + "Represents the unit calendar name, see cf_units.CALENDARS" origin = None - 'The original string used to create this unit.' + "The original string used to create this unit." __slots__ = () @@ -728,12 +768,12 @@ def __init__(self, unit, calendar=None): encoding = UT_UTF8 if unit is None: - unit = '' + unit = "" unit = str(unit).strip() - if unit.lower().endswith(' utc'): - unit = unit[:unit.lower().rfind(' utc')] + if unit.lower().endswith(" utc"): + unit = unit[: unit.lower().rfind(" utc")] if unit.endswith(" since epoch"): unit = unit.replace("epoch", EPOCH) @@ -755,10 +795,11 @@ def __init__(self, unit, calendar=None): category = _CATEGORY_UDUNIT str_unit = unit try: - ut_unit = _ud.parse(_ud_system, unit.encode('utf8'), encoding) + ut_unit = _ud.parse(_ud_system, unit.encode("utf8"), encoding) except _ud.UdunitsError as exception: value_error = _ud_value_error( - exception, u'Failed to parse unit "{}"'.format(str_unit)) + exception, 'Failed to parse unit "{}"'.format(str_unit) + ) raise value_error from None if _OP_SINCE in unit.lower(): if calendar is None: @@ -768,18 +809,24 @@ def __init__(self, unit, calendar=None): if calendar_ in CALENDAR_ALIASES: calendar_ = CALENDAR_ALIASES[calendar_] if calendar_ not in CALENDARS: - msg = '{!r} is an unsupported calendar.' + msg = "{!r} is an unsupported calendar." raise ValueError(msg.format(calendar)) else: - msg = 'Expected string-like calendar argument, got {!r}.' + msg = "Expected string-like calendar argument, got {!r}." raise TypeError(msg.format(type(calendar))) # Call the OrderedHashable's init. - self._init(category, ut_unit, calendar_, unit,) + self._init( + category, + ut_unit, + calendar_, + unit, + ) @classmethod - def _new_from_existing_ut(cls, category, ut_unit, - calendar=None, origin=None): + def _new_from_existing_ut( + cls, category, ut_unit, calendar=None, origin=None + ): # Short-circuit __init__ if we know what we are doing and already # have a UT handle. unit = cls.__new__(cls) @@ -798,14 +845,14 @@ def _new_from_existing_ut(cls, category, ut_unit, def __getstate__(self): # state capture method for Pickle.dump() # - return the instance data needed to reconstruct a Unit value - return {'unit_text': self.origin, 'calendar': self.calendar} + return {"unit_text": self.origin, "calendar": self.calendar} def __setstate__(self, state): # object reconstruction method for Pickle.load() # intercept the Pickle.load() operation and call own __init__ again # - this is to ensure a valid ut_unit attribute (as these # handles aren't persistent) - self.__init__(state['unit_text'], calendar=state['calendar']) + self.__init__(state["unit_text"], calendar=state["calendar"]) def __copy__(self): return self @@ -834,7 +881,7 @@ def is_time(self): if self.is_unknown() or self.is_no_unit(): result = False else: - day = _ud.get_unit_by_name(_ud_system, b'day') + day = _ud.get_unit_by_name(_ud_system, b"day") result = _ud.are_convertible(self.ut_unit, day) return result @@ -860,10 +907,10 @@ def is_vertical(self): if self.is_unknown() or self.is_no_unit(): result = False else: - bar = _ud.get_unit_by_name(_ud_system, b'bar') + bar = _ud.get_unit_by_name(_ud_system, b"bar") result = _ud.are_convertible(self.ut_unit, bar) if not result: - meter = _ud.get_unit_by_name(_ud_system, b'meter') + meter = _ud.get_unit_by_name(_ud_system, b"meter") result = _ud.are_convertible(self.ut_unit, meter) return result @@ -913,10 +960,11 @@ def is_long_time_interval(self): """ result = False - long_time_intervals = ['year', 'month'] + long_time_intervals = ["year", "month"] if self.is_time_reference(): - result = any(interval in self.origin - for interval in long_time_intervals) + result = any( + interval in self.origin for interval in long_time_intervals + ) return result def title(self, value): @@ -941,9 +989,9 @@ def title(self, value): """ if self.is_time_reference(): dt = self.num2date(value) - result = dt.strftime('%Y-%m-%d %H:%M:%S') + result = dt.strftime("%Y-%m-%d %H:%M:%S") else: - result = '%s %s' % (str(value), self) + result = "%s %s" % (str(value), self) return result @property @@ -968,9 +1016,9 @@ def modulus(self): """ - if self == 'radians': + if self == "radians": result = np.pi * 2 - elif self == 'degrees': + elif self == "degrees": result = 360.0 else: result = None @@ -997,12 +1045,17 @@ def is_convertible(self, other): """ other = as_unit(other) - if self.is_unknown() or self.is_no_unit() or other.is_unknown() or \ - other.is_no_unit(): + if ( + self.is_unknown() + or self.is_no_unit() + or other.is_unknown() + or other.is_no_unit() + ): result = False else: - result = (self.calendar == other.calendar and - _ud.are_convertible(self.ut_unit, other.ut_unit)) + result = self.calendar == other.calendar and _ud.are_convertible( + self.ut_unit, other.ut_unit + ) return result def is_dimensionless(self): @@ -1023,8 +1076,9 @@ def is_dimensionless(self): True """ - return (self.category == _CATEGORY_UDUNIT and - bool(_ud.is_dimensionless(self.ut_unit))) + return self.category == _CATEGORY_UDUNIT and bool( + _ud.is_dimensionless(self.ut_unit) + ) def is_unknown(self): """ @@ -1118,8 +1172,9 @@ def format(self, option=None): option = [option] for i in option: bitmask |= i - encoding = bitmask & \ - (UT_ASCII | UT_ISO_8859_1 | UT_LATIN1 | UT_UTF8) + encoding = bitmask & ( + UT_ASCII | UT_ISO_8859_1 | UT_LATIN1 | UT_UTF8 + ) encoding_str = _encoding_lookup[encoding] result = _ud.format(self.ut_unit, bitmask) @@ -1225,13 +1280,15 @@ def offset_by_time(self, origin): """ if not isinstance(origin, (float, (int,))): - raise TypeError('a numeric type for the origin argument is' - ' required') + raise TypeError( + "a numeric type for the origin argument is" " required" + ) try: ut_unit = _ud.offset_by_time(self.ut_unit, origin) except _ud.UdunitsError as exception: value_error = _ud_value_error( - exception, 'Failed to offset {!r}'.format(self)) + exception, "Failed to offset {!r}".format(self) + ) raise value_error from None calendar = None return Unit._new_from_existing_ut(_CATEGORY_UDUNIT, ut_unit, calendar) @@ -1259,7 +1316,8 @@ def invert(self): else: ut_unit = _ud.invert(self.ut_unit) result = Unit._new_from_existing_ut( - _CATEGORY_UDUNIT, ut_unit, calendar=None) + _CATEGORY_UDUNIT, ut_unit, calendar=None + ) return result def root(self, root): @@ -1286,14 +1344,14 @@ def root(self, root): """ if round(root) != root: - raise TypeError('An integer for the root argument is required') + raise TypeError("An integer for the root argument is required") if self.is_unknown(): result = self elif self.is_no_unit(): raise ValueError("Cannot take the root of a 'no-unit'.") else: # only update the unit if it is not scalar - if self == Unit('1'): + if self == Unit("1"): result = self else: try: @@ -1301,11 +1359,13 @@ def root(self, root): except _ud.UdunitsError as exception: value_error = _ud_value_error( exception, - 'Failed to take the root of {!r}'.format(self)) + "Failed to take the root of {!r}".format(self), + ) raise value_error from None calendar = None result = Unit._new_from_existing_ut( - _CATEGORY_UDUNIT, ut_unit, calendar) + _CATEGORY_UDUNIT, ut_unit, calendar + ) return result def log(self, base): @@ -1336,17 +1396,20 @@ def log(self, base): try: ut_unit = _ud.log(base, self.ut_unit) except TypeError: - raise TypeError('A numeric type for the base argument is ' - ' required') + raise TypeError( + "A numeric type for the base argument is " " required" + ) except _ud.UdunitsError as exception: value_err = _ud_value_error( exception, - 'Failed to calculate logorithmic base ' - 'of {!r}'.format(self)) + "Failed to calculate logorithmic base " + "of {!r}".format(self), + ) raise value_err from None calendar = None result = Unit._new_from_existing_ut( - _CATEGORY_UDUNIT, ut_unit, calendar) + _CATEGORY_UDUNIT, ut_unit, calendar + ) return result def __str__(self): @@ -1385,7 +1448,8 @@ def __repr__(self): result = "{}('{}')".format(self.__class__.__name__, self) else: result = "{}('{}', calendar='{}')".format( - self.__class__.__name__, self, self.calendar) + self.__class__.__name__, self, self.calendar + ) return result def _offset_common(self, offset): @@ -1400,7 +1464,8 @@ def _offset_common(self, offset): result = NotImplemented else: result = Unit._new_from_existing_ut( - _CATEGORY_UDUNIT, ut_unit, calendar=None) + _CATEGORY_UDUNIT, ut_unit, calendar=None + ) return result def __add__(self, other): @@ -1434,11 +1499,13 @@ def _op_common(self, other, op_func): except _ud.UdunitsError as exception: value_err = _ud_value_error( exception, - 'Failed to {} {!r} by {!r}'.format(op_label, self, other)) + "Failed to {} {!r} by {!r}".format(op_label, self, other), + ) raise value_err from None calendar = None result = Unit._new_from_existing_ut( - _CATEGORY_UDUNIT, ut_unit, calendar) + _CATEGORY_UDUNIT, ut_unit, calendar + ) return result def __rmul__(self, other): @@ -1553,14 +1620,15 @@ def __pow__(self, power): try: power = float(power) except ValueError: - raise TypeError('A numeric value is required for the power' - ' argument.') + raise TypeError( + "A numeric value is required for the power" " argument." + ) if self.is_unknown(): result = self elif self.is_no_unit(): raise ValueError("Cannot raise the power of a 'no-unit'.") - elif self == Unit('1'): + elif self == Unit("1"): # 1 ** N -> 1 result = self else: @@ -1570,14 +1638,14 @@ def __pow__(self, power): # root. if not approx_equal(power, 0.0) and abs(power) < 1: if not approx_equal(1 / power, round(1 / power)): - raise ValueError('Cannot raise a unit by a decimal.') + raise ValueError("Cannot raise a unit by a decimal.") root = int(round(1 / power)) result = self.root(root) else: # Failing that, check for powers which are (very nearly) simple # integer values. if not approx_equal(power, round(power)): - msg = 'Cannot raise a unit by a decimal (got %s).' % power + msg = "Cannot raise a unit by a decimal (got %s)." % power raise ValueError(msg) power = int(round(power)) @@ -1586,7 +1654,8 @@ def __pow__(self, power): except _ud.UdunitsError as exception: value_err = _ud_value_error( exception, - 'Failed to raise the power of {!r}'.format(self)) + "Failed to raise the power of {!r}".format(self), + ) raise value_err from None result = Unit._new_from_existing_ut(_CATEGORY_UDUNIT, ut_unit) return result @@ -1717,26 +1786,31 @@ def convert(self, value, other, ctype=FLOAT64, inplace=False): result = copy.deepcopy(value) # Use cftime for converting reference times that are not using a # gregorian calendar as it handles these and udunits does not. - if self.is_time_reference() \ - and self.calendar != CALENDAR_GREGORIAN: + if ( + self.is_time_reference() + and self.calendar != CALENDAR_GREGORIAN + ): result_datetimes = cftime.num2date( - result, self.cftime_unit, self.calendar) + result, self.cftime_unit, self.calendar + ) result = cftime.date2num( - result_datetimes, other.cftime_unit, other.calendar) - convert_type = ( - isinstance(value, np.ndarray) - and np.issubsctype(value.dtype, np.floating) + result_datetimes, other.cftime_unit, other.calendar ) + convert_type = isinstance( + value, np.ndarray + ) and np.issubsctype(value.dtype, np.floating) if convert_type: result = result.astype(value.dtype) else: try: - ut_converter = _ud.get_converter(self.ut_unit, - other.ut_unit) + ut_converter = _ud.get_converter( + self.ut_unit, other.ut_unit + ) except _ud.UdunitsError as exception: value_err = _ud_value_error( exception, - 'Failed to convert {!r} to {!r}'.format(self, other)) + "Failed to convert {!r} to {!r}".format(self, other), + ) raise value_err from None if isinstance(result, np.ndarray): # Can only handle array of np.float32 or np.float64 so @@ -1747,47 +1821,54 @@ def convert(self, value, other, ctype=FLOAT64, inplace=False): # Convert arrays with explicit endianness to native # endianness: udunits seems to be tripped up by arrays # with endianness other than native. - if result.dtype.byteorder != '=': + if result.dtype.byteorder != "=": if inplace: raise ValueError( - 'Unable to convert non-native byte ordered ' - 'array in-place. Consider byte-swapping ' - 'first.') + "Unable to convert non-native byte ordered " + "array in-place. Consider byte-swapping " + "first." + ) else: result = result.astype(result.dtype.type) # Strict type check of numpy array. if result.dtype.type not in (np.float32, np.float64): raise TypeError( - "Expect a numpy array of '%s' or '%s'" % - np.float32, np.float64) + "Expect a numpy array of '%s' or '%s'" + % np.float32, + np.float64, + ) ctype = result.dtype.type # Utilise global convenience dictionary # _cv_convert_array to convert our array in 1d form - result_tmp = result.ravel(order='A') + result_tmp = result.ravel(order="A") # Do the actual conversion. _cv_convert_array[ctype]( - ut_converter, result_tmp, result_tmp) + ut_converter, result_tmp, result_tmp + ) # If result_tmp was a copy, not a view (i.e. not C # contiguous), copy the data back to the original. if not np.shares_memory(result, result_tmp): result_tmp = result_tmp.reshape( - result.shape, order='A') + result.shape, order="A" + ) if isinstance(result, np.ma.MaskedArray): result.data[...] = result_tmp else: result[...] = result_tmp else: if ctype not in _cv_convert_scalar: - raise ValueError('Invalid target type. Can only ' - 'convert to float or double.') + raise ValueError( + "Invalid target type. Can only " + "convert to float or double." + ) # Utilise global convenience dictionary # _cv_convert_scalar - result = _cv_convert_scalar[ctype](ut_converter, - result) + result = _cv_convert_scalar[ctype](ut_converter, result) return result else: - raise ValueError("Unable to convert from '%r' to '%r'." % - (self, other)) + raise ValueError( + "Unable to convert from '%r' to '%r'." % (self, other) + ) @property def cftime_unit(self): @@ -1797,13 +1878,15 @@ def cftime_unit(self): """ if self.calendar is None: - raise ValueError('Unit has undefined calendar') + raise ValueError("Unit has undefined calendar") # `cftime` cannot parse long time intervals ("months" or "years"). if self.is_long_time_interval(): - interval = self.origin.split(' ')[0] - emsg = ('Time units with interval of "months", "years" ' - '(or singular of these) cannot be processed, got "{!s}".') + interval = self.origin.split(" ")[0] + emsg = ( + 'Time units with interval of "months", "years" ' + '(or singular of these) cannot be processed, got "{!s}".' + ) raise ValueError(emsg.format(interval)) # @@ -1899,5 +1982,7 @@ def num2date(self, time_value, only_use_cftime_datetimes=True): """ return _num2date_to_nearest_second( - time_value, self, - only_use_cftime_datetimes=only_use_cftime_datetimes) + time_value, + self, + only_use_cftime_datetimes=only_use_cftime_datetimes, + ) diff --git a/cf_units/_udunits2.pyx b/cf_units/_udunits2.pyx index 14d38c27..7778b386 100644 --- a/cf_units/_udunits2.pyx +++ b/cf_units/_udunits2.pyx @@ -16,8 +16,8 @@ See also: `UDUNITS-2 # cython: nonecheck=True import numpy as np -cimport numpy as np +cimport numpy as np from libc cimport errno, string diff --git a/cf_units/_udunits2_parser/__init__.py b/cf_units/_udunits2_parser/__init__.py index ceedb743..482e3a66 100644 --- a/cf_units/_udunits2_parser/__init__.py +++ b/cf_units/_udunits2_parser/__init__.py @@ -6,25 +6,26 @@ import unicodedata -from antlr4 import InputStream, CommonTokenStream +from antlr4 import CommonTokenStream, InputStream from antlr4.error.ErrorListener import ErrorListener +from . import graph from .parser.udunits2Lexer import udunits2Lexer from .parser.udunits2Parser import udunits2Parser from .parser.udunits2ParserVisitor import udunits2ParserVisitor -from . import graph - # Dictionary mapping token rule id to token name. -TOKEN_ID_NAMES = {getattr(udunits2Lexer, rule, None): rule - for rule in udunits2Lexer.ruleNames} +TOKEN_ID_NAMES = { + getattr(udunits2Lexer, rule, None): rule + for rule in udunits2Lexer.ruleNames +} def handle_UNICODE_EXPONENT(string): # Convert unicode to compatibility form, replacing unicode minus with # ascii minus (which is actually a less good version # of unicode minus). - normd = unicodedata.normalize('NFKC', string).replace('−', '-') + normd = unicodedata.normalize("NFKC", string).replace("−", "-") return int(normd) @@ -33,28 +34,29 @@ class UnitParseVisitor(udunits2ParserVisitor): A visitor which converts the parse tree into an abstract expression graph. """ + #: A dictionary mapping lexer TOKEN names to the action that should be #: taken on them when visited. For full context of what is allowed, see #: visitTerminal. TERM_HANDLERS = { - 'CLOSE_PAREN': None, - 'DATE': str, - 'DIVIDE': graph.Operand('/'), # Drop context, such as " per ". - 'E_POWER': str, - 'FLOAT': graph.Number, # Preserve precision as str. - 'HOUR_MINUTE_SECOND': str, - 'HOUR_MINUTE': str, - 'ID': graph.Identifier, - 'INT': lambda c: graph.Number(int(c)), - 'MULTIPLY': graph.Operand('*'), - 'OPEN_PAREN': None, - 'PERIOD': str, - 'RAISE': graph.Operand, - 'TIMESTAMP': graph.Timestamp, - 'SIGNED_INT': lambda c: graph.Number(int(c)), - 'SHIFT_OP': None, - 'WS': None, - 'UNICODE_EXPONENT': handle_UNICODE_EXPONENT, + "CLOSE_PAREN": None, + "DATE": str, + "DIVIDE": graph.Operand("/"), # Drop context, such as " per ". + "E_POWER": str, + "FLOAT": graph.Number, # Preserve precision as str. + "HOUR_MINUTE_SECOND": str, + "HOUR_MINUTE": str, + "ID": graph.Identifier, + "INT": lambda c: graph.Number(int(c)), + "MULTIPLY": graph.Operand("*"), + "OPEN_PAREN": None, + "PERIOD": str, + "RAISE": graph.Operand, + "TIMESTAMP": graph.Timestamp, + "SIGNED_INT": lambda c: graph.Number(int(c)), + "SHIFT_OP": None, + "WS": None, + "UNICODE_EXPONENT": handle_UNICODE_EXPONENT, } def defaultResult(self): @@ -116,7 +118,7 @@ def visitProduct(self, ctx): # e.g. 1*2/3*4*5 = 1*(2/(3*(4*5))) for node in nodes[:-1][::-1]: if isinstance(node, graph.Operand): - if node.content == '/': + if node.content == "/": op_type = graph.Divide else: op_type = graph.Multiply @@ -152,7 +154,7 @@ def visitShift_spec(self, ctx): def visitUnit_spec(self, ctx): node = self.visitChildren(ctx) if not node: - node = graph.Terminal('') + node = graph.Terminal("") return node @@ -161,13 +163,14 @@ class SyntaxErrorRaiser(ErrorListener): Turn any parse errors into sensible SyntaxErrors. """ + def __init__(self, unit_string): self.unit_string = unit_string super(ErrorListener, self).__init__() def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): # https://stackoverflow.com/a/36367357/741316 - context = ("inline", line, column+2, "'{}'".format(self.unit_string)) + context = ("inline", line, column + 2, "'{}'".format(self.unit_string)) syntax_error = SyntaxError(msg, context) raise syntax_error from None @@ -186,7 +189,7 @@ def _debug_tokens(unit_string): parser.unit_spec() for token in stream.tokens: - if token.text == '': + if token.text == "": continue token_type_idx = token.type rule = TOKEN_ID_NAMES[token_type_idx] diff --git a/cf_units/_udunits2_parser/compile.py b/cf_units/_udunits2_parser/compile.py index 3cea8c7a..cd2a6bc5 100644 --- a/cf_units/_udunits2_parser/compile.py +++ b/cf_units/_udunits2_parser/compile.py @@ -17,42 +17,43 @@ """ import collections -from pathlib import Path import re import subprocess import urllib.request +from pathlib import Path try: import jinja2 except ImportError: - raise ImportError('Jinja2 needed to compile the grammar.') + raise ImportError("Jinja2 needed to compile the grammar.") -JAR_NAME = 'antlr-4.7.2-complete.jar' -JAR_URL = f'https://www.antlr.org/download/{JAR_NAME}' +JAR_NAME = "antlr-4.7.2-complete.jar" +JAR_URL = f"https://www.antlr.org/download/{JAR_NAME}" HERE = Path(__file__).resolve().parent JAR = HERE / JAR_NAME -LEXER = HERE / 'parser' / 'udunits2Lexer.g4' -PARSER = HERE / 'udunits2Parser.g4' +LEXER = HERE / "parser" / "udunits2Lexer.g4" +PARSER = HERE / "udunits2Parser.g4" def expand_lexer(source, target): - MODE_P = re.compile(r'mode ([A-Z_]+)\;') - TOKEN_P = re.compile(r'([A-Z_]+) ?\:.*') + MODE_P = re.compile(r"mode ([A-Z_]+)\;") + TOKEN_P = re.compile(r"([A-Z_]+) ?\:.*") - with open(source, 'r') as fh: + with open(source, "r") as fh: content = fh.read() - template = jinja2.Environment( - loader=jinja2.BaseLoader).from_string(content) + template = jinja2.Environment(loader=jinja2.BaseLoader).from_string( + content + ) - current_mode = 'DEFAULT_MODE' + current_mode = "DEFAULT_MODE" tokens = collections.defaultdict(list) - for line in content.split('\n'): + for line in content.split("\n"): mode_g = MODE_P.match(line) if mode_g: current_mode = mode_g.group(1) @@ -62,33 +63,50 @@ def expand_lexer(source, target): tokens[current_mode].append(token_g.group(1)) new_content = template.render(tokens=tokens) - with open(target, 'w') as fh: + with open(target, "w") as fh: fh.write(new_content) def main(): if not JAR.exists(): - print(f'Downloading {JAR_NAME}...') + print(f"Downloading {JAR_NAME}...") urllib.request.urlretrieve(JAR_URL, str(JAR)) - print('Expanding lexer...') - expand_lexer(LEXER.parent.parent / (LEXER.name + '.jinja'), str(LEXER)) + print("Expanding lexer...") + expand_lexer(LEXER.parent.parent / (LEXER.name + ".jinja"), str(LEXER)) - print('Compiling lexer...') + print("Compiling lexer...") subprocess.run( - ['java', '-jar', str(JAR), '-Dlanguage=Python3', - str(LEXER), '-o', 'parser'], - check=True) - - print('Compiling parser...') + [ + "java", + "-jar", + str(JAR), + "-Dlanguage=Python3", + str(LEXER), + "-o", + "parser", + ], + check=True, + ) + + print("Compiling parser...") subprocess.run( - ['java', '-jar', str(JAR), '-Dlanguage=Python3', - '-no-listener', '-visitor', - str(PARSER), '-o', 'parser'], - check=True) - - print('Done.') - - -if __name__ == '__main__': + [ + "java", + "-jar", + str(JAR), + "-Dlanguage=Python3", + "-no-listener", + "-visitor", + str(PARSER), + "-o", + "parser", + ], + check=True, + ) + + print("Done.") + + +if __name__ == "__main__": main() diff --git a/cf_units/_udunits2_parser/graph.py b/cf_units/_udunits2_parser/graph.py index e6b4c270..ba0bd34f 100644 --- a/cf_units/_udunits2_parser/graph.py +++ b/cf_units/_udunits2_parser/graph.py @@ -10,6 +10,7 @@ class Node: Represents a node in an expression graph. """ + def __init__(self, **kwargs): self._attrs = kwargs @@ -27,13 +28,13 @@ def __getattr__(self, name): def _repr_ctx(self): # Return a dictionary that is useful for passing to string.format. - kwargs = ', '.join( - '{}={!r}'.format(key, value) - for key, value in self._attrs.items()) + kwargs = ", ".join( + "{}={!r}".format(key, value) for key, value in self._attrs.items() + ) return dict(cls_name=self.__class__.__name__, kwargs=kwargs) def __repr__(self): - return '{cls_name}({kwargs})'.format(**self._repr_ctx()) + return "{cls_name}({kwargs})".format(**self._repr_ctx()) class Terminal(Node): @@ -41,6 +42,7 @@ class Terminal(Node): A generic terminal node in an expression graph. """ + def __init__(self, content): super().__init__(content=content) @@ -48,7 +50,7 @@ def children(self): return [] def __str__(self): - return '{}'.format(self.content) + return "{}".format(self.content) class Operand(Terminal): @@ -61,6 +63,7 @@ class Number(Terminal): class Identifier(Terminal): """The unit itself (e.g. meters, m, km and π)""" + pass @@ -71,17 +74,17 @@ def __init__(self, lhs, rhs): class Raise(BinaryOp): def __str__(self): - return f'{self.lhs}^{self.rhs}' + return f"{self.lhs}^{self.rhs}" class Multiply(BinaryOp): def __str__(self): - return f'{self.lhs}·{self.rhs}' + return f"{self.lhs}·{self.rhs}" class Divide(BinaryOp): def __str__(self): - return f'{self.lhs}/{self.rhs}' + return f"{self.lhs}/{self.rhs}" class Shift(Node): @@ -90,7 +93,7 @@ def __init__(self, unit, shift_from): super().__init__(unit=unit, shift_from=shift_from) def __str__(self): - return f'({self.unit} @ {self.shift_from})' + return f"({self.unit} @ {self.shift_from})" class Timestamp(Terminal): @@ -112,9 +115,10 @@ class Visitor: of an expression graph. """ + def visit(self, node): """Visit a node.""" - method = 'visit_' + node.__class__.__name__ + method = "visit_" + node.__class__.__name__ visitor = getattr(self, method, self.generic_visit) return visitor(node) diff --git a/cf_units/_udunits2_parser/parser/udunits2Lexer.py b/cf_units/_udunits2_parser/parser/udunits2Lexer.py index ce86bd13..308b1b1b 100644 --- a/cf_units/_udunits2_parser/parser/udunits2Lexer.py +++ b/cf_units/_udunits2_parser/parser/udunits2Lexer.py @@ -1,9 +1,9 @@ # Generated from /Users/pelson/dev/scitools/cf-units/cf_units/_udunits2_parser/parser/udunits2Lexer.g4 by ANTLR 4.7.2 -from antlr4 import * +import sys from io import StringIO from typing.io import TextIO -import sys +from antlr4 import * def serializedATN(): @@ -14,8 +14,8 @@ def serializedATN(): buf.write("\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22") buf.write("\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27") buf.write("\4\30\t\30\4\31\t\31\4\32\t\32\4\33\t\33\4\34\t\34\4\35") - buf.write("\t\35\4\36\t\36\4\37\t\37\4 \t \4!\t!\4\"\t\"\4#\t#\4") - buf.write("$\t$\4%\t%\4&\t&\4\'\t\'\4(\t(\4)\t)\4*\t*\4+\t+\4,\t") + buf.write('\t\35\4\36\t\36\4\37\t\37\4 \t \4!\t!\4"\t"\4#\t#\4') + buf.write("$\t$\4%\t%\4&\t&\4'\t'\4(\t(\4)\t)\4*\t*\4+\t+\4,\t") buf.write(",\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t\62\4\63") buf.write("\t\63\4\64\t\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\4") buf.write("9\t9\4:\t:\4;\t;\4<\t<\4=\t=\4>\t>\4?\t?\4@\t@\4A\tA\4") @@ -46,10 +46,10 @@ def serializedATN(): buf.write("\35\3\35\3\35\5\35\u0172\n\35\3\36\5\36\u0175\n\36\3\36") buf.write("\3\36\3\36\3\36\3\36\3\36\3\36\5\36\u017e\n\36\3\37\3") buf.write("\37\5\37\u0182\n\37\3\37\5\37\u0185\n\37\3\37\5\37\u0188") - buf.write("\n\37\3 \3 \3 \3 \3 \3 \3!\3!\3!\3!\3\"\3\"\3#\3#\3#\3") + buf.write('\n\37\3 \3 \3 \3 \3 \3 \3!\3!\3!\3!\3"\3"\3#\3#\3#\3') buf.write("#\3#\3#\5#\u019c\n#\3$\3$\3$\5$\u01a1\n$\5$\u01a3\n$\3") buf.write("%\3%\3%\5%\u01a8\n%\5%\u01aa\n%\3%\3%\3%\3&\3&\3&\3&\3") - buf.write("\'\3\'\3\'\3\'\3(\3(\3(\3(\3)\3)\3)\3)\3*\3*\3*\3*\3+") + buf.write("'\3'\3'\3'\3(\3(\3(\3(\3)\3)\3)\3)\3*\3*\3*\3*\3+") buf.write("\3+\3+\3+\3,\3,\3,\3,\3-\3-\3-\3-\3.\3.\3.\3.\3/\3/\3") buf.write("/\3/\3\60\3\60\3\60\3\60\3\61\3\61\3\61\3\61\3\62\3\62") buf.write("\3\62\3\62\3\63\3\63\3\63\3\63\3\64\3\64\3\64\3\64\3\65") @@ -62,7 +62,7 @@ def serializedATN(): buf.write("J\3J\3J\3K\3K\3K\3K\3K\3L\3L\3L\3L\3L\3M\3M\3M\3M\3M\3") buf.write("N\3N\3N\3N\3N\3O\3O\3O\3O\3O\2\2P\5\3\7\4\t\2\13\5\r\6") buf.write("\17\7\21\b\23\t\25\n\27\13\31\2\33\f\35\r\37\16!\17#\20") - buf.write("%\21\'\22)\23+\24-\25/\26\61\27\63\2\65\2\67\29\2;\2=") + buf.write("%\21'\22)\23+\24-\25/\26\61\27\63\2\65\2\67\29\2;\2=") buf.write("\2?\2A\30C\31E\32G\33I\2K\34M\35O\2Q\2S\2U\2W\2Y\2[\2") buf.write("]\2_\2a\2c\2e\2g\2i\2k\2m\2o\2q\2s\2u\2w\2y\2{\2}\2\177") buf.write("\2\u0081\2\u0083\2\u0085\2\u0087\2\u0089\2\u008b\2\u008d") @@ -71,11 +71,11 @@ def serializedATN(): buf.write("g\6\2\u00b4\u00b5\u00bb\u00bb\u2072\u2072\u2076\u207d") buf.write("\5\2C\\aac|\13\2\u0082\u0082\u00af\u00af\u00b2\u00b2\u00b7") buf.write("\u00b7\u00c2\u00d8\u00da\u00f8\u00fa\u0101\u03ab\u03ab") - buf.write("\u03c2\u03c2\3\2\"\"\2\u0292\2\5\3\2\2\2\2\7\3\2\2\2\2") + buf.write('\u03c2\u03c2\3\2""\2\u0292\2\5\3\2\2\2\2\7\3\2\2\2\2') buf.write("\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23") buf.write("\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2\2\33\3\2\2\2\2\35\3") buf.write("\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2\2\2\2%\3\2\2\2\2") - buf.write("\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2/\3\2\2\2") + buf.write("'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2/\3\2\2\2") buf.write("\3\61\3\2\2\2\3A\3\2\2\2\3C\3\2\2\2\3E\3\2\2\2\3G\3\2") buf.write("\2\2\3K\3\2\2\2\3M\3\2\2\2\3O\3\2\2\2\3Q\3\2\2\2\3S\3") buf.write("\2\2\2\3U\3\2\2\2\3W\3\2\2\2\3Y\3\2\2\2\3[\3\2\2\2\3]") @@ -92,7 +92,7 @@ def serializedATN(): buf.write("\2\2\21\u00ca\3\2\2\2\23\u00cc\3\2\2\2\25\u00ce\3\2\2") buf.write("\2\27\u00d1\3\2\2\2\31\u00d7\3\2\2\2\33\u00d9\3\2\2\2") buf.write("\35\u00ef\3\2\2\2\37\u0103\3\2\2\2!\u0108\3\2\2\2#\u010f") - buf.write("\3\2\2\2%\u011a\3\2\2\2\'\u011c\3\2\2\2)\u0134\3\2\2\2") + buf.write("\3\2\2\2%\u011a\3\2\2\2'\u011c\3\2\2\2)\u0134\3\2\2\2") buf.write("+\u013a\3\2\2\2-\u013c\3\2\2\2/\u013e\3\2\2\2\61\u0147") buf.write("\3\2\2\2\63\u014b\3\2\2\2\65\u0156\3\2\2\2\67\u0159\3") buf.write("\2\2\29\u0160\3\2\2\2;\u0171\3\2\2\2=\u017d\3\2\2\2?\u017f") @@ -119,10 +119,10 @@ def serializedATN(): buf.write("\3\2\2\2\u00ae\f\3\2\2\2\u00af\u00b1\5-\26\2\u00b0\u00af") buf.write("\3\2\2\2\u00b1\u00b4\3\2\2\2\u00b2\u00b0\3\2\2\2\u00b2") buf.write("\u00b3\3\2\2\2\u00b3\u00c0\3\2\2\2\u00b4\u00b2\3\2\2\2") - buf.write("\u00b5\u00c1\7\61\2\2\u00b6\u00b7\7\"\2\2\u00b7\u00b8") + buf.write('\u00b5\u00c1\7\61\2\2\u00b6\u00b7\7"\2\2\u00b7\u00b8') buf.write("\7R\2\2\u00b8\u00b9\7G\2\2\u00b9\u00ba\7T\2\2\u00ba\u00c1") - buf.write("\7\"\2\2\u00bb\u00bc\7\"\2\2\u00bc\u00bd\7r\2\2\u00bd") - buf.write("\u00be\7g\2\2\u00be\u00bf\7t\2\2\u00bf\u00c1\7\"\2\2\u00c0") + buf.write('\7"\2\2\u00bb\u00bc\7"\2\2\u00bc\u00bd\7r\2\2\u00bd') + buf.write('\u00be\7g\2\2\u00be\u00bf\7t\2\2\u00bf\u00c1\7"\2\2\u00c0') buf.write("\u00b5\3\2\2\2\u00c0\u00b6\3\2\2\2\u00c0\u00bb\3\2\2\2") buf.write("\u00c1\u00c5\3\2\2\2\u00c2\u00c4\5-\26\2\u00c3\u00c2\3") buf.write("\2\2\2\u00c4\u00c7\3\2\2\2\u00c5\u00c3\3\2\2\2\u00c5\u00c6") @@ -155,7 +155,7 @@ def serializedATN(): buf.write("\2\2\2\u0104\u0105\3\2\2\2\u0105\u0106\b\17\2\2\u0106") buf.write(" \3\2\2\2\u0107\u0109\t\4\2\2\u0108\u0107\3\2\2\2\u0109") buf.write("\u010a\3\2\2\2\u010a\u0108\3\2\2\2\u010a\u010b\3\2\2\2") - buf.write("\u010b\"\3\2\2\2\u010c\u0110\7`\2\2\u010d\u010e\7,\2\2") + buf.write('\u010b"\3\2\2\2\u010c\u0110\7`\2\2\u010d\u010e\7,\2\2') buf.write("\u010e\u0110\7,\2\2\u010f\u010c\3\2\2\2\u010f\u010d\3") buf.write("\2\2\2\u0110$\3\2\2\2\u0111\u0112\7n\2\2\u0112\u0113\7") buf.write("q\2\2\u0113\u011b\7i\2\2\u0114\u0115\7n\2\2\u0115\u011b") @@ -227,7 +227,7 @@ def serializedATN(): buf.write("\u01aa\u01ab\3\2\2\2\u01ab\u01ac\7V\2\2\u01ac\u01ad\5") buf.write("I$\2\u01adL\3\2\2\2\u01ae\u01af\5G#\2\u01af\u01b0\7V\2") buf.write("\2\u01b0\u01b1\5I$\2\u01b1N\3\2\2\2\u01b2\u01b3\5\5\2") - buf.write("\2\u01b3\u01b4\3\2\2\2\u01b4\u01b5\b\'\4\2\u01b5P\3\2") + buf.write("\2\u01b3\u01b4\3\2\2\2\u01b4\u01b5\b'\4\2\u01b5P\3\2") buf.write("\2\2\u01b6\u01b7\5\7\3\2\u01b7\u01b8\3\2\2\2\u01b8\u01b9") buf.write("\b(\5\2\u01b9R\3\2\2\2\u01ba\u01bb\5\13\5\2\u01bb\u01bc") buf.write("\3\2\2\2\u01bc\u01bd\b)\6\2\u01bdT\3\2\2\2\u01be\u01bf") @@ -247,7 +247,7 @@ def serializedATN(): buf.write("\2\2\2\u01e4\u01e5\b\63\20\2\u01e5h\3\2\2\2\u01e6\u01e7") buf.write("\5#\21\2\u01e7\u01e8\3\2\2\2\u01e8\u01e9\b\64\21\2\u01e9") buf.write("j\3\2\2\2\u01ea\u01eb\5%\22\2\u01eb\u01ec\3\2\2\2\u01ec") - buf.write("\u01ed\b\65\22\2\u01edl\3\2\2\2\u01ee\u01ef\5\'\23\2\u01ef") + buf.write("\u01ed\b\65\22\2\u01edl\3\2\2\2\u01ee\u01ef\5'\23\2\u01ef") buf.write("\u01f0\3\2\2\2\u01f0\u01f1\b\66\23\2\u01f1n\3\2\2\2\u01f2") buf.write("\u01f3\5)\24\2\u01f3\u01f4\3\2\2\2\u01f4\u01f5\b\67\24") buf.write("\2\u01f5p\3\2\2\2\u01f6\u01f7\5+\25\2\u01f7\u01f8\3\2") @@ -284,7 +284,7 @@ def serializedATN(): buf.write("\bI\21\2\u024b\u024c\bI\30\2\u024c\u0094\3\2\2\2\u024d") buf.write("\u024e\5%\22\2\u024e\u024f\3\2\2\2\u024f\u0250\bJ\22\2") buf.write("\u0250\u0251\bJ\30\2\u0251\u0096\3\2\2\2\u0252\u0253\5") - buf.write("\'\23\2\u0253\u0254\3\2\2\2\u0254\u0255\bK\23\2\u0255") + buf.write("'\23\2\u0253\u0254\3\2\2\2\u0254\u0255\bK\23\2\u0255") buf.write("\u0256\bK\30\2\u0256\u0098\3\2\2\2\u0257\u0258\5)\24\2") buf.write("\u0258\u0259\3\2\2\2\u0259\u025a\bL\24\2\u025a\u025b\b") buf.write("L\30\2\u025b\u009a\3\2\2\2\u025c\u025d\5+\25\2\u025d\u025e") @@ -308,7 +308,7 @@ class udunits2Lexer(Lexer): atn = ATNDeserializer().deserialize(serializedATN()) - decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] + decisionsToDFA = [DFA(ds, i) for i, ds in enumerate(atn.decisionToState)] SHIFT_MODE = 1 ID_SEEN = 2 @@ -341,49 +341,131 @@ class udunits2Lexer(Lexer): TIMESTAMP = 26 DT_T_CLOCK = 27 - channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ] + channelNames = ["DEFAULT_TOKEN_CHANNEL", "HIDDEN"] - modeNames = [ "DEFAULT_MODE", "SHIFT_MODE", "ID_SEEN" ] + modeNames = ["DEFAULT_MODE", "SHIFT_MODE", "ID_SEEN"] - literalNames = [ "", - "'+'", "'.'", "'('", "')'", "':'", "'-'" ] + literalNames = ["", "'+'", "'.'", "'('", "')'", "':'", "'-'"] - symbolicNames = [ "", - "SIGNED_INT", "PLUS", "MULTIPLY", "DIVIDE", "PERIOD", "OPEN_PAREN", - "CLOSE_PAREN", "SEMICOLON", "INT", "E_POWER", "FLOAT", "SHIFT_OP", - "UNICODE_EXPONENT", "RAISE", "LOG", "LOGREF", "ID", "LATIN_SUBSET", - "WS", "ERRORCHARACTER", "TIMEZONE", "HOUR_MINUTE_SECOND", "HOUR_MINUTE", - "M_MINUS", "DATE", "TIMESTAMP", "DT_T_CLOCK" ] + symbolicNames = [ + "", + "SIGNED_INT", + "PLUS", + "MULTIPLY", + "DIVIDE", + "PERIOD", + "OPEN_PAREN", + "CLOSE_PAREN", + "SEMICOLON", + "INT", + "E_POWER", + "FLOAT", + "SHIFT_OP", + "UNICODE_EXPONENT", + "RAISE", + "LOG", + "LOGREF", + "ID", + "LATIN_SUBSET", + "WS", + "ERRORCHARACTER", + "TIMEZONE", + "HOUR_MINUTE_SECOND", + "HOUR_MINUTE", + "M_MINUS", + "DATE", + "TIMESTAMP", + "DT_T_CLOCK", + ] - ruleNames = [ "SIGNED_INT", "PLUS", "MINUS", "MULTIPLY", "DIVIDE", "PERIOD", - "OPEN_PAREN", "CLOSE_PAREN", "SEMICOLON", "INT", "ANY_INT", - "E_POWER", "FLOAT", "SHIFT_OP", "UNICODE_EXPONENT", "RAISE", - "LOG", "LOGREF", "ID", "LATIN_SUBSET", "WS", "ERRORCHARACTER", - "TIMEZONE", "SIGN", "HOUR", "MINUTE", "SECOND", "MONTH", - "DAY", "YEAR", "HOUR_MINUTE_SECOND", "HOUR_MINUTE", "M_MINUS", - "DATE", "CLOCK", "TIMESTAMP", "DT_T_CLOCK", "SHIFT_MODE_SIGNED_INT", - "SHIFT_MODE_PLUS", "SHIFT_MODE_MULTIPLY", "SHIFT_MODE_DIVIDE", - "SHIFT_MODE_PERIOD", "SHIFT_MODE_OPEN_PAREN", "SHIFT_MODE_CLOSE_PAREN", - "SHIFT_MODE_SEMICOLON", "SHIFT_MODE_INT", "SHIFT_MODE_E_POWER", - "SHIFT_MODE_FLOAT", "SHIFT_MODE_SHIFT_OP", "SHIFT_MODE_UNICODE_EXPONENT", - "SHIFT_MODE_RAISE", "SHIFT_MODE_LOG", "SHIFT_MODE_LOGREF", - "SHIFT_MODE_ID", "SHIFT_MODE_LATIN_SUBSET", "SHIFT_MODE_WS", - "SHIFT_MODE_ERRORCHARACTER", "ID_SEEN_SIGNED_INT", "EXTRA_MULTIPLY", - "ID_SEEN_AUTO_SIGNED_INT", "ID_SEEN_AUTO_PLUS", "ID_SEEN_AUTO_MULTIPLY", - "ID_SEEN_AUTO_DIVIDE", "ID_SEEN_AUTO_PERIOD", "ID_SEEN_AUTO_OPEN_PAREN", - "ID_SEEN_AUTO_CLOSE_PAREN", "ID_SEEN_AUTO_SEMICOLON", - "ID_SEEN_AUTO_INT", "ID_SEEN_AUTO_E_POWER", "ID_SEEN_AUTO_SHIFT_OP", - "ID_SEEN_AUTO_UNICODE_EXPONENT", "ID_SEEN_AUTO_RAISE", - "ID_SEEN_AUTO_LOG", "ID_SEEN_AUTO_LOGREF", "ID_SEEN_AUTO_ID", - "ID_SEEN_AUTO_LATIN_SUBSET", "ID_SEEN_AUTO_WS", "ID_SEEN_AUTO_ERRORCHARACTER" ] + ruleNames = [ + "SIGNED_INT", + "PLUS", + "MINUS", + "MULTIPLY", + "DIVIDE", + "PERIOD", + "OPEN_PAREN", + "CLOSE_PAREN", + "SEMICOLON", + "INT", + "ANY_INT", + "E_POWER", + "FLOAT", + "SHIFT_OP", + "UNICODE_EXPONENT", + "RAISE", + "LOG", + "LOGREF", + "ID", + "LATIN_SUBSET", + "WS", + "ERRORCHARACTER", + "TIMEZONE", + "SIGN", + "HOUR", + "MINUTE", + "SECOND", + "MONTH", + "DAY", + "YEAR", + "HOUR_MINUTE_SECOND", + "HOUR_MINUTE", + "M_MINUS", + "DATE", + "CLOCK", + "TIMESTAMP", + "DT_T_CLOCK", + "SHIFT_MODE_SIGNED_INT", + "SHIFT_MODE_PLUS", + "SHIFT_MODE_MULTIPLY", + "SHIFT_MODE_DIVIDE", + "SHIFT_MODE_PERIOD", + "SHIFT_MODE_OPEN_PAREN", + "SHIFT_MODE_CLOSE_PAREN", + "SHIFT_MODE_SEMICOLON", + "SHIFT_MODE_INT", + "SHIFT_MODE_E_POWER", + "SHIFT_MODE_FLOAT", + "SHIFT_MODE_SHIFT_OP", + "SHIFT_MODE_UNICODE_EXPONENT", + "SHIFT_MODE_RAISE", + "SHIFT_MODE_LOG", + "SHIFT_MODE_LOGREF", + "SHIFT_MODE_ID", + "SHIFT_MODE_LATIN_SUBSET", + "SHIFT_MODE_WS", + "SHIFT_MODE_ERRORCHARACTER", + "ID_SEEN_SIGNED_INT", + "EXTRA_MULTIPLY", + "ID_SEEN_AUTO_SIGNED_INT", + "ID_SEEN_AUTO_PLUS", + "ID_SEEN_AUTO_MULTIPLY", + "ID_SEEN_AUTO_DIVIDE", + "ID_SEEN_AUTO_PERIOD", + "ID_SEEN_AUTO_OPEN_PAREN", + "ID_SEEN_AUTO_CLOSE_PAREN", + "ID_SEEN_AUTO_SEMICOLON", + "ID_SEEN_AUTO_INT", + "ID_SEEN_AUTO_E_POWER", + "ID_SEEN_AUTO_SHIFT_OP", + "ID_SEEN_AUTO_UNICODE_EXPONENT", + "ID_SEEN_AUTO_RAISE", + "ID_SEEN_AUTO_LOG", + "ID_SEEN_AUTO_LOGREF", + "ID_SEEN_AUTO_ID", + "ID_SEEN_AUTO_LATIN_SUBSET", + "ID_SEEN_AUTO_WS", + "ID_SEEN_AUTO_ERRORCHARACTER", + ] grammarFileName = "udunits2Lexer.g4" - def __init__(self, input=None, output:TextIO = sys.stdout): + def __init__(self, input=None, output: TextIO = sys.stdout): super().__init__(input, output) self.checkVersion("4.7.2") - self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache()) + self._interp = LexerATNSimulator( + self, self.atn, self.decisionsToDFA, PredictionContextCache() + ) self._actions = None self._predicates = None - - diff --git a/cf_units/_udunits2_parser/parser/udunits2Parser.py b/cf_units/_udunits2_parser/parser/udunits2Parser.py index 47e1a8ea..46bc98aa 100644 --- a/cf_units/_udunits2_parser/parser/udunits2Parser.py +++ b/cf_units/_udunits2_parser/parser/udunits2Parser.py @@ -1,9 +1,10 @@ # Generated from /Users/pelson/dev/scitools/cf-units/cf_units/_udunits2_parser/udunits2Parser.g4 by ANTLR 4.7.2 # encoding: utf-8 -from antlr4 import * +import sys from io import StringIO from typing.io import TextIO -import sys + +from antlr4 import * def serializedATN(): @@ -26,9 +27,9 @@ def serializedATN(): buf.write("\2\24\u0081\3\2\2\2\26\30\5\4\3\2\27\26\3\2\2\2\27\30") buf.write("\3\2\2\2\30\31\3\2\2\2\31\32\7\2\2\3\32\3\3\2\2\2\33\61") buf.write("\5\6\4\2\34\36\5\6\4\2\35\37\7\25\2\2\36\35\3\2\2\2\36") - buf.write("\37\3\2\2\2\37 \3\2\2\2 \"\7\16\2\2!#\7\25\2\2\"!\3\2") - buf.write("\2\2\"#\3\2\2\2#$\3\2\2\2$%\5\16\b\2%\61\3\2\2\2&(\5\6") - buf.write("\4\2\')\7\25\2\2(\'\3\2\2\2()\3\2\2\2)*\3\2\2\2*,\7\16") + buf.write('\37\3\2\2\2\37 \3\2\2\2 "\7\16\2\2!#\7\25\2\2"!\3\2') + buf.write('\2\2"#\3\2\2\2#$\3\2\2\2$%\5\16\b\2%\61\3\2\2\2&(\5\6') + buf.write("\4\2')\7\25\2\2('\3\2\2\2()\3\2\2\2)*\3\2\2\2*,\7\16") buf.write("\2\2+-\7\25\2\2,+\3\2\2\2,-\3\2\2\2-.\3\2\2\2./\5\20\t") buf.write("\2/\61\3\2\2\2\60\33\3\2\2\2\60\34\3\2\2\2\60&\3\2\2\2") buf.write("\61\5\3\2\2\2\62\63\b\4\1\2\63\64\5\b\5\2\64F\3\2\2\2") @@ -52,33 +53,78 @@ def serializedATN(): buf.write("\21\3\2\2\2z~\7\30\2\2{~\7\31\2\2|~\5\f\7\2}z\3\2\2\2") buf.write("}{\3\2\2\2}|\3\2\2\2~\23\3\2\2\2\177\u0082\7\31\2\2\u0080") buf.write("\u0082\5\f\7\2\u0081\177\3\2\2\2\u0081\u0080\3\2\2\2\u0082") - buf.write("\25\3\2\2\2\26\27\36\"(,\60ADFT\\bgknsvx}\u0081") + buf.write('\25\3\2\2\2\26\27\36"(,\60ADFT\\bgknsvx}\u0081') return buf.getvalue() -class udunits2Parser ( Parser ): +class udunits2Parser(Parser): grammarFileName = "udunits2Parser.g4" atn = ATNDeserializer().deserialize(serializedATN()) - decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] + decisionsToDFA = [DFA(ds, i) for i, ds in enumerate(atn.decisionToState)] sharedContextCache = PredictionContextCache() - literalNames = [ "", "", "'+'", "", "", - "'.'", "'('", "')'", "':'", "", "", - "", "", "", "", - "", "", "", "", - "", "", "", "", - "", "'-'" ] - - symbolicNames = [ "", "SIGNED_INT", "PLUS", "MULTIPLY", "DIVIDE", - "PERIOD", "OPEN_PAREN", "CLOSE_PAREN", "SEMICOLON", - "INT", "E_POWER", "FLOAT", "SHIFT_OP", "UNICODE_EXPONENT", - "RAISE", "LOG", "LOGREF", "ID", "LATIN_SUBSET", "WS", - "ERRORCHARACTER", "TIMEZONE", "HOUR_MINUTE_SECOND", - "HOUR_MINUTE", "M_MINUS", "DATE", "TIMESTAMP", "DT_T_CLOCK" ] + literalNames = [ + "", + "", + "'+'", + "", + "", + "'.'", + "'('", + "')'", + "':'", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "'-'", + ] + + symbolicNames = [ + "", + "SIGNED_INT", + "PLUS", + "MULTIPLY", + "DIVIDE", + "PERIOD", + "OPEN_PAREN", + "CLOSE_PAREN", + "SEMICOLON", + "INT", + "E_POWER", + "FLOAT", + "SHIFT_OP", + "UNICODE_EXPONENT", + "RAISE", + "LOG", + "LOGREF", + "ID", + "LATIN_SUBSET", + "WS", + "ERRORCHARACTER", + "TIMEZONE", + "HOUR_MINUTE_SECOND", + "HOUR_MINUTE", + "M_MINUS", + "DATE", + "TIMESTAMP", + "DT_T_CLOCK", + ] RULE_unit_spec = 0 RULE_shift_spec = 1 @@ -91,50 +137,63 @@ class udunits2Parser ( Parser ): RULE_signed_clock = 8 RULE_timezone_offset = 9 - ruleNames = [ "unit_spec", "shift_spec", "product", "power", "basic_spec", - "integer", "number", "timestamp", "signed_clock", "timezone_offset" ] + ruleNames = [ + "unit_spec", + "shift_spec", + "product", + "power", + "basic_spec", + "integer", + "number", + "timestamp", + "signed_clock", + "timezone_offset", + ] EOF = Token.EOF - SIGNED_INT=1 - PLUS=2 - MULTIPLY=3 - DIVIDE=4 - PERIOD=5 - OPEN_PAREN=6 - CLOSE_PAREN=7 - SEMICOLON=8 - INT=9 - E_POWER=10 - FLOAT=11 - SHIFT_OP=12 - UNICODE_EXPONENT=13 - RAISE=14 - LOG=15 - LOGREF=16 - ID=17 - LATIN_SUBSET=18 - WS=19 - ERRORCHARACTER=20 - TIMEZONE=21 - HOUR_MINUTE_SECOND=22 - HOUR_MINUTE=23 - M_MINUS=24 - DATE=25 - TIMESTAMP=26 - DT_T_CLOCK=27 - - def __init__(self, input:TokenStream, output:TextIO = sys.stdout): + SIGNED_INT = 1 + PLUS = 2 + MULTIPLY = 3 + DIVIDE = 4 + PERIOD = 5 + OPEN_PAREN = 6 + CLOSE_PAREN = 7 + SEMICOLON = 8 + INT = 9 + E_POWER = 10 + FLOAT = 11 + SHIFT_OP = 12 + UNICODE_EXPONENT = 13 + RAISE = 14 + LOG = 15 + LOGREF = 16 + ID = 17 + LATIN_SUBSET = 18 + WS = 19 + ERRORCHARACTER = 20 + TIMEZONE = 21 + HOUR_MINUTE_SECOND = 22 + HOUR_MINUTE = 23 + M_MINUS = 24 + DATE = 25 + TIMESTAMP = 26 + DT_T_CLOCK = 27 + + def __init__(self, input: TokenStream, output: TextIO = sys.stdout): super().__init__(input, output) self.checkVersion("4.7.2") - self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache) + self._interp = ParserATNSimulator( + self, self.atn, self.decisionsToDFA, self.sharedContextCache + ) self._predicates = None - - - class Unit_specContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser @@ -142,36 +201,42 @@ def EOF(self): return self.getToken(udunits2Parser.EOF, 0) def shift_spec(self): - return self.getTypedRuleContext(udunits2Parser.Shift_specContext,0) - + return self.getTypedRuleContext( + udunits2Parser.Shift_specContext, 0 + ) def getRuleIndex(self): return udunits2Parser.RULE_unit_spec - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitUnit_spec" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitUnit_spec"): return visitor.visitUnit_spec(self) else: return visitor.visitChildren(self) - - - def unit_spec(self): localctx = udunits2Parser.Unit_specContext(self, self._ctx, self.state) self.enterRule(localctx, 0, self.RULE_unit_spec) - self._la = 0 # Token type + self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) self.state = 21 self._errHandler.sync(self) _la = self._input.LA(1) - if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << udunits2Parser.SIGNED_INT) | (1 << udunits2Parser.OPEN_PAREN) | (1 << udunits2Parser.INT) | (1 << udunits2Parser.FLOAT) | (1 << udunits2Parser.ID))) != 0): + if ((_la) & ~0x3F) == 0 and ( + (1 << _la) + & ( + (1 << udunits2Parser.SIGNED_INT) + | (1 << udunits2Parser.OPEN_PAREN) + | (1 << udunits2Parser.INT) + | (1 << udunits2Parser.FLOAT) + | (1 << udunits2Parser.ID) + ) + ) != 0: self.state = 20 self.shift_spec() - self.state = 23 self.match(udunits2Parser.EOF) except RecognitionException as re: @@ -182,55 +247,54 @@ def unit_spec(self): self.exitRule() return localctx - class Shift_specContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser def product(self): - return self.getTypedRuleContext(udunits2Parser.ProductContext,0) - + return self.getTypedRuleContext(udunits2Parser.ProductContext, 0) def SHIFT_OP(self): return self.getToken(udunits2Parser.SHIFT_OP, 0) def number(self): - return self.getTypedRuleContext(udunits2Parser.NumberContext,0) + return self.getTypedRuleContext(udunits2Parser.NumberContext, 0) - - def WS(self, i:int=None): + def WS(self, i: int = None): if i is None: return self.getTokens(udunits2Parser.WS) else: return self.getToken(udunits2Parser.WS, i) def timestamp(self): - return self.getTypedRuleContext(udunits2Parser.TimestampContext,0) - + return self.getTypedRuleContext(udunits2Parser.TimestampContext, 0) def getRuleIndex(self): return udunits2Parser.RULE_shift_spec - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitShift_spec" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitShift_spec"): return visitor.visitShift_spec(self) else: return visitor.visitChildren(self) - - - def shift_spec(self): - localctx = udunits2Parser.Shift_specContext(self, self._ctx, self.state) + localctx = udunits2Parser.Shift_specContext( + self, self._ctx, self.state + ) self.enterRule(localctx, 2, self.RULE_shift_spec) - self._la = 0 # Token type + self._la = 0 # Token type try: self.state = 46 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,5,self._ctx) + la_ = self._interp.adaptivePredict(self._input, 5, self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) self.state = 25 @@ -244,21 +308,19 @@ def shift_spec(self): self.state = 28 self._errHandler.sync(self) _la = self._input.LA(1) - if _la==udunits2Parser.WS: + if _la == udunits2Parser.WS: self.state = 27 self.match(udunits2Parser.WS) - self.state = 30 self.match(udunits2Parser.SHIFT_OP) self.state = 32 self._errHandler.sync(self) _la = self._input.LA(1) - if _la==udunits2Parser.WS: + if _la == udunits2Parser.WS: self.state = 31 self.match(udunits2Parser.WS) - self.state = 34 self.number() pass @@ -270,26 +332,23 @@ def shift_spec(self): self.state = 38 self._errHandler.sync(self) _la = self._input.LA(1) - if _la==udunits2Parser.WS: + if _la == udunits2Parser.WS: self.state = 37 self.match(udunits2Parser.WS) - self.state = 40 self.match(udunits2Parser.SHIFT_OP) self.state = 42 self._errHandler.sync(self) _la = self._input.LA(1) - if _la==udunits2Parser.WS: + if _la == udunits2Parser.WS: self.state = 41 self.match(udunits2Parser.WS) - self.state = 44 self.timestamp() pass - except RecognitionException as re: localctx.exception = re self._errHandler.reportError(self, re) @@ -298,20 +357,21 @@ def shift_spec(self): self.exitRule() return localctx - class ProductContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser def power(self): - return self.getTypedRuleContext(udunits2Parser.PowerContext,0) - + return self.getTypedRuleContext(udunits2Parser.PowerContext, 0) def product(self): - return self.getTypedRuleContext(udunits2Parser.ProductContext,0) - + return self.getTypedRuleContext(udunits2Parser.ProductContext, 0) def MULTIPLY(self): return self.getToken(udunits2Parser.MULTIPLY, 0) @@ -319,7 +379,7 @@ def MULTIPLY(self): def DIVIDE(self): return self.getToken(udunits2Parser.DIVIDE, 0) - def WS(self, i:int=None): + def WS(self, i: int = None): if i is None: return self.getTokens(udunits2Parser.WS) else: @@ -328,22 +388,20 @@ def WS(self, i:int=None): def getRuleIndex(self): return udunits2Parser.RULE_product - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitProduct" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitProduct"): return visitor.visitProduct(self) else: return visitor.visitChildren(self) - - - def product(self, _p:int=0): + def product(self, _p: int = 0): _parentctx = self._ctx _parentState = self.state localctx = udunits2Parser.ProductContext(self, self._ctx, _parentState) _prevctx = localctx _startState = 4 self.enterRecursionRule(localctx, 4, self.RULE_product, _p) - self._la = 0 # Token type + self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) self.state = 49 @@ -351,33 +409,53 @@ def product(self, _p:int=0): self._ctx.stop = self._input.LT(-1) self.state = 68 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,8,self._ctx) - while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: - if _alt==1: + _alt = self._interp.adaptivePredict(self._input, 8, self._ctx) + while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: + if _alt == 1: if self._parseListeners is not None: self.triggerExitRuleEvent() _prevctx = localctx self.state = 66 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,7,self._ctx) + la_ = self._interp.adaptivePredict( + self._input, 7, self._ctx + ) if la_ == 1: - localctx = udunits2Parser.ProductContext(self, _parentctx, _parentState) - self.pushNewRecursionContext(localctx, _startState, self.RULE_product) + localctx = udunits2Parser.ProductContext( + self, _parentctx, _parentState + ) + self.pushNewRecursionContext( + localctx, _startState, self.RULE_product + ) self.state = 51 if not self.precpred(self._ctx, 4): - from antlr4.error.Errors import FailedPredicateException - raise FailedPredicateException(self, "self.precpred(self._ctx, 4)") + from antlr4.error.Errors import ( + FailedPredicateException, + ) + + raise FailedPredicateException( + self, "self.precpred(self._ctx, 4)" + ) self.state = 52 self.power() pass elif la_ == 2: - localctx = udunits2Parser.ProductContext(self, _parentctx, _parentState) - self.pushNewRecursionContext(localctx, _startState, self.RULE_product) + localctx = udunits2Parser.ProductContext( + self, _parentctx, _parentState + ) + self.pushNewRecursionContext( + localctx, _startState, self.RULE_product + ) self.state = 53 if not self.precpred(self._ctx, 3): - from antlr4.error.Errors import FailedPredicateException - raise FailedPredicateException(self, "self.precpred(self._ctx, 3)") + from antlr4.error.Errors import ( + FailedPredicateException, + ) + + raise FailedPredicateException( + self, "self.precpred(self._ctx, 3)" + ) self.state = 54 self.match(udunits2Parser.MULTIPLY) self.state = 55 @@ -385,12 +463,21 @@ def product(self, _p:int=0): pass elif la_ == 3: - localctx = udunits2Parser.ProductContext(self, _parentctx, _parentState) - self.pushNewRecursionContext(localctx, _startState, self.RULE_product) + localctx = udunits2Parser.ProductContext( + self, _parentctx, _parentState + ) + self.pushNewRecursionContext( + localctx, _startState, self.RULE_product + ) self.state = 56 if not self.precpred(self._ctx, 2): - from antlr4.error.Errors import FailedPredicateException - raise FailedPredicateException(self, "self.precpred(self._ctx, 2)") + from antlr4.error.Errors import ( + FailedPredicateException, + ) + + raise FailedPredicateException( + self, "self.precpred(self._ctx, 2)" + ) self.state = 57 self.match(udunits2Parser.DIVIDE) self.state = 58 @@ -398,32 +485,40 @@ def product(self, _p:int=0): pass elif la_ == 4: - localctx = udunits2Parser.ProductContext(self, _parentctx, _parentState) - self.pushNewRecursionContext(localctx, _startState, self.RULE_product) + localctx = udunits2Parser.ProductContext( + self, _parentctx, _parentState + ) + self.pushNewRecursionContext( + localctx, _startState, self.RULE_product + ) self.state = 59 if not self.precpred(self._ctx, 1): - from antlr4.error.Errors import FailedPredicateException - raise FailedPredicateException(self, "self.precpred(self._ctx, 1)") - self.state = 61 + from antlr4.error.Errors import ( + FailedPredicateException, + ) + + raise FailedPredicateException( + self, "self.precpred(self._ctx, 1)" + ) + self.state = 61 self._errHandler.sync(self) _la = self._input.LA(1) while True: self.state = 60 self.match(udunits2Parser.WS) - self.state = 63 + self.state = 63 self._errHandler.sync(self) _la = self._input.LA(1) - if not (_la==udunits2Parser.WS): + if not (_la == udunits2Parser.WS): break self.state = 65 self.power() pass - self.state = 70 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,8,self._ctx) + _alt = self._interp.adaptivePredict(self._input, 8, self._ctx) except RecognitionException as re: localctx.exception = re @@ -433,20 +528,23 @@ def product(self, _p:int=0): self.unrollRecursionContexts(_parentctx) return localctx - class PowerContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser def basic_spec(self): - return self.getTypedRuleContext(udunits2Parser.Basic_specContext,0) - + return self.getTypedRuleContext( + udunits2Parser.Basic_specContext, 0 + ) def integer(self): - return self.getTypedRuleContext(udunits2Parser.IntegerContext,0) - + return self.getTypedRuleContext(udunits2Parser.IntegerContext, 0) def RAISE(self): return self.getToken(udunits2Parser.RAISE, 0) @@ -457,15 +555,12 @@ def UNICODE_EXPONENT(self): def getRuleIndex(self): return udunits2Parser.RULE_power - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitPower" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitPower"): return visitor.visitPower(self) else: return visitor.visitChildren(self) - - - def power(self): localctx = udunits2Parser.PowerContext(self, self._ctx, self.state) @@ -473,7 +568,7 @@ def power(self): try: self.state = 82 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,9,self._ctx) + la_ = self._interp.adaptivePredict(self._input, 9, self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) self.state = 71 @@ -506,7 +601,6 @@ def power(self): self.match(udunits2Parser.UNICODE_EXPONENT) pass - except RecognitionException as re: localctx.exception = re self._errHandler.reportError(self, re) @@ -515,10 +609,13 @@ def power(self): self.exitRule() return localctx - class Basic_specContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser @@ -529,31 +626,30 @@ def OPEN_PAREN(self): return self.getToken(udunits2Parser.OPEN_PAREN, 0) def shift_spec(self): - return self.getTypedRuleContext(udunits2Parser.Shift_specContext,0) - + return self.getTypedRuleContext( + udunits2Parser.Shift_specContext, 0 + ) def CLOSE_PAREN(self): return self.getToken(udunits2Parser.CLOSE_PAREN, 0) def number(self): - return self.getTypedRuleContext(udunits2Parser.NumberContext,0) - + return self.getTypedRuleContext(udunits2Parser.NumberContext, 0) def getRuleIndex(self): return udunits2Parser.RULE_basic_spec - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitBasic_spec" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitBasic_spec"): return visitor.visitBasic_spec(self) else: return visitor.visitChildren(self) - - - def basic_spec(self): - localctx = udunits2Parser.Basic_specContext(self, self._ctx, self.state) + localctx = udunits2Parser.Basic_specContext( + self, self._ctx, self.state + ) self.enterRule(localctx, 8, self.RULE_basic_spec) try: self.state = 90 @@ -573,7 +669,11 @@ def basic_spec(self): self.state = 87 self.match(udunits2Parser.CLOSE_PAREN) pass - elif token in [udunits2Parser.SIGNED_INT, udunits2Parser.INT, udunits2Parser.FLOAT]: + elif token in [ + udunits2Parser.SIGNED_INT, + udunits2Parser.INT, + udunits2Parser.FLOAT, + ]: self.enterOuterAlt(localctx, 3) self.state = 89 self.number() @@ -589,10 +689,13 @@ def basic_spec(self): self.exitRule() return localctx - class IntegerContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser @@ -605,25 +708,24 @@ def SIGNED_INT(self): def getRuleIndex(self): return udunits2Parser.RULE_integer - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitInteger" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitInteger"): return visitor.visitInteger(self) else: return visitor.visitChildren(self) - - - def integer(self): localctx = udunits2Parser.IntegerContext(self, self._ctx, self.state) self.enterRule(localctx, 10, self.RULE_integer) - self._la = 0 # Token type + self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) self.state = 92 _la = self._input.LA(1) - if not(_la==udunits2Parser.SIGNED_INT or _la==udunits2Parser.INT): + if not ( + _la == udunits2Parser.SIGNED_INT or _la == udunits2Parser.INT + ): self._errHandler.recoverInline(self) else: self._errHandler.reportMatch(self) @@ -636,16 +738,18 @@ def integer(self): self.exitRule() return localctx - class NumberContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser def integer(self): - return self.getTypedRuleContext(udunits2Parser.IntegerContext,0) - + return self.getTypedRuleContext(udunits2Parser.IntegerContext, 0) def FLOAT(self): return self.getToken(udunits2Parser.FLOAT, 0) @@ -653,15 +757,12 @@ def FLOAT(self): def getRuleIndex(self): return udunits2Parser.RULE_number - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitNumber" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitNumber"): return visitor.visitNumber(self) else: return visitor.visitChildren(self) - - - def number(self): localctx = udunits2Parser.NumberContext(self, self._ctx, self.state) @@ -691,10 +792,13 @@ def number(self): self.exitRule() return localctx - class TimestampContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser @@ -705,18 +809,20 @@ def INT(self): return self.getToken(udunits2Parser.INT, 0) def signed_clock(self): - return self.getTypedRuleContext(udunits2Parser.Signed_clockContext,0) + return self.getTypedRuleContext( + udunits2Parser.Signed_clockContext, 0 + ) - - def WS(self, i:int=None): + def WS(self, i: int = None): if i is None: return self.getTokens(udunits2Parser.WS) else: return self.getToken(udunits2Parser.WS, i) def timezone_offset(self): - return self.getTypedRuleContext(udunits2Parser.Timezone_offsetContext,0) - + return self.getTypedRuleContext( + udunits2Parser.Timezone_offsetContext, 0 + ) def DT_T_CLOCK(self): return self.getToken(udunits2Parser.DT_T_CLOCK, 0) @@ -727,29 +833,28 @@ def TIMESTAMP(self): def getRuleIndex(self): return udunits2Parser.RULE_timestamp - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitTimestamp" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitTimestamp"): return visitor.visitTimestamp(self) else: return visitor.visitChildren(self) - - - def timestamp(self): localctx = udunits2Parser.TimestampContext(self, self._ctx, self.state) self.enterRule(localctx, 14, self.RULE_timestamp) - self._la = 0 # Token type + self._la = 0 # Token type try: self.state = 118 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,17,self._ctx) + la_ = self._interp.adaptivePredict(self._input, 17, self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) self.state = 98 _la = self._input.LA(1) - if not(_la==udunits2Parser.INT or _la==udunits2Parser.DATE): + if not ( + _la == udunits2Parser.INT or _la == udunits2Parser.DATE + ): self._errHandler.recoverInline(self) else: self._errHandler.reportMatch(self) @@ -760,7 +865,9 @@ def timestamp(self): self.enterOuterAlt(localctx, 2) self.state = 99 _la = self._input.LA(1) - if not(_la==udunits2Parser.INT or _la==udunits2Parser.DATE): + if not ( + _la == udunits2Parser.INT or _la == udunits2Parser.DATE + ): self._errHandler.recoverInline(self) else: self._errHandler.reportMatch(self) @@ -768,29 +875,34 @@ def timestamp(self): self.state = 101 self._errHandler.sync(self) _la = self._input.LA(1) - if _la==udunits2Parser.WS: + if _la == udunits2Parser.WS: self.state = 100 self.match(udunits2Parser.WS) - self.state = 103 self.signed_clock() self.state = 108 self._errHandler.sync(self) _la = self._input.LA(1) - if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << udunits2Parser.SIGNED_INT) | (1 << udunits2Parser.INT) | (1 << udunits2Parser.WS) | (1 << udunits2Parser.HOUR_MINUTE))) != 0): + if ((_la) & ~0x3F) == 0 and ( + (1 << _la) + & ( + (1 << udunits2Parser.SIGNED_INT) + | (1 << udunits2Parser.INT) + | (1 << udunits2Parser.WS) + | (1 << udunits2Parser.HOUR_MINUTE) + ) + ) != 0: self.state = 105 self._errHandler.sync(self) _la = self._input.LA(1) - if _la==udunits2Parser.WS: + if _la == udunits2Parser.WS: self.state = 104 self.match(udunits2Parser.WS) - self.state = 107 self.timezone_offset() - pass elif la_ == 3: @@ -806,22 +918,26 @@ def timestamp(self): self.state = 113 self._errHandler.sync(self) _la = self._input.LA(1) - if _la==udunits2Parser.WS: + if _la == udunits2Parser.WS: self.state = 112 self.match(udunits2Parser.WS) - self.state = 116 self._errHandler.sync(self) _la = self._input.LA(1) - if (((_la) & ~0x3f) == 0 and ((1 << _la) & ((1 << udunits2Parser.SIGNED_INT) | (1 << udunits2Parser.INT) | (1 << udunits2Parser.HOUR_MINUTE))) != 0): + if ((_la) & ~0x3F) == 0 and ( + (1 << _la) + & ( + (1 << udunits2Parser.SIGNED_INT) + | (1 << udunits2Parser.INT) + | (1 << udunits2Parser.HOUR_MINUTE) + ) + ) != 0: self.state = 115 self.timezone_offset() - pass - except RecognitionException as re: localctx.exception = re self._errHandler.reportError(self, re) @@ -830,10 +946,13 @@ def timestamp(self): self.exitRule() return localctx - class Signed_clockContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser @@ -844,24 +963,22 @@ def HOUR_MINUTE(self): return self.getToken(udunits2Parser.HOUR_MINUTE, 0) def integer(self): - return self.getTypedRuleContext(udunits2Parser.IntegerContext,0) - + return self.getTypedRuleContext(udunits2Parser.IntegerContext, 0) def getRuleIndex(self): return udunits2Parser.RULE_signed_clock - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitSigned_clock" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitSigned_clock"): return visitor.visitSigned_clock(self) else: return visitor.visitChildren(self) - - - def signed_clock(self): - localctx = udunits2Parser.Signed_clockContext(self, self._ctx, self.state) + localctx = udunits2Parser.Signed_clockContext( + self, self._ctx, self.state + ) self.enterRule(localctx, 16, self.RULE_signed_clock) try: self.state = 123 @@ -893,10 +1010,13 @@ def signed_clock(self): self.exitRule() return localctx - class Timezone_offsetContext(ParserRuleContext): - - def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + def __init__( + self, + parser, + parent: ParserRuleContext = None, + invokingState: int = -1, + ): super().__init__(parent, invokingState) self.parser = parser @@ -904,24 +1024,22 @@ def HOUR_MINUTE(self): return self.getToken(udunits2Parser.HOUR_MINUTE, 0) def integer(self): - return self.getTypedRuleContext(udunits2Parser.IntegerContext,0) - + return self.getTypedRuleContext(udunits2Parser.IntegerContext, 0) def getRuleIndex(self): return udunits2Parser.RULE_timezone_offset - def accept(self, visitor:ParseTreeVisitor): - if hasattr( visitor, "visitTimezone_offset" ): + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitTimezone_offset"): return visitor.visitTimezone_offset(self) else: return visitor.visitChildren(self) - - - def timezone_offset(self): - localctx = udunits2Parser.Timezone_offsetContext(self, self._ctx, self.state) + localctx = udunits2Parser.Timezone_offsetContext( + self, self._ctx, self.state + ) self.enterRule(localctx, 18, self.RULE_timezone_offset) try: self.state = 127 @@ -948,9 +1066,7 @@ def timezone_offset(self): self.exitRule() return localctx - - - def sempred(self, localctx:RuleContext, ruleIndex:int, predIndex:int): + def sempred(self, localctx: RuleContext, ruleIndex: int, predIndex: int): if self._predicates == None: self._predicates = dict() self._predicates[2] = self.product_sempred @@ -960,23 +1076,15 @@ def sempred(self, localctx:RuleContext, ruleIndex:int, predIndex:int): else: return pred(localctx, predIndex) - def product_sempred(self, localctx:ProductContext, predIndex:int): - if predIndex == 0: - return self.precpred(self._ctx, 4) - - - if predIndex == 1: - return self.precpred(self._ctx, 3) - - - if predIndex == 2: - return self.precpred(self._ctx, 2) - - - if predIndex == 3: - return self.precpred(self._ctx, 1) - - + def product_sempred(self, localctx: ProductContext, predIndex: int): + if predIndex == 0: + return self.precpred(self._ctx, 4) + if predIndex == 1: + return self.precpred(self._ctx, 3) + if predIndex == 2: + return self.precpred(self._ctx, 2) + if predIndex == 3: + return self.precpred(self._ctx, 1) diff --git a/cf_units/_udunits2_parser/parser/udunits2ParserVisitor.py b/cf_units/_udunits2_parser/parser/udunits2ParserVisitor.py index 338b9a07..b26f9060 100644 --- a/cf_units/_udunits2_parser/parser/udunits2ParserVisitor.py +++ b/cf_units/_udunits2_parser/parser/udunits2ParserVisitor.py @@ -1,5 +1,6 @@ # Generated from /Users/pelson/dev/scitools/cf-units/cf_units/_udunits2_parser/udunits2Parser.g4 by ANTLR 4.7.2 from antlr4 import * + if __name__ is not None and "." in __name__: from .udunits2Parser import udunits2Parser else: @@ -7,57 +8,48 @@ # This class defines a complete generic visitor for a parse tree produced by udunits2Parser. + class udunits2ParserVisitor(ParseTreeVisitor): # Visit a parse tree produced by udunits2Parser#unit_spec. - def visitUnit_spec(self, ctx:udunits2Parser.Unit_specContext): + def visitUnit_spec(self, ctx: udunits2Parser.Unit_specContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#shift_spec. - def visitShift_spec(self, ctx:udunits2Parser.Shift_specContext): + def visitShift_spec(self, ctx: udunits2Parser.Shift_specContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#product. - def visitProduct(self, ctx:udunits2Parser.ProductContext): + def visitProduct(self, ctx: udunits2Parser.ProductContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#power. - def visitPower(self, ctx:udunits2Parser.PowerContext): + def visitPower(self, ctx: udunits2Parser.PowerContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#basic_spec. - def visitBasic_spec(self, ctx:udunits2Parser.Basic_specContext): + def visitBasic_spec(self, ctx: udunits2Parser.Basic_specContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#integer. - def visitInteger(self, ctx:udunits2Parser.IntegerContext): + def visitInteger(self, ctx: udunits2Parser.IntegerContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#number. - def visitNumber(self, ctx:udunits2Parser.NumberContext): + def visitNumber(self, ctx: udunits2Parser.NumberContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#timestamp. - def visitTimestamp(self, ctx:udunits2Parser.TimestampContext): + def visitTimestamp(self, ctx: udunits2Parser.TimestampContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#signed_clock. - def visitSigned_clock(self, ctx:udunits2Parser.Signed_clockContext): + def visitSigned_clock(self, ctx: udunits2Parser.Signed_clockContext): return self.visitChildren(ctx) - # Visit a parse tree produced by udunits2Parser#timezone_offset. - def visitTimezone_offset(self, ctx:udunits2Parser.Timezone_offsetContext): + def visitTimezone_offset(self, ctx: udunits2Parser.Timezone_offsetContext): return self.visitChildren(ctx) - -del udunits2Parser \ No newline at end of file +del udunits2Parser diff --git a/cf_units/config.py b/cf_units/config.py index c9b73cc0..888753e8 100644 --- a/cf_units/config.py +++ b/cf_units/config.py @@ -6,7 +6,6 @@ import configparser - import os.path import sys @@ -28,11 +27,11 @@ def get_option(section, option, default=None): ROOT_PATH = os.path.abspath(os.path.dirname(__file__)) # The full path to the configuration directory of the active cf_units instance. -CONFIG_PATH = os.path.join(ROOT_PATH, 'etc') +CONFIG_PATH = os.path.join(ROOT_PATH, "etc") # Load the optional "site.cfg" file if it exists. if sys.version_info >= (3, 2): config = configparser.ConfigParser() else: config = configparser.SafeConfigParser() -config.read([os.path.join(CONFIG_PATH, 'site.cfg')]) +config.read([os.path.join(CONFIG_PATH, "site.cfg")]) diff --git a/cf_units/tests/integration/parse/test_graph.py b/cf_units/tests/integration/parse/test_graph.py index 28e3a767..7cd65665 100644 --- a/cf_units/tests/integration/parse/test_graph.py +++ b/cf_units/tests/integration/parse/test_graph.py @@ -4,58 +4,58 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. -from cf_units._udunits2_parser import parse import cf_units._udunits2_parser.graph as g +from cf_units._udunits2_parser import parse def test_Node_attributes(): - n = g.Node(a=1, kwarg='two', arbitrary_kwargs=3) + n = g.Node(a=1, kwarg="two", arbitrary_kwargs=3) assert n.a == 1 - assert n.kwarg == 'two' + assert n.kwarg == "two" assert n.arbitrary_kwargs == 3 def test_Node_str(): - n = g.Node(a=1, kwarg='two', arbitrary_kwargs=3) + n = g.Node(a=1, kwarg="two", arbitrary_kwargs=3) assert str(n) == "Node(a=1, kwarg='two', arbitrary_kwargs=3)" def test_Node_children(): - n = g.Node(a=1, kwarg='two', arbitrary_kwargs=3) + n = g.Node(a=1, kwarg="two", arbitrary_kwargs=3) # Ordered, and consistent. - assert n.children() == [1, 'two', 3] + assert n.children() == [1, "two", 3] def test_large_graph(): - graph = parse('m2/4.1.2π per second @ 10') + graph = parse("m2/4.1.2π per second @ 10") assert isinstance(graph, g.Shift) unit, shift_from = graph.children() assert isinstance(shift_from, g.Number) - assert str(shift_from) == '10' + assert str(shift_from) == "10" assert isinstance(unit, g.Divide) lhs, rhs = unit.children() - assert str(lhs) == 'm^2/4.1·.2·π' - assert str(rhs) == 'second' + assert str(lhs) == "m^2/4.1·.2·π" + assert str(rhs) == "second" assert isinstance(lhs, g.Multiply) lhs, rhs = lhs.children() - assert str(lhs) == 'm^2/4.1·.2' - assert str(rhs) == 'π' + assert str(lhs) == "m^2/4.1·.2" + assert str(rhs) == "π" assert isinstance(lhs, g.Multiply) lhs, rhs = lhs.children() - assert str(lhs) == 'm^2/4.1' - assert str(rhs) == '.2' + assert str(lhs) == "m^2/4.1" + assert str(rhs) == ".2" assert isinstance(lhs, g.Divide) lhs, rhs = lhs.children() - assert str(lhs) == 'm^2' - assert str(rhs) == '4.1' + assert str(lhs) == "m^2" + assert str(rhs) == "4.1" assert isinstance(lhs, g.Raise) lhs, rhs = lhs.children() - assert str(lhs) == 'm' - assert str(rhs) == '2' + assert str(lhs) == "m" + assert str(rhs) == "2" diff --git a/cf_units/tests/integration/parse/test_parse.py b/cf_units/tests/integration/parse/test_parse.py index 11d14dc0..6c1f9391 100644 --- a/cf_units/tests/integration/parse/test_parse.py +++ b/cf_units/tests/integration/parse/test_parse.py @@ -11,142 +11,129 @@ import cf_units from cf_units._udunits2_parser import normalize - testdata = [ - '', - '1', - '12', - '1.2', - '+1', - '+1.2', - '-1', - '-1.2', - '-1.2e0', - '2e6', - '2e-6', - '2.e-6', - '.1e2', - '.1e2.2', - '2e', # <- TODO: Assert this isn't 2e1, but is infact the unit e *2 - 'm', - 'meter', - + "", + "1", + "12", + "1.2", + "+1", + "+1.2", + "-1", + "-1.2", + "-1.2e0", + "2e6", + "2e-6", + "2.e-6", + ".1e2", + ".1e2.2", + "2e", # <- TODO: Assert this isn't 2e1, but is infact the unit e *2 + "m", + "meter", # Multiplication - '1 2 3', - '1 -2 -3', - '1m', - '1*m', - 'm·m', - '1 m', - '1 m', - 'm -1', - 'm -1.2', - 'm 1', - 'm 1.2', - 'm-+2', - 'm--4', - 'm*1*2', - 'm--2--3', - + "1 2 3", + "1 -2 -3", + "1m", + "1*m", + "m·m", + "1 m", + "1 m", + "m -1", + "m -1.2", + "m 1", + "m 1.2", + "m-+2", + "m--4", + "m*1*2", + "m--2--3", # TODO: add some tests with brackets. - 'm(2.3)', - 'm(2.3m)', - '(1.2)(2.4)', - '(5m(6s-1))', - '2*3*4/5m/6*7*8', - - - 'm/2', - 'm1', - 'm m', - 'm2', - 'm+2', - 'm¹', - 'm²', - 'm³', - '2⁴', # NOTE: Udunits can't do m⁴ for some reason. Bug? - '2⁵', - '2⁴²', - '3⁻²', - 'm2 s2', - 'm^2*s^2', - - '1-2', - '1-2-3', # nb. looks a bit like a date, but it isn't! - 'm-1', - 'm^2', - 'm^+2', - 'm^-1', - 'm.2', # This is 2*m - 'm.+2', # 2*m - 'm.2.4', # This is 2.4 * m - 'm0.2', # But this is 2 m^0 - 'm2.5', # And this is 5m^2 - 'm2.3.4', # 0.4 * m^2 - 'm--1', - + "m(2.3)", + "m(2.3m)", + "(1.2)(2.4)", + "(5m(6s-1))", + "2*3*4/5m/6*7*8", + "m/2", + "m1", + "m m", + "m2", + "m+2", + "m¹", + "m²", + "m³", + "2⁴", # NOTE: Udunits can't do m⁴ for some reason. Bug? + "2⁵", + "2⁴²", + "3⁻²", + "m2 s2", + "m^2*s^2", + "1-2", + "1-2-3", # nb. looks a bit like a date, but it isn't! + "m-1", + "m^2", + "m^+2", + "m^-1", + "m.2", # This is 2*m + "m.+2", # 2*m + "m.2.4", # This is 2.4 * m + "m0.2", # But this is 2 m^0 + "m2.5", # And this is 5m^2 + "m2.3.4", # 0.4 * m^2 + "m--1", # Division - 'm per 2', - 'm per s', - 'm / 2', - + "m per 2", + "m per s", + "m / 2", # Shift - 'm@10', - 'm @10', - 'm @ 10', - 'm@ 10', - 'm from2', - 'm from2e-1', - '(m @ 10) (s @ 10)', - + "m@10", + "m @10", + "m @ 10", + "m@ 10", + "m from2", + "m from2e-1", + "(m @ 10) (s @ 10)", # Date shift - 's from 1990', - 'minutes since 1990', - 'hour@1990', - 'hours from 1990-1', - 'hours from 1990-1-1', - 'hours from 1990-1-1 0', - 'hours from 1990-1-1 0:1:1', - 'hours from 1990-1-1 0:0:1 +2', - 's since 1990-1-2+5:2:2', - 's since 1990-1-2+5:2', - 's since 1990-1-2 5 6:0', # Undocumented packed_clock format? - 's since 19900102T5', # Packed format (undocumented?) - 's since 19900101T190030 +2', - 's since 199022T1', # UGLY! (bug?). - - 's since 1990 +2:0:2.9', - 's since 1990-2T1', - 'hours from 1990-1-1 -19:4:2', - 'hours from 1990-1-1 3+1', - - 'seconds from 1990-1-1 0:0:0 +2550', - 's since 1990-1-2+5:2:2', - 'hours from 1990-1-1 0:1:60', - 'hours from 1990-1-1 0:1:62', - - '(hours since 1900) (s since 1980)', # Really fruity behaviour. - + "s from 1990", + "minutes since 1990", + "hour@1990", + "hours from 1990-1", + "hours from 1990-1-1", + "hours from 1990-1-1 0", + "hours from 1990-1-1 0:1:1", + "hours from 1990-1-1 0:0:1 +2", + "s since 1990-1-2+5:2:2", + "s since 1990-1-2+5:2", + "s since 1990-1-2 5 6:0", # Undocumented packed_clock format? + "s since 19900102T5", # Packed format (undocumented?) + "s since 19900101T190030 +2", + "s since 199022T1", # UGLY! (bug?). + "s since 1990 +2:0:2.9", + "s since 1990-2T1", + "hours from 1990-1-1 -19:4:2", + "hours from 1990-1-1 3+1", + "seconds from 1990-1-1 0:0:0 +2550", + "s since 1990-1-2+5:2:2", + "hours from 1990-1-1 0:1:60", + "hours from 1990-1-1 0:1:62", + "(hours since 1900) (s since 1980)", # Really fruity behaviour. # Unicode / constants - 'π', - 'e', - '°C', + "π", + "e", + "°C", ] invalid = [ - '1 * m', - 'm--m', - '-m', - '.1e2.', - 'm+-1', - '--1', - '+-1', - '--3.1', - '$', - '£', - 'hours from 1990-0-0 0:0:0', - 'hours since 1900-1 10:12 10:0 1', - 's since 1990:01:02T1900 +1', + "1 * m", + "m--m", + "-m", + ".1e2.", + "m+-1", + "--1", + "+-1", + "--3.1", + "$", + "£", + "hours from 1990-0-0 0:0:0", + "hours since 1900-1 10:12 10:0 1", + "s since 1990:01:02T1900 +1", ] @@ -167,10 +154,7 @@ def test_normed_units_equivalent(_, unit_str): assert raw_symbol == parsed_expr_symbol -udunits_bugs = [ - '2¹²³⁴⁵⁶⁷⁸⁹⁰', - 'm⁻²' -] +udunits_bugs = ["2¹²³⁴⁵⁶⁷⁸⁹⁰", "m⁻²"] @pytest.mark.parametrize("_, unit_str", enumerate(invalid)) @@ -184,8 +168,9 @@ def test_invalid_units(_, unit_str): cf_valid = False # Double check that udunits2 can't parse this. - assert cf_valid is False, \ - 'Unit {!r} is unexpectedly valid in UDUNITS2'.format(unit_str) + assert ( + cf_valid is False + ), "Unit {!r} is unexpectedly valid in UDUNITS2".format(unit_str) try: normalize(unit_str) @@ -194,7 +179,7 @@ def test_invalid_units(_, unit_str): can_parse = False # Now confirm that we couldn't parse this either. - msg = 'Parser unexpectedly able to deal with {}'.format(unit_str) + msg = "Parser unexpectedly able to deal with {}".format(unit_str) assert can_parse is False, msg @@ -204,21 +189,20 @@ def multi_enumerate(items): not_udunits = [ - ['foo', 'foo'], - ['mfrom1', 'mfrom^1'], - ['m⁴', 'm^4'], # udunits bug. - ['2¹²³⁴⁵⁶⁷⁸⁹⁰', '2^1234567890'], - + ["foo", "foo"], + ["mfrom1", "mfrom^1"], + ["m⁴", "m^4"], # udunits bug. + ["2¹²³⁴⁵⁶⁷⁸⁹⁰", "2^1234567890"], # Unicode (subset of the subset). - ['À'] * 2, - ['Á'] * 2, - ['Ö'] * 2, - ['Ø'] * 2, - ['ö'] * 2, - ['ø'] * 2, - ['ÿ'] * 2, - ['µ'] * 2, - ['µ°F·Ω⁻¹', 'µ°F·Ω^-1'], + ["À"] * 2, + ["Á"] * 2, + ["Ö"] * 2, + ["Ø"] * 2, + ["ö"] * 2, + ["ø"] * 2, + ["ÿ"] * 2, + ["µ"] * 2, + ["µ°F·Ω⁻¹", "µ°F·Ω^-1"], ] @@ -241,19 +225,19 @@ def test_invalid_in_udunits_but_still_parses(_, unit_str, expected): known_issues = [ # Disabled due to crazy results from UDUNITS. - ['s since +1990 +2:0:2.9', SyntaxError], - ['s since -1990 +2:0:2.9', SyntaxError], - + ["s since +1990 +2:0:2.9", SyntaxError], + ["s since -1990 +2:0:2.9", SyntaxError], # The following are not yet implemented. - ['hours since 2001-12-31 23:59:59.999UTC', SyntaxError], - ['hours since 2001-12-31 23:59:59.999 Z', SyntaxError], - ['hours since 2001-12-31 23:59:59.999 GMT', SyntaxError], - ['0.1 lg(re 1 mW)', SyntaxError], + ["hours since 2001-12-31 23:59:59.999UTC", SyntaxError], + ["hours since 2001-12-31 23:59:59.999 Z", SyntaxError], + ["hours since 2001-12-31 23:59:59.999 GMT", SyntaxError], + ["0.1 lg(re 1 mW)", SyntaxError], ] -@pytest.mark.parametrize("_, unit_str, expected", - multi_enumerate(known_issues)) +@pytest.mark.parametrize( + "_, unit_str, expected", multi_enumerate(known_issues) +) def test_known_issues(_, unit_str, expected): # Unfortunately the grammar is not perfect. # These are the cases that don't work yet but which do work with udunits. @@ -274,7 +258,7 @@ def test_syntax_parse_error_quality(): msg = re.escape(r"no viable alternative at input 'm^m' (inline, line 1)") with pytest.raises(SyntaxError, match=msg) as err: - normalize('m^m 2s') + normalize("m^m 2s") # The problem is with the m after "^", so make sure the exception is # pointing at it (including the leading speechmark). assert err.value.offset == 4 @@ -286,7 +270,7 @@ def test_unknown_symbol_error(): # The × character is explicitly excluded in the UDUNITS2 # implementation. It would make some sense to support it in the # future though. - normalize('Thing×Another') + normalize("Thing×Another") # The 7th character (including the speechmark) is the problem, check that # the exception points at the right location. # correct location... @@ -297,10 +281,10 @@ def test_unknown_symbol_error(): not_allowed = [ - 'hours from 1990-1-1 -20:4:18 +2', - 'm++2', - 'm s^(-1)', - 'm per /s', + "hours from 1990-1-1 -20:4:18 +2", + "m++2", + "m s^(-1)", + "m per /s", ] diff --git a/cf_units/tests/integration/test__num2date_to_nearest_second.py b/cf_units/tests/integration/test__num2date_to_nearest_second.py index 617696b5..f4b12b6b 100644 --- a/cf_units/tests/integration/test__num2date_to_nearest_second.py +++ b/cf_units/tests/integration/test__num2date_to_nearest_second.py @@ -5,11 +5,11 @@ # licensing details. """Test function :func:`cf_units._num2date_to_nearest_second`.""" -import unittest import datetime +import unittest -import numpy as np import cftime +import numpy as np import cf_units from cf_units import _num2date_to_nearest_second @@ -17,15 +17,16 @@ class Test(unittest.TestCase): def setup_units(self, calendar): - self.useconds = cf_units.Unit('seconds since 1970-01-01', calendar) - self.uminutes = cf_units.Unit('minutes since 1970-01-01', calendar) - self.uhours = cf_units.Unit('hours since 1970-01-01', calendar) - self.udays = cf_units.Unit('days since 1970-01-01', calendar) + self.useconds = cf_units.Unit("seconds since 1970-01-01", calendar) + self.uminutes = cf_units.Unit("minutes since 1970-01-01", calendar) + self.uhours = cf_units.Unit("hours since 1970-01-01", calendar) + self.udays = cf_units.Unit("days since 1970-01-01", calendar) def check_dates(self, nums, units, expected, only_cftime=True): for num, unit, exp in zip(nums, units, expected): res = _num2date_to_nearest_second( - num, unit, only_use_cftime_datetimes=only_cftime) + num, unit, only_use_cftime_datetimes=only_cftime + ) self.assertEqual(exp, res) self.assertIsInstance(res, type(exp)) @@ -38,235 +39,295 @@ def check_timedelta(self, nums, units, expected): self.assertEqual((delta.days, seconds), exp) def test_scalar(self): - unit = cf_units.Unit('seconds since 1970-01-01', 'gregorian') - num = 5. + unit = cf_units.Unit("seconds since 1970-01-01", "gregorian") + num = 5.0 exp = datetime.datetime(1970, 1, 1, 0, 0, 5) res = _num2date_to_nearest_second(num, unit) self.assertEqual(exp, res) self.assertIsInstance(res, cftime.datetime) def test_sequence(self): - unit = cf_units.Unit('seconds since 1970-01-01', 'gregorian') - nums = [20., 40., 60., 80, 100.] - exp = [datetime.datetime(1970, 1, 1, 0, 0, 20), - datetime.datetime(1970, 1, 1, 0, 0, 40), - datetime.datetime(1970, 1, 1, 0, 1), - datetime.datetime(1970, 1, 1, 0, 1, 20), - datetime.datetime(1970, 1, 1, 0, 1, 40)] + unit = cf_units.Unit("seconds since 1970-01-01", "gregorian") + nums = [20.0, 40.0, 60.0, 80, 100.0] + exp = [ + datetime.datetime(1970, 1, 1, 0, 0, 20), + datetime.datetime(1970, 1, 1, 0, 0, 40), + datetime.datetime(1970, 1, 1, 0, 1), + datetime.datetime(1970, 1, 1, 0, 1, 20), + datetime.datetime(1970, 1, 1, 0, 1, 40), + ] res = _num2date_to_nearest_second(nums, unit) np.testing.assert_array_equal(exp, res) def test_multidim_sequence(self): - unit = cf_units.Unit('seconds since 1970-01-01', 'gregorian') - nums = [[20., 40., 60.], - [80, 100., 120.]] + unit = cf_units.Unit("seconds since 1970-01-01", "gregorian") + nums = [[20.0, 40.0, 60.0], [80, 100.0, 120.0]] exp_shape = (2, 3) res = _num2date_to_nearest_second(nums, unit) self.assertEqual(exp_shape, res.shape) def test_masked_ndarray(self): - unit = cf_units.Unit('seconds since 1970-01-01', 'gregorian') - nums = np.ma.masked_array([20., 40., 60.], [False, True, False]) - exp = [datetime.datetime(1970, 1, 1, 0, 0, 20), - None, - datetime.datetime(1970, 1, 1, 0, 1)] + unit = cf_units.Unit("seconds since 1970-01-01", "gregorian") + nums = np.ma.masked_array([20.0, 40.0, 60.0], [False, True, False]) + exp = [ + datetime.datetime(1970, 1, 1, 0, 0, 20), + None, + datetime.datetime(1970, 1, 1, 0, 1), + ] res = _num2date_to_nearest_second(nums, unit) np.testing.assert_array_equal(exp, res) # Gregorian Calendar tests def test_simple_gregorian_py_datetime_type(self): - self.setup_units('gregorian') - nums = [20., 40., - 75., 150., - 8., 16., - 300., 600.] - units = [self.useconds, self.useconds, - self.uminutes, self.uminutes, - self.uhours, self.uhours, - self.udays, self.udays] - expected = [datetime.datetime(1970, 1, 1, 0, 0, 20), - datetime.datetime(1970, 1, 1, 0, 0, 40), - datetime.datetime(1970, 1, 1, 1, 15), - datetime.datetime(1970, 1, 1, 2, 30), - datetime.datetime(1970, 1, 1, 8), - datetime.datetime(1970, 1, 1, 16), - datetime.datetime(1970, 10, 28), - datetime.datetime(1971, 8, 24)] + self.setup_units("gregorian") + nums = [20.0, 40.0, 75.0, 150.0, 8.0, 16.0, 300.0, 600.0] + units = [ + self.useconds, + self.useconds, + self.uminutes, + self.uminutes, + self.uhours, + self.uhours, + self.udays, + self.udays, + ] + expected = [ + datetime.datetime(1970, 1, 1, 0, 0, 20), + datetime.datetime(1970, 1, 1, 0, 0, 40), + datetime.datetime(1970, 1, 1, 1, 15), + datetime.datetime(1970, 1, 1, 2, 30), + datetime.datetime(1970, 1, 1, 8), + datetime.datetime(1970, 1, 1, 16), + datetime.datetime(1970, 10, 28), + datetime.datetime(1971, 8, 24), + ] self.check_dates(nums, units, expected, only_cftime=False) def test_simple_gregorian(self): - self.setup_units('gregorian') - nums = [20., 40., - 75., 150., - 8., 16., - 300., 600.] - units = [self.useconds, self.useconds, - self.uminutes, self.uminutes, - self.uhours, self.uhours, - self.udays, self.udays] + self.setup_units("gregorian") + nums = [20.0, 40.0, 75.0, 150.0, 8.0, 16.0, 300.0, 600.0] + units = [ + self.useconds, + self.useconds, + self.uminutes, + self.uminutes, + self.uhours, + self.uhours, + self.udays, + self.udays, + ] expected = [ - cftime.datetime(1970, 1, 1, 0, 0, 20, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 0, 40, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 1, 15, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 2, 30, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 8, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 16, calendar='gregorian'), - cftime.datetime(1970, 10, 28, calendar='gregorian'), - cftime.datetime(1971, 8, 24, calendar='gregorian')] + cftime.datetime(1970, 1, 1, 0, 0, 20, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 0, 40, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 1, 15, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 2, 30, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 8, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 16, calendar="gregorian"), + cftime.datetime(1970, 10, 28, calendar="gregorian"), + cftime.datetime(1971, 8, 24, calendar="gregorian"), + ] self.check_dates(nums, units, expected) def test_fractional_gregorian(self): - self.setup_units('gregorian') - nums = [5./60., 10./60., - 15./60., 30./60., - 8./24., 16./24.] - units = [self.uminutes, self.uminutes, - self.uhours, self.uhours, - self.udays, self.udays] + self.setup_units("gregorian") + nums = [ + 5.0 / 60.0, + 10.0 / 60.0, + 15.0 / 60.0, + 30.0 / 60.0, + 8.0 / 24.0, + 16.0 / 24.0, + ] + units = [ + self.uminutes, + self.uminutes, + self.uhours, + self.uhours, + self.udays, + self.udays, + ] expected = [ - cftime.datetime(1970, 1, 1, 0, 0, 5, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 0, 10, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 15, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 30, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 8, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 16, calendar='gregorian')] + cftime.datetime(1970, 1, 1, 0, 0, 5, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 0, 10, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 15, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 30, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 8, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 16, calendar="gregorian"), + ] self.check_dates(nums, units, expected) def test_fractional_second_gregorian(self): - self.setup_units('gregorian') - nums = [0.25, 0.5, 0.75, - 1.5, 2.5, 3.5, 4.5] + self.setup_units("gregorian") + nums = [0.25, 0.5, 0.75, 1.5, 2.5, 3.5, 4.5] units = [self.useconds] * 7 - expected = [cftime.datetime(1970, 1, 1, 0, 0, 0, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 0, 1, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 0, 1, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 0, 2, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 0, 3, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 0, 4, calendar='gregorian'), - cftime.datetime(1970, 1, 1, 0, 0, 5, calendar='gregorian')] + expected = [ + cftime.datetime(1970, 1, 1, 0, 0, 0, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 0, 1, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 0, 1, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 0, 2, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 0, 3, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 0, 4, calendar="gregorian"), + cftime.datetime(1970, 1, 1, 0, 0, 5, calendar="gregorian"), + ] self.check_dates(nums, units, expected) # 360 day Calendar tests def test_simple_360_day(self): - self.setup_units('360_day') - nums = [20., 40., - 75., 150., - 8., 16., - 300., 600.] - units = [self.useconds, self.useconds, - self.uminutes, self.uminutes, - self.uhours, self.uhours, - self.udays, self.udays] + self.setup_units("360_day") + nums = [20.0, 40.0, 75.0, 150.0, 8.0, 16.0, 300.0, 600.0] + units = [ + self.useconds, + self.useconds, + self.uminutes, + self.uminutes, + self.uhours, + self.uhours, + self.udays, + self.udays, + ] # Expected results in (days, seconds) delta from unit epoch. - expected = [(0, nums[0]), - (0, nums[1]), - (0, nums[2]*60), - (0, nums[3]*60), - (0, nums[4]*60*60), - (0, nums[5]*60*60), - (nums[6], 0), - (nums[7], 0)] + expected = [ + (0, nums[0]), + (0, nums[1]), + (0, nums[2] * 60), + (0, nums[3] * 60), + (0, nums[4] * 60 * 60), + (0, nums[5] * 60 * 60), + (nums[6], 0), + (nums[7], 0), + ] self.check_timedelta(nums, units, expected) def test_fractional_360_day(self): - self.setup_units('360_day') - nums = [5./60., 10./60., - 15./60., 30./60., - 8./24., 16./24.] - units = [self.uminutes, self.uminutes, - self.uhours, self.uhours, - self.udays, self.udays] + self.setup_units("360_day") + nums = [ + 5.0 / 60.0, + 10.0 / 60.0, + 15.0 / 60.0, + 30.0 / 60.0, + 8.0 / 24.0, + 16.0 / 24.0, + ] + units = [ + self.uminutes, + self.uminutes, + self.uhours, + self.uhours, + self.udays, + self.udays, + ] # Expected results in (days, seconds) delta from unit epoch. - expected = [(0, nums[0]*60), - (0, nums[1]*60), - (0, nums[2]*60*60), - (0, nums[3]*60*60), - (0, nums[4]*24*60*60), - (0, nums[5]*24*60*60)] + expected = [ + (0, nums[0] * 60), + (0, nums[1] * 60), + (0, nums[2] * 60 * 60), + (0, nums[3] * 60 * 60), + (0, nums[4] * 24 * 60 * 60), + (0, nums[5] * 24 * 60 * 60), + ] self.check_timedelta(nums, units, expected) def test_fractional_second_360_day(self): - self.setup_units('360_day') - nums = [0.25, 0.5, 0.75, - 1.5, 2.5, 3.5, 4.5] + self.setup_units("360_day") + nums = [0.25, 0.5, 0.75, 1.5, 2.5, 3.5, 4.5] units = [self.useconds] * 7 # Expected results in (days, seconds) delta from unit epoch. - expected = [(0, 0.0), # rounded down to this - (0, 1.0), # rounded up to this - (0, 1.0), # rounded up to this - (0, 2.0), # rounded up to this - (0, 3.0), # rounded up to this - (0, 4.0), # rounded up to this - (0, 5.0)] # rounded up to this + expected = [ + (0, 0.0), # rounded down to this + (0, 1.0), # rounded up to this + (0, 1.0), # rounded up to this + (0, 2.0), # rounded up to this + (0, 3.0), # rounded up to this + (0, 4.0), # rounded up to this + (0, 5.0), + ] # rounded up to this self.check_timedelta(nums, units, expected) # 365 day Calendar tests def test_simple_365_day(self): - self.setup_units('365_day') - nums = [20., 40., - 75., 150., - 8., 16., - 300., 600.] - units = [self.useconds, self.useconds, - self.uminutes, self.uminutes, - self.uhours, self.uhours, - self.udays, self.udays] + self.setup_units("365_day") + nums = [20.0, 40.0, 75.0, 150.0, 8.0, 16.0, 300.0, 600.0] + units = [ + self.useconds, + self.useconds, + self.uminutes, + self.uminutes, + self.uhours, + self.uhours, + self.udays, + self.udays, + ] # Expected results in (days, seconds) delta from unit epoch. - expected = [(0, nums[0]), - (0, nums[1]), - (0, nums[2]*60), - (0, nums[3]*60), - (0, nums[4]*60*60), - (0, nums[5]*60*60), - (nums[6], 0), - (nums[7], 0)] + expected = [ + (0, nums[0]), + (0, nums[1]), + (0, nums[2] * 60), + (0, nums[3] * 60), + (0, nums[4] * 60 * 60), + (0, nums[5] * 60 * 60), + (nums[6], 0), + (nums[7], 0), + ] self.check_timedelta(nums, units, expected) def test_fractional_365_day(self): - self.setup_units('365_day') - nums = [5./60., 10./60., - 15./60., 30./60., - 8./24., 16./24.] - units = [self.uminutes, self.uminutes, - self.uhours, self.uhours, - self.udays, self.udays] + self.setup_units("365_day") + nums = [ + 5.0 / 60.0, + 10.0 / 60.0, + 15.0 / 60.0, + 30.0 / 60.0, + 8.0 / 24.0, + 16.0 / 24.0, + ] + units = [ + self.uminutes, + self.uminutes, + self.uhours, + self.uhours, + self.udays, + self.udays, + ] # Expected results in (days, seconds) delta from unit epoch. - expected = [(0, nums[0]*60), - (0, nums[1]*60), - (0, nums[2]*60*60), - (0, nums[3]*60*60), - (0, nums[4]*24*60*60), - (0, nums[5]*24*60*60)] + expected = [ + (0, nums[0] * 60), + (0, nums[1] * 60), + (0, nums[2] * 60 * 60), + (0, nums[3] * 60 * 60), + (0, nums[4] * 24 * 60 * 60), + (0, nums[5] * 24 * 60 * 60), + ] self.check_timedelta(nums, units, expected) def test_fractional_second_365_day(self): - self.setup_units('365_day') - nums = [0.25, 0.5, 0.75, - 1.5, 2.5, 3.5, 4.5] + self.setup_units("365_day") + nums = [0.25, 0.5, 0.75, 1.5, 2.5, 3.5, 4.5] units = [self.useconds] * 7 # Expected results in (days, seconds) delta from unit epoch. - expected = [(0, 0.0), # rounded down to this - (0, 1.0), # rounded up to this - (0, 1.0), # rounded up to this - (0, 2.0), # rounded up to this - (0, 3.0), # rounded up to this - (0, 4.0), # rounded up to this - (0, 5.0)] # rounded up to this + expected = [ + (0, 0.0), # rounded down to this + (0, 1.0), # rounded up to this + (0, 1.0), # rounded up to this + (0, 2.0), # rounded up to this + (0, 3.0), # rounded up to this + (0, 4.0), # rounded up to this + (0, 5.0), + ] # rounded up to this self.check_timedelta(nums, units, expected) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/cf_units/tests/integration/test_date2num.py b/cf_units/tests/integration/test_date2num.py index 9820d585..7905c31e 100644 --- a/cf_units/tests/integration/test_date2num.py +++ b/cf_units/tests/integration/test_date2num.py @@ -5,8 +5,8 @@ # licensing details. """Test function :func:`cf_units.date2num`.""" -import unittest import datetime +import unittest import numpy as np @@ -15,53 +15,61 @@ class Test(unittest.TestCase): def setUp(self): - self.unit = 'seconds since 1970-01-01' - self.calendar = 'gregorian' + self.unit = "seconds since 1970-01-01" + self.calendar = "gregorian" def test_single(self): date = datetime.datetime(1970, 1, 1, 0, 0, 5) - exp = 5. + exp = 5.0 res = date2num(date, self.unit, self.calendar) # num2date won't return an exact value representing the date, # even if one exists self.assertAlmostEqual(exp, res, places=4) def test_sequence(self): - dates = [datetime.datetime(1970, 1, 1, 0, 0, 20), - datetime.datetime(1970, 1, 1, 0, 0, 40), - datetime.datetime(1970, 1, 1, 0, 1), - datetime.datetime(1970, 1, 1, 0, 1, 20), - datetime.datetime(1970, 1, 1, 0, 1, 40)] - exp = [20., 40., 60., 80, 100.] + dates = [ + datetime.datetime(1970, 1, 1, 0, 0, 20), + datetime.datetime(1970, 1, 1, 0, 0, 40), + datetime.datetime(1970, 1, 1, 0, 1), + datetime.datetime(1970, 1, 1, 0, 1, 20), + datetime.datetime(1970, 1, 1, 0, 1, 40), + ] + exp = [20.0, 40.0, 60.0, 80, 100.0] res = date2num(dates, self.unit, self.calendar) np.testing.assert_array_almost_equal(exp, res, decimal=4) def test_multidim_sequence(self): - dates = [[datetime.datetime(1970, 1, 1, 0, 0, 20), - datetime.datetime(1970, 1, 1, 0, 0, 40), - datetime.datetime(1970, 1, 1, 0, 1)], - [datetime.datetime(1970, 1, 1, 0, 1, 20), - datetime.datetime(1970, 1, 1, 0, 1, 40), - datetime.datetime(1970, 1, 1, 0, 2)]] + dates = [ + [ + datetime.datetime(1970, 1, 1, 0, 0, 20), + datetime.datetime(1970, 1, 1, 0, 0, 40), + datetime.datetime(1970, 1, 1, 0, 1), + ], + [ + datetime.datetime(1970, 1, 1, 0, 1, 20), + datetime.datetime(1970, 1, 1, 0, 1, 40), + datetime.datetime(1970, 1, 1, 0, 2), + ], + ] exp_shape = (2, 3) res = date2num(dates, self.unit, self.calendar) self.assertEqual(exp_shape, res.shape) def test_discard_mircosecond(self): date = datetime.datetime(1970, 1, 1, 0, 0, 5, 750000) - exp = 5. + exp = 5.0 res = date2num(date, self.unit, self.calendar) self.assertAlmostEqual(exp, res, places=4) def test_long_time_interval(self): # This test should fail with an error that we need to catch properly. - unit = 'years since 1970-01-01' + unit = "years since 1970-01-01" date = datetime.datetime(1970, 1, 1, 0, 0, 5) exp_emsg = 'interval of "months", "years" .* got "years".' with self.assertRaisesRegex(ValueError, exp_emsg): date2num(date, unit, self.calendar) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/cf_units/tests/test_coding_standards.py b/cf_units/tests/test_coding_standards.py index a1ebc073..c9689c86 100644 --- a/cf_units/tests/test_coding_standards.py +++ b/cf_units/tests/test_coding_standards.py @@ -4,18 +4,15 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. -from datetime import datetime -from fnmatch import fnmatch -from glob import glob -from itertools import chain import os -import re import subprocess import unittest +from datetime import datetime +from fnmatch import fnmatch +from glob import glob import cf_units - LICENSE_TEMPLATE = """# Copyright cf-units contributors # # This file is part of cf-units and is released under the LGPL license. @@ -27,12 +24,15 @@ # against Python finding the cf_units package via a symlink. DIR = os.path.realpath(os.path.dirname(cf_units.__file__)) REPO_DIR = os.path.dirname(DIR) -DOCS_DIR = os.path.join(REPO_DIR, 'doc') -DOCS_DIR = cf_units.config.get_option('Resources', 'doc_dir', default=DOCS_DIR) -exclusion = ['Makefile', 'make.bat', 'build'] -DOCS_DIRS = glob(os.path.join(DOCS_DIR, '*')) -DOCS_DIRS = [DOC_DIR for DOC_DIR in DOCS_DIRS if os.path.basename(DOC_DIR) not - in exclusion] +DOCS_DIR = os.path.join(REPO_DIR, "doc") +DOCS_DIR = cf_units.config.get_option("Resources", "doc_dir", default=DOCS_DIR) +exclusion = ["Makefile", "make.bat", "build"] +DOCS_DIRS = glob(os.path.join(DOCS_DIR, "*")) +DOCS_DIRS = [ + DOC_DIR + for DOC_DIR in DOCS_DIRS + if os.path.basename(DOC_DIR) not in exclusion +] class TestLicenseHeaders(unittest.TestCase): @@ -53,12 +53,12 @@ def whatchanged_parse(whatchanged_output): for line in whatchanged_output: if not line.strip(): continue - elif line.startswith('TIME:'): + elif line.startswith("TIME:"): dt = datetime.fromtimestamp(int(line[5:])) else: # Non blank, non date, line -> must be the lines # containing the file info. - fname = ' '.join(line.split('\t')[1:]) + fname = " ".join(line.split("\t")[1:]) yield fname, dt @staticmethod @@ -75,16 +75,16 @@ def last_change_by_fname(): """ # Check the ".git" folder exists at the repo dir. - if not os.path.isdir(os.path.join(REPO_DIR, '.git')): - raise ValueError('{} is not a git repository.'.format(REPO_DIR)) + if not os.path.isdir(os.path.join(REPO_DIR, ".git")): + raise ValueError("{} is not a git repository.".format(REPO_DIR)) # Call "git whatchanged" to get the details of all the files and when # they were last changed. - output = subprocess.check_output(['git', 'whatchanged', - "--pretty=TIME:%ct"], - cwd=REPO_DIR) - output = str(output.decode('ascii')) - output = output.split('\n') + output = subprocess.check_output( + ["git", "whatchanged", "--pretty=TIME:%ct"], cwd=REPO_DIR + ) + output = str(output.decode("ascii")) + output = output.split("\n") res = {} for fname, dt in TestLicenseHeaders.whatchanged_parse(output): if fname not in res or dt > res[fname]: @@ -93,38 +93,44 @@ def last_change_by_fname(): return res def test_license_headers(self): - exclude_patterns = ('setup.py', - 'build/*', - 'dist/*', - 'cf_units/_version.py', - 'cf_units/_udunits2_parser/parser/*' - ) + exclude_patterns = ( + "setup.py", + "build/*", + "dist/*", + "cf_units/_version.py", + "cf_units/_udunits2_parser/parser/*", + ) try: last_change_by_fname = self.last_change_by_fname() except ValueError: # Caught the case where this is not a git repo. - return self.skipTest('cf_units installation did not look like a ' - 'git repo.') + return self.skipTest( + "cf_units installation did not look like a " "git repo." + ) failed = False for fname, last_change in sorted(last_change_by_fname.items()): full_fname = os.path.join(REPO_DIR, fname) - if full_fname.endswith('.py') and os.path.isfile(full_fname) and \ - not any(fnmatch(fname, pat) for pat in exclude_patterns): + if ( + full_fname.endswith(".py") + and os.path.isfile(full_fname) + and not any(fnmatch(fname, pat) for pat in exclude_patterns) + ): with open(full_fname) as fh: content = fh.read() if not content.startswith(LICENSE_TEMPLATE): print( - f'The file {fname} does not start with the ' - f'required license header.' + f"The file {fname} does not start with the " + f"required license header." ) failed = True if failed: raise AssertionError( - 'There were license header failures. See stdout.') + "There were license header failures. See stdout." + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/cf_units/tests/test_tex.py b/cf_units/tests/test_tex.py index 37fae3fb..667896c1 100644 --- a/cf_units/tests/test_tex.py +++ b/cf_units/tests/test_tex.py @@ -4,42 +4,40 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. -import pytest - from cf_units.tex import tex def test_basic(): - u = 'kg kg-1' - assert tex(u) == r'{kg}\cdot{{kg}^{-1}}' + u = "kg kg-1" + assert tex(u) == r"{kg}\cdot{{kg}^{-1}}" def test_identifier_micro(): - u = 'microW m-2' - assert tex(u) == r'{{\mu}W}\cdot{{m}^{-2}}' + u = "microW m-2" + assert tex(u) == r"{{\mu}W}\cdot{{m}^{-2}}" def test_raise(): - u = 'm^2' - assert tex(u) == r'{m}^{2}' + u = "m^2" + assert tex(u) == r"{m}^{2}" def test_multiply(): - u = 'foo bar' - assert tex(u) == r'{foo}\cdot{bar}' + u = "foo bar" + assert tex(u) == r"{foo}\cdot{bar}" def test_divide(): - u = 'foo per bar' - assert tex(u) == r'\frac{foo}{bar}' + u = "foo per bar" + assert tex(u) == r"\frac{foo}{bar}" def test_shift(): - u = 'foo @ 50' - assert tex(u) == r'{foo} @ {50}' + u = "foo @ 50" + assert tex(u) == r"{foo} @ {50}" def test_complex(): - u = 'microW^2 / (5^-2)π per sec @ 42' - expected = r'{\frac{{\frac{{{\mu}W}^{2}}{{5}^{-2}}}\cdot{π}}{sec}} @ {42}' + u = "microW^2 / (5^-2)π per sec @ 42" + expected = r"{\frac{{\frac{{{\mu}W}^{2}}{{5}^{-2}}}\cdot{π}}{sec}} @ {42}" assert tex(u) == expected diff --git a/cf_units/tests/test_unit.py b/cf_units/tests/test_unit.py index 1bf51567..f1d58bcd 100644 --- a/cf_units/tests/test_unit.py +++ b/cf_units/tests/test_unit.py @@ -8,540 +8,519 @@ """ -import unittest import copy import datetime as datetime import operator import re - +import unittest from operator import truediv -import numpy as np import cftime +import numpy as np import cf_units as unit from cf_units import suppress_errors - Unit = unit.Unit class Test_unit__creation(unittest.TestCase): - def test_is_valid_unit_string(self): - unit_strs = [' meter', - 'meter ', - ' meter '] + unit_strs = [" meter", "meter ", " meter "] for unit_str in unit_strs: u = Unit(unit_str) - self.assertEqual(u.name, 'meter') + self.assertEqual(u.name, "meter") def test_not_valid_unit_str(self): - with self.assertRaisesRegex(ValueError, 'Failed to parse unit'): - Unit('wibble') + with self.assertRaisesRegex(ValueError, "Failed to parse unit"): + Unit("wibble") def test_calendar(self): calendar = unit.CALENDAR_365_DAY - u = Unit('hours since 1970-01-01 00:00:00', calendar=calendar) + u = Unit("hours since 1970-01-01 00:00:00", calendar=calendar) self.assertEqual(u.calendar, calendar) def test_calendar_alias(self): calendar = unit.CALENDAR_NO_LEAP - u = Unit('hours since 1970-01-01 00:00:00', calendar=calendar) + u = Unit("hours since 1970-01-01 00:00:00", calendar=calendar) self.assertEqual(u.calendar, unit.CALENDAR_365_DAY) def test_no_calendar(self): - u = Unit('hours since 1970-01-01 00:00:00') + u = Unit("hours since 1970-01-01 00:00:00") self.assertEqual(u.calendar, unit.CALENDAR_GREGORIAN) def test_unsupported_calendar(self): - with self.assertRaisesRegex(ValueError, 'unsupported calendar'): - Unit('hours since 1970-01-01 00:00:00', calendar='wibble') + with self.assertRaisesRegex(ValueError, "unsupported calendar"): + Unit("hours since 1970-01-01 00:00:00", calendar="wibble") def test_calendar_w_unicode(self): calendar = unit.CALENDAR_365_DAY - u = Unit(u'hours\xb2 hours-1 since epoch', calendar=calendar) + u = Unit("hours\xb2 hours-1 since epoch", calendar=calendar) self.assertEqual(u.calendar, calendar) - expected = u'hours² hours-1 since 1970-01-01 00:00:00' + expected = "hours² hours-1 since 1970-01-01 00:00:00" self.assertEqual(u.origin, expected) def test_unicode_valid(self): # Some unicode characters are allowed. - u = Unit(u'm²') - assert u.symbol == 'm2' + u = Unit("m²") + assert u.symbol == "m2" def test_py2k_unicode(self): - u = Unit(u'm\xb2') - assert u.symbol == 'm2' + u = Unit("m\xb2") + assert u.symbol == "m2" def test_unicode_invalid(self): # Not all unicode characters are allowed. - msg = r'Failed to parse unit \"ø\"' + msg = r"Failed to parse unit \"ø\"" with self.assertRaisesRegex(ValueError, msg): - Unit('ø') + Unit("ø") class Test_modulus(unittest.TestCase): - def test_modulus__degrees(self): - u = Unit('degrees') + u = Unit("degrees") self.assertEqual(u.modulus, 360.0) def test_modulus__radians(self): - u = Unit('radians') - self.assertEqual(u.modulus, np.pi*2) + u = Unit("radians") + self.assertEqual(u.modulus, np.pi * 2) def test_no_modulus(self): - u = Unit('meter') + u = Unit("meter") self.assertEqual(u.modulus, None) class Test_is_convertible(unittest.TestCase): - def test_convert_distance_to_force(self): - u = Unit('meter') - v = Unit('newton') + u = Unit("meter") + v = Unit("newton") self.assertFalse(u.is_convertible(v)) def test_convert_distance_units(self): - u = Unit('meter') - v = Unit('mile') + u = Unit("meter") + v = Unit("mile") self.assertTrue(u.is_convertible(v)) def test_convert_to_unknown(self): - u = Unit('meter') - v = Unit('unknown') + u = Unit("meter") + v = Unit("unknown") self.assertFalse(u.is_convertible(v)) self.assertFalse(v.is_convertible(u)) def test_convert_to_no_unit(self): - u = Unit('meter') - v = Unit('no unit') + u = Unit("meter") + v = Unit("no unit") self.assertFalse(u.is_convertible(v)) self.assertFalse(v.is_convertible(u)) def test_convert_unknown_to_no_unit(self): - u = Unit('unknown') - v = Unit('no unit') + u = Unit("unknown") + v = Unit("no unit") self.assertFalse(u.is_convertible(v)) self.assertFalse(v.is_convertible(u)) class Test_is_dimensionless(unittest.TestCase): - def test_dimensionless(self): - u = Unit('1') + u = Unit("1") self.assertTrue(u.is_dimensionless()) def test_distance_dimensionless(self): - u = Unit('meter') + u = Unit("meter") self.assertFalse(u.is_dimensionless()) def test_unknown_dimensionless(self): - u = Unit('unknown') + u = Unit("unknown") self.assertFalse(u.is_dimensionless()) def test_no_unit_dimensionless(self): - u = Unit('no unit') + u = Unit("no unit") self.assertFalse(u.is_dimensionless()) class Test_format(unittest.TestCase): - def test_basic(self): - u = Unit('watt') - self.assertEqual(u.format(), 'W') + u = Unit("watt") + self.assertEqual(u.format(), "W") def test_format_ascii(self): - u = Unit('watt') - self.assertEqual(u.format(unit.UT_ASCII), 'W') + u = Unit("watt") + self.assertEqual(u.format(unit.UT_ASCII), "W") def test_format_ut_names(self): - u = Unit('watt') - self.assertEqual(u.format(unit.UT_NAMES), 'watt') + u = Unit("watt") + self.assertEqual(u.format(unit.UT_NAMES), "watt") def test_format_unit_definition(self): - u = Unit('watt') - self.assertEqual(u.format(unit.UT_DEFINITION), 'm2.kg.s-3') + u = Unit("watt") + self.assertEqual(u.format(unit.UT_DEFINITION), "m2.kg.s-3") def test_format_multiple_options(self): - u = Unit('watt') + u = Unit("watt") self.assertEqual( u.format([unit.UT_NAMES, unit.UT_DEFINITION]), - 'meter^2-kilogram-second^-3') + "meter^2-kilogram-second^-3", + ) def test_format_multiple_options_utf8(self): - u = Unit('watt') + u = Unit("watt") self.assertEqual( u.format([unit.UT_NAMES, unit.UT_DEFINITION, unit.UT_UTF8]), - u'meter²·kilogram·second⁻³') + "meter²·kilogram·second⁻³", + ) def test_format_unknown(self): - u = Unit('?') - self.assertEqual(u.format(), 'unknown') + u = Unit("?") + self.assertEqual(u.format(), "unknown") def test_format_no_unit(self): - u = Unit('nounit') - self.assertEqual(u.format(), 'no_unit') + u = Unit("nounit") + self.assertEqual(u.format(), "no_unit") def test_format_names_utf8(self): - u = Unit('m2') - self.assertEqual(u.format([unit.UT_UTF8, unit.UT_NAMES]), u'meter²') + u = Unit("m2") + self.assertEqual(u.format([unit.UT_UTF8, unit.UT_NAMES]), "meter²") def test_format_latin1(self): - u = Unit('m2') - self.assertEqual(u.format(unit.UT_LATIN1), u'm²') + u = Unit("m2") + self.assertEqual(u.format(unit.UT_LATIN1), "m²") class Test_name(unittest.TestCase): - def test_basic(self): - u = Unit('newton') - self.assertEqual(u.name, 'newton') + u = Unit("newton") + self.assertEqual(u.name, "newton") def test_unknown(self): - u = Unit('unknown') - self.assertEqual(u.name, 'unknown') + u = Unit("unknown") + self.assertEqual(u.name, "unknown") def test_no_unit(self): - u = Unit('no unit') - self.assertEqual(u.name, 'no_unit') + u = Unit("no unit") + self.assertEqual(u.name, "no_unit") class Test_symbol(unittest.TestCase): - def test_basic(self): - u = Unit('joule') - self.assertEqual(u.symbol, 'J') + u = Unit("joule") + self.assertEqual(u.symbol, "J") def test_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertEqual(u.symbol, unit._UNKNOWN_UNIT_SYMBOL) def test_no_unit(self): - u = Unit('no unit') + u = Unit("no unit") self.assertEqual(u.symbol, unit._NO_UNIT_SYMBOL) class Test_definition(unittest.TestCase): - def test_basic(self): - u = Unit('joule') - self.assertEqual(u.definition, 'm2.kg.s-2') + u = Unit("joule") + self.assertEqual(u.definition, "m2.kg.s-2") def test_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertEqual(u.definition, unit._UNKNOWN_UNIT_SYMBOL) def test_no_unit(self): - u = Unit('no unit') + u = Unit("no unit") self.assertEqual(u.definition, unit._NO_UNIT_SYMBOL) class Test__apply_offset(unittest.TestCase): - def test_add_integer_offset(self): - u = Unit('meter') - self.assertEqual(u + 10, 'm @ 10') + u = Unit("meter") + self.assertEqual(u + 10, "m @ 10") def test_add_float_offset(self): - u = Unit('meter') - self.assertEqual(u + 100.0, 'm @ 100') + u = Unit("meter") + self.assertEqual(u + 100.0, "m @ 100") def test_not_numerical_offset(self): - u = Unit('meter') - with self.assertRaisesRegex(TypeError, - 'unsupported operand type'): - operator.add(u, 'not_a_number') + u = Unit("meter") + with self.assertRaisesRegex(TypeError, "unsupported operand type"): + operator.add(u, "not_a_number") def test_unit_unknown(self): - u = Unit('unknown') - self.assertEqual(u + 10, 'unknown') + u = Unit("unknown") + self.assertEqual(u + 10, "unknown") def test_no_unit(self): - u = Unit('no unit') - with self.assertRaisesRegex(ValueError, 'Cannot offset'): + u = Unit("no unit") + with self.assertRaisesRegex(ValueError, "Cannot offset"): operator.add(u, 10) class Test_offset_by_time(unittest.TestCase): - def test_offset(self): - u = Unit('hour') + u = Unit("hour") v = u.offset_by_time(unit.encode_time(2007, 1, 15, 12, 6, 0)) - self.assertEqual(v, '(3600 s) @ 20070115T120600.00000000 UTC') + self.assertEqual(v, "(3600 s) @ 20070115T120600.00000000 UTC") def test_not_numerical_offset(self): - u = Unit('hour') - with self.assertRaisesRegex(TypeError, 'numeric type'): - u.offset_by_time('not_a_number') + u = Unit("hour") + with self.assertRaisesRegex(TypeError, "numeric type"): + u.offset_by_time("not_a_number") def test_not_time_unit(self): - u = Unit('mile') - with self.assertRaisesRegex(ValueError, 'Failed to offset'): + u = Unit("mile") + with self.assertRaisesRegex(ValueError, "Failed to offset"): u.offset_by_time(10) def test_unit_unknown(self): - u = Unit('unknown') - emsg = 'Failed to offset' + u = Unit("unknown") + emsg = "Failed to offset" with self.assertRaisesRegex(ValueError, emsg), suppress_errors(): u.offset_by_time(unit.encode_time(1970, 1, 1, 0, 0, 0)) def test_no_unit(self): - u = Unit('no unit') - emsg = 'Failed to offset' + u = Unit("no unit") + emsg = "Failed to offset" with self.assertRaisesRegex(ValueError, emsg), suppress_errors(): u.offset_by_time(unit.encode_time(1970, 1, 1, 0, 0, 0)) class Test_invert(unittest.TestCase): - def test_basic(self): - u = Unit('newton') - self.assertEqual(u.invert(), 'm-1.kg-1.s2') + u = Unit("newton") + self.assertEqual(u.invert(), "m-1.kg-1.s2") def test_double_invert(self): # Double-inverting a unit should take you back to where you started. - u = Unit('newton') + u = Unit("newton") self.assertEqual(u.invert().invert(), u) def test_invert_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertEqual(u.invert(), u) def test_invert_no_unit(self): - u = Unit('no unit') - with self.assertRaisesRegex(ValueError, 'Cannot invert'): + u = Unit("no unit") + with self.assertRaisesRegex(ValueError, "Cannot invert"): u.invert() class Test_root(unittest.TestCase): - def setUp(self): unit.suppress_errors() def test_square_root(self): - u = Unit('volt^2') - self.assertEqual(u.root(2), 'V') + u = Unit("volt^2") + self.assertEqual(u.root(2), "V") def test_square_root_integer_float(self): - u = Unit('volt^2') - self.assertEqual(u.root(2.), 'V') + u = Unit("volt^2") + self.assertEqual(u.root(2.0), "V") def test_not_numeric(self): - u = Unit('volt') - with self.assertRaisesRegex(TypeError, 'numeric type'): - u.offset_by_time('not_a_number') + u = Unit("volt") + with self.assertRaisesRegex(TypeError, "numeric type"): + u.offset_by_time("not_a_number") def test_not_integer(self): - u = Unit('volt') - with self.assertRaisesRegex(TypeError, 'integer .* required'): + u = Unit("volt") + with self.assertRaisesRegex(TypeError, "integer .* required"): u.root(1.2) def test_meaningless_operation(self): - u = Unit('volt') - emsg = 'UT_MEANINGLESS' + u = Unit("volt") + emsg = "UT_MEANINGLESS" with self.assertRaisesRegex(ValueError, emsg), suppress_errors(): u.root(2) def test_unit_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertEqual(u.root(2), u) def test_no_unit(self): - u = Unit('no unit') - with self.assertRaisesRegex(ValueError, 'Cannot take the root'): + u = Unit("no unit") + with self.assertRaisesRegex(ValueError, "Cannot take the root"): u.root(2) class Test_log(unittest.TestCase): - def test_base_2(self): - u = Unit('hPa') - self.assertEqual(u.log(2), 'lb(re 100 Pa)') + u = Unit("hPa") + self.assertEqual(u.log(2), "lb(re 100 Pa)") def test_base_10(self): - u = Unit('hPa') - self.assertEqual(u.log(10), 'lg(re 100 Pa)') + u = Unit("hPa") + self.assertEqual(u.log(10), "lg(re 100 Pa)") def test_negative(self): - u = Unit('hPa') + u = Unit("hPa") msg = re.escape("Failed to calculate logorithmic base of Unit('hPa')") with self.assertRaisesRegex(ValueError, msg): u.log(-1) def test_not_numeric(self): - u = Unit('hPa') - with self.assertRaisesRegex(TypeError, 'numeric type'): - u.log('not_a_number') + u = Unit("hPa") + with self.assertRaisesRegex(TypeError, "numeric type"): + u.log("not_a_number") def test_unit_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertEqual(u.log(10), u) def test_no_unit(self): - u = Unit('no unit') - emsg = 'Cannot take the logarithm' + u = Unit("no unit") + emsg = "Cannot take the logarithm" with self.assertRaisesRegex(ValueError, emsg): u.log(10) class Test_multiply(unittest.TestCase): - def test_multiply_by_int(self): - u = Unit('amp') - self.assertEqual((u * 10).format(), '10 A') + u = Unit("amp") + self.assertEqual((u * 10).format(), "10 A") def test_multiply_by_float(self): - u = Unit('amp') - self.assertEqual((u * 100.0).format(), '100 A') + u = Unit("amp") + self.assertEqual((u * 100.0).format(), "100 A") def test_multiply_electrical_units(self): - u = Unit('amp') - v = Unit('volt') - self.assertEqual((u * v).format(), 'W') + u = Unit("amp") + v = Unit("volt") + self.assertEqual((u * v).format(), "W") def test_multiply_not_numeric(self): - u = Unit('amp') - with self.assertRaisesRegex(ValueError, 'Failed to parse unit'): - operator.mul(u, 'not_a_number') + u = Unit("amp") + with self.assertRaisesRegex(ValueError, "Failed to parse unit"): + operator.mul(u, "not_a_number") def test_multiply_with_unknown_unit(self): - u = Unit('unknown') - v = Unit('meters') + u = Unit("unknown") + v = Unit("meters") self.assertTrue((u * v).is_unknown()) self.assertTrue((v * u).is_unknown()) def test_multiply_with_no_unit(self): - u = Unit('meters') - v = Unit('no unit') - with self.assertRaisesRegex(ValueError, 'Cannot multiply'): + u = Unit("meters") + v = Unit("no unit") + with self.assertRaisesRegex(ValueError, "Cannot multiply"): operator.mul(u, v) operator.mul(v, u) def test_multiply_unknown_and_no_unit(self): - u = Unit('unknown') - v = Unit('no unit') - with self.assertRaisesRegex(ValueError, 'Cannot multiply'): + u = Unit("unknown") + v = Unit("no unit") + with self.assertRaisesRegex(ValueError, "Cannot multiply"): operator.mul(u, v) operator.mul(v, u) class Test_divide(unittest.TestCase): - def test_divide_by_int(self): - u = Unit('watts') - self.assertEqual((u / 10).format(), '0.1 W') + u = Unit("watts") + self.assertEqual((u / 10).format(), "0.1 W") def test_divide_by_float(self): - u = Unit('watts') - self.assertEqual((u / 100.0).format(), '0.01 W') + u = Unit("watts") + self.assertEqual((u / 100.0).format(), "0.01 W") def test_divide_electrical_units(self): - u = Unit('watts') - v = Unit('volts') - self.assertEqual((u / v).format(), 'A') + u = Unit("watts") + v = Unit("volts") + self.assertEqual((u / v).format(), "A") def test_divide_not_numeric(self): - u = Unit('watts') - with self.assertRaisesRegex(ValueError, 'Failed to parse unit'): - truediv(u, 'not_a_number') + u = Unit("watts") + with self.assertRaisesRegex(ValueError, "Failed to parse unit"): + truediv(u, "not_a_number") def test_divide_with_unknown_unit(self): - u = Unit('unknown') - v = Unit('meters') + u = Unit("unknown") + v = Unit("meters") self.assertTrue((u / v).is_unknown()) self.assertTrue((v / u).is_unknown()) def test_divide_with_no_unit(self): - u = Unit('meters') - v = Unit('no unit') - with self.assertRaisesRegex(ValueError, 'Cannot divide'): + u = Unit("meters") + v = Unit("no unit") + with self.assertRaisesRegex(ValueError, "Cannot divide"): truediv(u, v) truediv(v, u) def test_divide_unknown_and_no_unit(self): - u = Unit('unknown') - v = Unit('no unit') - with self.assertRaisesRegex(ValueError, 'Cannot divide'): + u = Unit("unknown") + v = Unit("no unit") + with self.assertRaisesRegex(ValueError, "Cannot divide"): truediv(u, v) truediv(v, u) class Test_power(unittest.TestCase): - def test_basic(self): - u = Unit('m^2') - self.assertEqual(u ** 0.5, Unit('m')) + u = Unit("m^2") + self.assertEqual(u ** 0.5, Unit("m")) def test_integer_power(self): - u = Unit('amp') - self.assertEqual(u ** 2, Unit('A^2')) + u = Unit("amp") + self.assertEqual(u ** 2, Unit("A^2")) def test_float_power(self): - u = Unit('amp') - self.assertEqual(u ** 3.0, Unit('A^3')) + u = Unit("amp") + self.assertEqual(u ** 3.0, Unit("A^3")) def test_dimensionless(self): - u = Unit('1') + u = Unit("1") self.assertEqual(u ** 2, u) def test_power(self): - u = Unit('amp') - self.assertRaises(TypeError, operator.pow, u, Unit('m')) - self.assertRaises(TypeError, operator.pow, u, Unit('unknown')) - self.assertRaises(TypeError, operator.pow, u, Unit('no unit')) + u = Unit("amp") + self.assertRaises(TypeError, operator.pow, u, Unit("m")) + self.assertRaises(TypeError, operator.pow, u, Unit("unknown")) + self.assertRaises(TypeError, operator.pow, u, Unit("no unit")) def test_not_numeric(self): - u = Unit('m^2') - emsg = 'numeric value is required' + u = Unit("m^2") + emsg = "numeric value is required" with self.assertRaisesRegex(TypeError, emsg): - operator.pow(u, 'not_a_number') + operator.pow(u, "not_a_number") def test_bad_power(self): - u = Unit('m^2') - emsg = 'Cannot raise .* by a decimal' + u = Unit("m^2") + emsg = "Cannot raise .* by a decimal" with self.assertRaisesRegex(ValueError, emsg): operator.pow(u, 0.4) def test_unit_power(self): - u = Unit('amp') - v = Unit('m') - emsg = 'argument must be a string or a number' + u = Unit("amp") + v = Unit("m") + emsg = "argument must be a string or a number" with self.assertRaisesRegex(TypeError, emsg): operator.pow(u, v) class Test_power__unknown(unittest.TestCase): - def setUp(self): - self.u = Unit('unknown') + self.u = Unit("unknown") def test_integer_power(self): - self.assertEqual(self.u ** 2, Unit('unknown')) + self.assertEqual(self.u ** 2, Unit("unknown")) def test_float_power(self): - self.assertEqual(self.u ** 3.0, Unit('unknown')) + self.assertEqual(self.u ** 3.0, Unit("unknown")) def test_not_numeric(self): - emsg = 'numeric value is required' + emsg = "numeric value is required" with self.assertRaisesRegex(TypeError, emsg): - operator.pow(self.u, 'not_a_number') + operator.pow(self.u, "not_a_number") def test_bad_power(self): self.assertEqual(self.u ** 0.4, self.u) def test_unit_power(self): - v = Unit('m') - emsg = 'argument must be a string or a number' + v = Unit("m") + emsg = "argument must be a string or a number" with self.assertRaisesRegex(TypeError, emsg): operator.pow(self.u, v) class Test_power__no_unit(unittest.TestCase): - def setUp(self): - self.u = Unit('no unit') + self.u = Unit("no unit") def test_integer_power(self): emsg = "Cannot raise .* a 'no-unit'" @@ -554,9 +533,9 @@ def test_float_power(self): operator.pow(self.u, 3.0) def test_not_numeric(self): - emsg = 'numeric value is required' + emsg = "numeric value is required" with self.assertRaisesRegex(TypeError, emsg): - operator.pow(self.u, 'not_a_number') + operator.pow(self.u, "not_a_number") def test_bad_power(self): emsg = "Cannot raise .* a 'no-unit'" @@ -564,163 +543,159 @@ def test_bad_power(self): operator.pow(self.u, 0.4) def test_unit_power(self): - v = Unit('m') - emsg = 'argument must be a string or a number' + v = Unit("m") + emsg = "argument must be a string or a number" with self.assertRaisesRegex(TypeError, emsg): operator.pow(self.u, v) class Test_copy(unittest.TestCase): - def test_basic(self): - u = Unit('joule') + u = Unit("joule") self.assertEqual(copy.copy(u), u) def test_unit_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertEqual(copy.copy(u), u) self.assertTrue(copy.copy(u).is_unknown()) def test_no_unit(self): - u = Unit('no unit') + u = Unit("no unit") self.assertEqual(copy.copy(u), u) self.assertTrue(copy.copy(u).is_no_unit()) class Test_stringify(unittest.TestCase): - def test___str__(self): - u = Unit('meter') - self.assertEqual(str(u), 'meter') + u = Unit("meter") + self.assertEqual(str(u), "meter") def test___repr___basic(self): - u = Unit('meter') + u = Unit("meter") self.assertEqual(repr(u), "Unit('meter')") def test___repr___time_unit(self): - u = Unit("hours since 2007-01-15 12:06:00", - calendar=unit.CALENDAR_STANDARD) + u = Unit( + "hours since 2007-01-15 12:06:00", calendar=unit.CALENDAR_STANDARD + ) exp = "Unit('hours since 2007-01-15 12:06:00', calendar='gregorian')" self.assertEqual(repr(u), exp) class Test_equality(unittest.TestCase): - def test_basic(self): - u = Unit('meter') - self.assertEqual(u, 'meter') + u = Unit("meter") + self.assertEqual(u, "meter") def test_equivalent_units(self): - u = Unit('meter') - v = Unit('m.s-1') - w = Unit('hertz') - self.assertEqual(u, v/w) + u = Unit("meter") + v = Unit("m.s-1") + w = Unit("hertz") + self.assertEqual(u, v / w) def test_non_equivalent_units(self): - u = Unit('meter') - v = Unit('amp') + u = Unit("meter") + v = Unit("amp") self.assertNotEqual(u, v) def test_eq_cross_category(self): - m = Unit('meter') - u = Unit('unknown') - n = Unit('no_unit') + m = Unit("meter") + u = Unit("unknown") + n = Unit("no_unit") self.assertNotEqual(m, u) self.assertNotEqual(m, n) def test_unknown(self): - u = Unit('unknown') - self.assertEqual(u, 'unknown') + u = Unit("unknown") + self.assertEqual(u, "unknown") def test_no_unit(self): - u = Unit('no_unit') - self.assertEqual(u, 'no_unit') + u = Unit("no_unit") + self.assertEqual(u, "no_unit") def test_unknown_no_unit(self): - u = Unit('unknown') - v = Unit('no_unit') + u = Unit("unknown") + v = Unit("no_unit") self.assertNotEqual(u, v) def test_not_implemented(self): - u = Unit('meter') + u = Unit("meter") self.assertFalse(u == {}) class Test_non_equality(unittest.TestCase): - def test_basic(self): - u = Unit('meter') - self.assertFalse(u != 'meter') + u = Unit("meter") + self.assertFalse(u != "meter") def test_non_equivalent_units(self): - u = Unit('meter') - v = Unit('amp') + u = Unit("meter") + v = Unit("amp") self.assertNotEqual(u, v) def test_ne_cross_category(self): - m = Unit('meter') - u = Unit('unknown') - n = Unit('no_unit') + m = Unit("meter") + u = Unit("unknown") + n = Unit("no_unit") self.assertNotEqual(m, u) self.assertNotEqual(m, n) self.assertNotEqual(u, n) def test_unknown(self): - u = Unit('unknown') - self.assertFalse(u != 'unknown') + u = Unit("unknown") + self.assertFalse(u != "unknown") def test_no_unit(self): - u = Unit('no_unit') - self.assertFalse(u != 'no_unit') + u = Unit("no_unit") + self.assertFalse(u != "no_unit") def test_not_implemented(self): - u = Unit('meter') + u = Unit("meter") self.assertNotEqual(u, {}) class Test_convert(unittest.TestCase): - def test_convert_float(self): - u = Unit('meter') - v = Unit('mile') + u = Unit("meter") + v = Unit("mile") result = u.convert(1609.344, v) expected = 1.0 self.assertEqual(result, expected) def test_convert_int(self): - u = Unit('mile') - v = Unit('meter') + u = Unit("mile") + v = Unit("meter") result = u.convert(1, v) expected = 1609.344 self.assertEqual(result, expected) def test_convert_array(self): - u = Unit('meter') - v = Unit('mile') + u = Unit("meter") + v = Unit("mile") expected = np.arange(2, dtype=np.float32) + 1 result = u.convert(expected * 1609.344, v) np.testing.assert_array_equal(result, expected) def test_convert_array_multidim(self): - u = Unit('meter') - v = Unit('mile') + u = Unit("meter") + v = Unit("mile") expected = (np.arange(2, dtype=np.float32) + 1).reshape([1, 1, 2, 1]) result = u.convert(expected * 1609.344, v) np.testing.assert_array_equal(result, expected) def test_incompatible_units(self): - u = Unit('m') - v = Unit('V') - emsg = 'Unable to convert' + u = Unit("m") + v = Unit("V") + emsg = "Unable to convert" with self.assertRaisesRegex(ValueError, emsg): u.convert(1.0, v) def test_unknown_units(self): - u = Unit('unknown') - v = Unit('no unit') - m = Unit('m') + u = Unit("unknown") + v = Unit("no unit") + m = Unit("m") val = 1.0 - emsg = 'Unable to convert' + emsg = "Unable to convert" with self.assertRaisesRegex(ValueError, emsg): u.convert(val, m) with self.assertRaisesRegex(ValueError, emsg): @@ -729,11 +704,11 @@ def test_unknown_units(self): u.convert(val, v) def test_no_units(self): - u = Unit('no unit') - v = Unit('unknown') - m = Unit('m') + u = Unit("no unit") + v = Unit("unknown") + m = Unit("m") val = 1.0 - emsg = 'Unable to convert' + emsg = "Unable to convert" with self.assertRaisesRegex(ValueError, emsg): u.convert(val, m) with self.assertRaisesRegex(ValueError, emsg): @@ -742,49 +717,47 @@ def test_no_units(self): u.convert(val, v) def test_convert_time_units(self): - u = Unit('seconds since 1978-09-01 00:00:00', calendar='360_day') - v = Unit('seconds since 1979-04-01 00:00:00', calendar='360_day') - u1point = np.array([54432000.], dtype=np.float32) - u2point = np.array([36288000.], dtype=np.float32) + u = Unit("seconds since 1978-09-01 00:00:00", calendar="360_day") + v = Unit("seconds since 1979-04-01 00:00:00", calendar="360_day") + u1point = np.array([54432000.0], dtype=np.float32) + u2point = np.array([36288000.0], dtype=np.float32) res = u.convert(u1point, v) np.testing.assert_array_almost_equal(res, u2point, decimal=6) def test_incompatible_time_units(self): - u = Unit('seconds since 1978-09-01 00:00:00', calendar='360_day') - v = Unit('seconds since 1979-04-01 00:00:00', calendar='gregorian') - u1point = np.array([54432000.], dtype=np.float32) - emsg = 'Unable to convert' + u = Unit("seconds since 1978-09-01 00:00:00", calendar="360_day") + v = Unit("seconds since 1979-04-01 00:00:00", calendar="gregorian") + u1point = np.array([54432000.0], dtype=np.float32) + emsg = "Unable to convert" with self.assertRaisesRegex(ValueError, emsg): u.convert(u1point, v) class Test_order(unittest.TestCase): - def test(self): - m = Unit('meter') - u = Unit('unknown') - n = Unit('no_unit') + m = Unit("meter") + u = Unit("unknown") + n = Unit("no_unit") start = [m, u, n] self.assertEqual(sorted(start), [m, n, u]) class Test_is_unknown(unittest.TestCase): - def _check(self, unknown_str): u = Unit(unknown_str) self.assertTrue(u.is_unknown()) def test_unknown_representations(self): - representations = ['unknown', '?', '???'] + representations = ["unknown", "?", "???"] for representation in representations: self._check(representation) def test_no_unit(self): - u = Unit('no unit') + u = Unit("no unit") self.assertFalse(u.is_unknown()) def test_known_unit(self): - u = Unit('meters') + u = Unit("meters") self.assertFalse(u.is_unknown()) def test_no_ut_pointer(self): @@ -795,68 +768,64 @@ def test_no_ut_pointer(self): class Test_is_no_unit(unittest.TestCase): - def _check(self, no_unit_str): u = Unit(no_unit_str) self.assertTrue(u.is_no_unit()) def test_no_unit_representations(self): - representations = ['no_unit', 'no unit', 'no-unit', 'nounit'] + representations = ["no_unit", "no unit", "no-unit", "nounit"] for representation in representations: self._check(representation) def test_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertFalse(u.is_no_unit()) def test_known_unit(self): - u = Unit('meters') + u = Unit("meters") self.assertFalse(u.is_no_unit()) class Test_is_udunits(unittest.TestCase): - def test_basic(self): - u = Unit('meters') + u = Unit("meters") self.assertTrue(u.is_udunits()) def test_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertFalse(u.is_udunits()) def test_no_unit(self): - u = Unit('no_unit') + u = Unit("no_unit") self.assertFalse(u.is_udunits()) class Test_is_time_reference(unittest.TestCase): - def test_basic(self): - u = Unit('hours since epoch') + u = Unit("hours since epoch") self.assertTrue(u.is_time_reference()) def test_not_time_reference(self): - u = Unit('hours') + u = Unit("hours") self.assertFalse(u.is_time_reference()) def test_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertFalse(u.is_time_reference()) def test_no_unit(self): - u = Unit('no_unit') + u = Unit("no_unit") self.assertFalse(u.is_time_reference()) class Test_title(unittest.TestCase): - def test_basic(self): - u = Unit('meter') - self.assertEqual(u.title(10), '10 meter') + u = Unit("meter") + self.assertEqual(u.title(10), "10 meter") def test_time_unit(self): - u = Unit('hours since epoch', calendar=unit.CALENDAR_STANDARD) - self.assertEqual(u.title(10), '1970-01-01 10:00:00') + u = Unit("hours since epoch", calendar=unit.CALENDAR_STANDARD) + self.assertEqual(u.title(10), "1970-01-01 10:00:00") class Test__immutable(unittest.TestCase): @@ -865,16 +834,16 @@ def _set_attr(self, unit, name): raise ValueError("'Unit' attribute {!r} is mutable!".format(name)) def test_immutable(self): - u = Unit('m') + u = Unit("m") for name in dir(u): - emsg = 'Instances .* are immutable' + emsg = "Instances .* are immutable" with self.assertRaisesRegex(AttributeError, emsg): self._set_attr(u, name) def test_common_hash(self): # Test different instances of length units (m) have a common hash. - u1 = Unit('m') - u2 = Unit('meter') + u1 = Unit("m") + u2 = Unit("meter") u3 = copy.deepcopy(u1) h = set() for u in (u1, u2, u3): @@ -882,8 +851,8 @@ def test_common_hash(self): self.assertEqual(len(h), 1) # Test different instances of electrical units (V) have a common hash. - v1 = Unit('V') - v2 = Unit('volt') + v1 = Unit("V") + v2 = Unit("volt") for u in (v1, v2): h.add(hash(u)) self.assertEqual(len(h), 2) @@ -894,15 +863,15 @@ class Test__inplace(unittest.TestCase): def test(self): # Check conversions do not change original object. - c = Unit('deg_c') - f = Unit('deg_f') + c = Unit("deg_c") + f = Unit("deg_f") orig = np.arange(3, dtype=np.float32) # Test arrays are not equal without inplace conversions. converted = c.convert(orig, f) - emsg = 'Arrays are not equal' + emsg = "Arrays are not equal" with self.assertRaisesRegex(AssertionError, emsg): np.testing.assert_array_equal(orig, converted) self.assertFalse(np.may_share_memory(orig, converted)) @@ -913,12 +882,17 @@ def test(self): self.assertTrue(np.may_share_memory(orig, converted)) def test_multidim_masked(self): - c = Unit('deg_c') - f = Unit('deg_f') + c = Unit("deg_c") + f = Unit("deg_f") # Manufacture a Fortran-ordered nd array to be converted. - orig = np.ma.masked_array(np.arange(4, dtype=np.float32), - mask=[1, 0, 0, 1]).reshape([2, 2]).T + orig = ( + np.ma.masked_array( + np.arange(4, dtype=np.float32), mask=[1, 0, 0, 1] + ) + .reshape([2, 2]) + .T + ) # Test arrays are not equal without inplace conversions. converted = c.convert(orig, f) @@ -934,14 +908,16 @@ def test_multidim_masked(self): self.assertTrue(np.may_share_memory(orig, converted)) def test_foreign_endian(self): - c = Unit('deg_c') - f = Unit('deg_f') + c = Unit("deg_c") + f = Unit("deg_f") # Manufacture a non-native byte-order array to be converted. orig = np.arange(4, dtype=np.float32).newbyteorder().byteswap() - emsg = ('Unable to convert non-native byte ordered array in-place. ' - 'Consider byte-swapping first.') + emsg = ( + "Unable to convert non-native byte ordered array in-place. " + "Consider byte-swapping first." + ) with self.assertRaisesRegex(ValueError, emsg): c.convert(orig, f, inplace=True) @@ -950,7 +926,6 @@ def test_foreign_endian(self): class TestTimeEncoding(unittest.TestCase): - def test_encode_time(self): result = unit.encode_time(2006, 1, 15, 12, 6, 0) self.assertEqual(result, 159019560.0) @@ -964,109 +939,109 @@ def test_encode_clock(self): self.assertEqual(result, 43560.0) def test_decode_time(self): - result = unit.decode_time(158976000.0+43560.0) + result = unit.decode_time(158976000.0 + 43560.0) year, month, day, hour, min, sec, res = result - self.assertEqual((year, month, day, hour, min, sec), - (2006, 1, 15, 12, 6, 0)) + self.assertEqual( + (year, month, day, hour, min, sec), (2006, 1, 15, 12, 6, 0) + ) class TestNumsAndDates(unittest.TestCase): - def test_num2date(self): - u = Unit('hours since 2010-11-02 12:00:00', - calendar=unit.CALENDAR_STANDARD) + u = Unit( + "hours since 2010-11-02 12:00:00", calendar=unit.CALENDAR_STANDARD + ) res = u.num2date(1) - self.assertEqual(str(res), '2010-11-02 13:00:00') - self.assertEqual(res.calendar, 'gregorian') + self.assertEqual(str(res), "2010-11-02 13:00:00") + self.assertEqual(res.calendar, "gregorian") self.assertIsInstance(res, cftime.datetime) def test_num2date_py_datetime_type(self): - u = Unit('hours since 2010-11-02 12:00:00', - calendar=unit.CALENDAR_STANDARD) + u = Unit( + "hours since 2010-11-02 12:00:00", calendar=unit.CALENDAR_STANDARD + ) res = u.num2date(1, only_use_cftime_datetimes=False) - self.assertEqual(str(res), '2010-11-02 13:00:00') + self.assertEqual(str(res), "2010-11-02 13:00:00") self.assertIsInstance(res, datetime.datetime) def test_date2num(self): - u = Unit('hours since 2010-11-02 12:00:00', - calendar=unit.CALENDAR_STANDARD) + u = Unit( + "hours since 2010-11-02 12:00:00", calendar=unit.CALENDAR_STANDARD + ) d = datetime.datetime(2010, 11, 2, 13, 0, 0) - self.assertEqual(str(u.num2date(u.date2num(d))), '2010-11-02 13:00:00') + self.assertEqual(str(u.num2date(u.date2num(d))), "2010-11-02 13:00:00") class Test_as_unit(unittest.TestCase): - def test_already_unit(self): - u = Unit('m') + u = Unit("m") result = unit.as_unit(u) self.assertIs(result, u) def test_known_unit_str(self): - u_str = 'm' - expected = Unit('m') + u_str = "m" + expected = Unit("m") result = unit.as_unit(u_str) self.assertEqual(expected, result) def test_not_unit_str(self): - u_str = 'wibble' - emsg = 'Failed to parse unit' + u_str = "wibble" + emsg = "Failed to parse unit" with self.assertRaisesRegex(ValueError, emsg): unit.as_unit(u_str) def test_unknown_str(self): - u_str = 'unknown' - expected = Unit('unknown') + u_str = "unknown" + expected = Unit("unknown") result = unit.as_unit(u_str) self.assertEqual(expected, result) def test_no_unit_str(self): - u_str = 'no_unit' - expected = Unit('no_unit') + u_str = "no_unit" + expected = Unit("no_unit") result = unit.as_unit(u_str) self.assertEqual(expected, result) class Test_is_time(unittest.TestCase): - def test_basic(self): - u = Unit('hours') + u = Unit("hours") self.assertTrue(u.is_time()) def test_not_time_unit(self): - u = Unit('meters') + u = Unit("meters") self.assertFalse(u.is_time()) def test_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertFalse(u.is_time()) def test_no_unit(self): - u = Unit('no_unit') + u = Unit("no_unit") self.assertFalse(u.is_time()) class Test_is_vertical(unittest.TestCase): - def test_pressure_unit(self): - u = Unit('millibar') + u = Unit("millibar") self.assertTrue(u.is_vertical()) def test_length_unit(self): - u = Unit('meters') + u = Unit("meters") self.assertTrue(u.is_vertical()) def test_not_vertical_unit(self): - u = Unit('hours') + u = Unit("hours") self.assertFalse(u.is_vertical()) def test_unknown(self): - u = Unit('unknown') + u = Unit("unknown") self.assertFalse(u.is_vertical()) def test_no_unit(self): - u = Unit('no_unit') + u = Unit("no_unit") self.assertFalse(u.is_vertical()) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/cf_units/tests/unit/test__udunits2.py b/cf_units/tests/unit/test__udunits2.py index c737cec1..069e8708 100644 --- a/cf_units/tests/unit/test__udunits2.py +++ b/cf_units/tests/unit/test__udunits2.py @@ -16,7 +16,6 @@ from cf_units import _udunits2 as _ud - _ud.set_error_message_handler(_ud.ignore) @@ -25,6 +24,7 @@ class Test_get_system(unittest.TestCase): Test case for operations which create a system object. """ + def test_read_xml(self): system = _ud.read_xml() @@ -32,10 +32,10 @@ def test_read_xml(self): def test_read_xml_invalid_path(self): with self.assertRaises(_ud.UdunitsError) as cm: - _ud.read_xml(b'/not/a/path.xml') + _ud.read_xml(b"/not/a/path.xml") ex = cm.exception - self.assertEqual(ex.status_msg(), 'UT_OPEN_ARG') + self.assertEqual(ex.status_msg(), "UT_OPEN_ARG") self.assertEqual(ex.errnum, errno.ENOENT) @@ -44,42 +44,44 @@ class Test_system(unittest.TestCase): Test case for system operations. """ + def setUp(self): self.system = _ud.read_xml() def test_get_unit_by_name(self): - unit = _ud.get_unit_by_name(self.system, b'metre') + unit = _ud.get_unit_by_name(self.system, b"metre") self.assertIsNotNone(unit) def test_get_unit_by_name_invalid_unit(self): with self.assertRaises(_ud.UdunitsError): - _ud.get_unit_by_name(self.system, b'jigawatt') + _ud.get_unit_by_name(self.system, b"jigawatt") def test_parse(self): - unit = _ud.parse(self.system, b'gigawatt', _ud.UT_ASCII) + unit = _ud.parse(self.system, b"gigawatt", _ud.UT_ASCII) self.assertIsNotNone(unit) def test_parse_latin1(self): - angstrom = _ud.parse(self.system, b'\xe5ngstr\xF6m', _ud.UT_LATIN1) + angstrom = _ud.parse(self.system, b"\xe5ngstr\xF6m", _ud.UT_LATIN1) self.assertIsNotNone(angstrom) def test_parse_ISO_8859_1(self): - angstrom = _ud.parse(self.system, b'\xe5ngstr\xF6m', _ud.UT_ISO_8859_1) + angstrom = _ud.parse(self.system, b"\xe5ngstr\xF6m", _ud.UT_ISO_8859_1) self.assertIsNotNone(angstrom) def test_parse_UTF8(self): - angstrom = _ud.parse(self.system, b'\xc3\xa5ngstr\xc3\xb6m', - _ud.UT_UTF8) + angstrom = _ud.parse( + self.system, b"\xc3\xa5ngstr\xc3\xb6m", _ud.UT_UTF8 + ) self.assertIsNotNone(angstrom) def test_parse_invalid_unit(self): with self.assertRaises(_ud.UdunitsError): - _ud.parse(self.system, b'jigawatt', _ud.UT_ASCII) + _ud.parse(self.system, b"jigawatt", _ud.UT_ASCII) class Test_unit(unittest.TestCase): @@ -87,11 +89,12 @@ class Test_unit(unittest.TestCase): Test case for unit operations. """ + def setUp(self): self.system = _ud.read_xml() - self.metre = _ud.get_unit_by_name(self.system, b'metre') - self.yard = _ud.get_unit_by_name(self.system, b'yard') - self.second = _ud.get_unit_by_name(self.system, b'second') + self.metre = _ud.get_unit_by_name(self.system, b"metre") + self.yard = _ud.get_unit_by_name(self.system, b"yard") + self.second = _ud.get_unit_by_name(self.system, b"second") def test_clone(self): metre_clone = _ud.clone(self.metre) @@ -99,7 +102,7 @@ def test_clone(self): self.assertIsNot(metre_clone, self.metre) def test_is_dimensionless_true(self): - radian = _ud.get_unit_by_name(self.system, b'radian') + radian = _ud.get_unit_by_name(self.system, b"radian") self.assertTrue(_ud.is_dimensionless(radian)) def test_is_dimensionless_false(self): @@ -131,7 +134,7 @@ def test_get_converter_invalid(self): _ud.get_converter(self.metre, self.second) ex = cm.exception - self.assertEqual(ex.status_msg(), 'UT_MEANINGLESS') + self.assertEqual(ex.status_msg(), "UT_MEANINGLESS") def test_scale(self): mm = _ud.scale(0.001, self.metre) @@ -139,7 +142,7 @@ def test_scale(self): self.assertIsNotNone(mm) def test_offset(self): - kelvin = _ud.get_unit_by_name(self.system, b'kelvin') + kelvin = _ud.get_unit_by_name(self.system, b"kelvin") celsius = _ud.offset(kelvin, 273.15) self.assertIsNotNone(celsius) @@ -179,7 +182,7 @@ def test_raise_(self): self.assertIsNotNone(sq_metre) def test_root(self): - hectare = _ud.get_unit_by_name(self.system, b'hectare') + hectare = _ud.get_unit_by_name(self.system, b"hectare") hundred_metre = _ud.root(hectare, 2) self.assertIsNotNone(hundred_metre) @@ -190,20 +193,19 @@ def test_log(self): self.assertIsNotNone(log_metre) def test_format(self): - pascal = _ud.get_unit_by_name(self.system, b'pascal') + pascal = _ud.get_unit_by_name(self.system, b"pascal") symb = _ud.format(pascal) name = _ud.format(pascal, _ud.UT_NAMES) defn = _ud.format(pascal, _ud.UT_DEFINITION) name_defn = _ud.format(pascal, _ud.UT_DEFINITION | _ud.UT_NAMES) - self.assertEqual(symb, b'Pa') - self.assertEqual(name, b'pascal') - self.assertEqual(defn, b'm-1.kg.s-2') - self.assertEqual(name_defn, b'meter^-1-kilogram-second^-2') + self.assertEqual(symb, b"Pa") + self.assertEqual(name, b"pascal") + self.assertEqual(defn, b"m-1.kg.s-2") + self.assertEqual(name_defn, b"meter^-1-kilogram-second^-2") class Test_time_encoding(unittest.TestCase): - def setUp(self): self.year, self.month, self.day = 2000, 1, 1 self.date_encoding = -31622400.0 @@ -216,29 +218,46 @@ def test_encode_date(self): self.assertEqual(self.date_encoding, res_date_encoding) def test_encode_clock(self): - res_clock_encoding = _ud.encode_clock(self.hours, self.minutes, - self.seconds) + res_clock_encoding = _ud.encode_clock( + self.hours, self.minutes, self.seconds + ) self.assertEqual(self.clock_encoding, res_clock_encoding) def test_encode_time(self): - res_time_encoding = _ud.encode_time(self.year, self.month, self.day, - self.hours, self.minutes, - self.seconds) + res_time_encoding = _ud.encode_time( + self.year, + self.month, + self.day, + self.hours, + self.minutes, + self.seconds, + ) - self.assertEqual(self.clock_encoding + self.date_encoding, - res_time_encoding) + self.assertEqual( + self.clock_encoding + self.date_encoding, res_time_encoding + ) def test_decode_time(self): - res_year, res_month, res_day, res_hours, res_minutes, res_seconds,\ - res_resolution =\ - _ud.decode_time(self.date_encoding + self.clock_encoding) + ( + res_year, + res_month, + res_day, + res_hours, + res_minutes, + res_seconds, + res_resolution, + ) = _ud.decode_time(self.date_encoding + self.clock_encoding) self.assertEqual( (res_year, res_month, res_day, res_hours, res_minutes), - (self.year, self.month, self.day, self.hours, self.minutes)) - self.assertTrue(res_seconds - res_resolution < self.seconds < - res_seconds + res_resolution) + (self.year, self.month, self.day, self.hours, self.minutes), + ) + self.assertTrue( + res_seconds - res_resolution + < self.seconds + < res_seconds + res_resolution + ) class Test_convert(unittest.TestCase): @@ -246,10 +265,11 @@ class Test_convert(unittest.TestCase): Test case for convert operations. """ + def setUp(self): system = _ud.read_xml() - metre = _ud.get_unit_by_name(system, b'metre') - yard = _ud.get_unit_by_name(system, b'yard') + metre = _ud.get_unit_by_name(system, b"metre") + yard = _ud.get_unit_by_name(system, b"yard") self.converter = _ud.get_converter(metre, yard) self.factor = 1.0936132669448853 @@ -258,7 +278,7 @@ def test_convert_float(self): np.testing.assert_approx_equal(2.5 * self.factor, res) def test_convert_floats(self): - arr = np.array([2.5, 5., 10.], dtype=np.float32) + arr = np.array([2.5, 5.0, 10.0], dtype=np.float32) res = np.empty_like(arr) _ud.convert_floats(self.converter, arr, res) np.testing.assert_array_almost_equal(arr * self.factor, res) @@ -268,11 +288,11 @@ def test_convert_double(self): np.testing.assert_approx_equal(2.5 * self.factor, res) def test_convert_doubles(self): - arr = np.array([2.5, 5., 10.], dtype=np.float64) + arr = np.array([2.5, 5.0, 10.0], dtype=np.float64) res = np.empty_like(arr) _ud.convert_doubles(self.converter, arr, res) np.testing.assert_array_almost_equal(arr * self.factor, res) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/cf_units/tests/unit/unit/test_Unit.py b/cf_units/tests/unit/unit/test_Unit.py index 49f7f77f..1ff356b9 100644 --- a/cf_units/tests/unit/unit/test_Unit.py +++ b/cf_units/tests/unit/unit/test_Unit.py @@ -14,26 +14,24 @@ class Test___init__(unittest.TestCase): - def test_capitalised_calendar(self): - calendar = 'GrEgoRian' + calendar = "GrEgoRian" expected = cf_units.CALENDAR_GREGORIAN - u = Unit('hours since 1970-01-01 00:00:00', calendar=calendar) + u = Unit("hours since 1970-01-01 00:00:00", calendar=calendar) self.assertEqual(u.calendar, expected) def test_not_basestring_calendar(self): with self.assertRaises(TypeError): - Unit('hours since 1970-01-01 00:00:00', calendar=5) + Unit("hours since 1970-01-01 00:00:00", calendar=5) def test_hash_replacement(self): - hash_unit = 'm # s-1' - expected = 'm 1 s-1' + hash_unit = "m # s-1" + expected = "m 1 s-1" u = Unit(hash_unit) self.assertEqual(u, expected) class Test_convert__calendar(unittest.TestCase): - class MyStr(str): pass @@ -44,10 +42,10 @@ def test_gregorian_calendar_conversion_setup(self): cal_str = cf_units.CALENDAR_GREGORIAN calendar = self.MyStr(cal_str) self.assertIsNot(calendar, cal_str) - u1 = Unit('hours since 1970-01-01 00:00:00', calendar=calendar) - u2 = Unit('hours since 1969-11-30 00:00:00', calendar=calendar) - u1point = np.array([8.], dtype=np.float32) - expected = np.array([776.], dtype=np.float32) + u1 = Unit("hours since 1970-01-01 00:00:00", calendar=calendar) + u2 = Unit("hours since 1969-11-30 00:00:00", calendar=calendar) + u1point = np.array([8.0], dtype=np.float32) + expected = np.array([776.0], dtype=np.float32) result = u1.convert(u1point, u2) return expected, result @@ -72,8 +70,8 @@ def test_non_gregorian_calendar_conversion_dtype(self): (np.int, False), ): data = np.arange(4, dtype=start_dtype) - u1 = Unit('hours since 2000-01-01 00:00:00', calendar='360_day') - u2 = Unit('hours since 2000-01-02 00:00:00', calendar='360_day') + u1 = Unit("hours since 2000-01-01 00:00:00", calendar="360_day") + u2 = Unit("hours since 2000-01-02 00:00:00", calendar="360_day") result = u1.convert(data, u2) if exp_convert: @@ -89,27 +87,34 @@ class Test_convert__endianness_time(unittest.TestCase): def setUp(self): self.time1_array = np.array([31.5, 32.5, 33.5]) self.time2_array = np.array([0.5, 1.5, 2.5]) - self.time1_unit = cf_units.Unit('days since 1970-01-01 00:00:00', - calendar=cf_units.CALENDAR_STANDARD) - self.time2_unit = cf_units.Unit('days since 1970-02-01 00:00:00', - calendar=cf_units.CALENDAR_STANDARD) + self.time1_unit = cf_units.Unit( + "days since 1970-01-01 00:00:00", + calendar=cf_units.CALENDAR_STANDARD, + ) + self.time2_unit = cf_units.Unit( + "days since 1970-02-01 00:00:00", + calendar=cf_units.CALENDAR_STANDARD, + ) def test_no_endian(self): - dtype = 'f8' - result = self.time1_unit.convert(self.time1_array.astype(dtype), - self.time2_unit) + dtype = "f8" + result = self.time1_unit.convert( + self.time1_array.astype(dtype), self.time2_unit + ) np.testing.assert_array_almost_equal(result, self.time2_array) def test_little_endian(self): - dtype = '