Skip to content

Commit

Permalink
Merge pull request #22 from ocefpaf/py3k
Browse files Browse the repository at this point in the history
Improving python 3 compatibility
  • Loading branch information
pelson committed Sep 7, 2015
2 parents c2ff677 + 4ddbed5 commit ac6e8a4
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 109 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ language: python

python:
- 2.7
#- 3.4
- 3.4

install:
# Install miniconda
Expand Down
110 changes: 77 additions & 33 deletions cf_units/cf_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"""

from __future__ import (absolute_import, division, print_function)
from six.moves import range, zip
import six

from contextlib import contextmanager
import copy
Expand All @@ -39,7 +41,7 @@
import numpy as np

from . import config
from . import util
from .util import approx_equal


__all__ = ['CALENDAR_STANDARD',
Expand Down Expand Up @@ -329,13 +331,13 @@
_ut_scale.argtypes = [ctypes.c_double, ctypes.c_void_p]
_ut_scale.restype = ctypes.c_void_p

# convenience dictionary for the Unit convert method
# Convenience dictionary for the Unit convert method.
_cv_convert_scalar = {FLOAT32: _cv_convert_float,
FLOAT64: _cv_convert_double}
_cv_convert_array = {FLOAT32: _cv_convert_floats,
FLOAT64: _cv_convert_doubles}
_numpy2ctypes = {np.float32: FLOAT32, np.float64: FLOAT64}
_ctypes2numpy = {v: k for k, v in _numpy2ctypes.iteritems()}
_ctypes2numpy = {v: k for k, v in _numpy2ctypes.items()}


@contextmanager
Expand Down Expand Up @@ -369,7 +371,7 @@ def suppress_errors():
if _ud_system is None:
_alt_xml_path = os.path.join(sys.prefix, 'share',
'udunits', 'udunits2.xml')
_ud_system = _ut_read_xml(_alt_xml_path)
_ud_system = _ut_read_xml(_alt_xml_path.encode())
if not _ud_system:
_status_msg = 'UNKNOWN'
_error_msg = ''
Expand Down Expand Up @@ -748,7 +750,7 @@ def num2date(time_value, unit, calendar):
########################################################################

def _Unit(category, ut_unit, calendar=None, origin=None):
unit = util._OrderedHashable.__new__(Unit)
unit = _OrderedHashable.__new__(Unit)
unit._init(category, ut_unit, calendar, origin)
return unit

Expand All @@ -769,7 +771,7 @@ def as_unit(unit):
result = unit
else:
result = None
use_cache = isinstance(unit, basestring) or unit is None
use_cache = isinstance(unit, six.string_types) or unit is None
if use_cache:
result = _CACHE.get(unit)
if result is None:
Expand Down Expand Up @@ -825,7 +827,10 @@ def is_vertical(unit):
return as_unit(unit).is_vertical()


class Unit(util._OrderedHashable):
from .util import _OrderedHashable


class Unit(_OrderedHashable):
"""
A class to represent S.I. units and support common operations to
manipulate such units in a consistent manner as per UDUNITS-2.
Expand All @@ -840,7 +845,46 @@ class Unit(util._OrderedHashable):
This class also supports time and calendar defintion and manipulation.
"""
# Declare the attribute names relevant to the _OrderedHashable behaviour.
def _init_from_tuple(self, values):
for name, value in zip(self._names, values):
object.__setattr__(self, name, value)

def _as_tuple(self):
return tuple(getattr(self, name) for name in self._names)

# Provide hash semantics

def _identity(self):
return self._as_tuple()

def __hash__(self):
return hash(self._identity())

def __eq__(self, other):
return (isinstance(other, type(self)) and
self._identity() == other._identity())

def __ne__(self, other):
# Since we've defined __eq__ we should also define __ne__.
return not self == other

# Provide default ordering semantics

def __lt__(self, other):
return self._identity() < other._identity()

# Prevent attribute updates

def __setattr__(self, name, value):
raise AttributeError('Instances of %s are immutable' %
type(self).__name__)

def __delattr__(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')

category = None
Expand Down Expand Up @@ -934,14 +978,14 @@ def __init__(self, unit, calendar=None):
unit = _NO_UNIT_STRING
else:
category = _CATEGORY_UDUNIT
ut_unit = _ut_parse(_ud_system, unit, UT_ASCII)
ut_unit = _ut_parse(_ud_system, unit.encode('ascii'), UT_ASCII)
# _ut_parse returns 0 on failure
if ut_unit is None:
self._raise_error('Failed to parse unit "%s"' % unit)
if _OP_SINCE in unit.lower():
if calendar is None:
calendar_ = CALENDAR_GREGORIAN
elif isinstance(calendar, basestring):
elif isinstance(calendar, six.string_types):
if calendar.lower() in CALENDARS:
calendar_ = calendar.lower()
else:
Expand All @@ -951,7 +995,7 @@ def __init__(self, unit, calendar=None):
msg = 'Expected string-like calendar argument, got {!r}.'
raise TypeError(msg.format(type(calendar)))

self._init(category, ut_unit, calendar_, unit)
self._init_from_tuple((category, ut_unit, calendar_, unit,))

def _raise_error(self, msg):
"""
Expand Down Expand Up @@ -1028,7 +1072,7 @@ def is_time(self):
if self.is_unknown() or self.is_no_unit():
result = False
else:
day = _ut_get_unit_by_name(_ud_system, 'day')
day = _ut_get_unit_by_name(_ud_system, b'day')
result = _ut_are_convertible(self.ut_unit, day) != 0
return result

Expand All @@ -1054,10 +1098,10 @@ def is_vertical(self):
if self.is_unknown() or self.is_no_unit():
result = False
else:
bar = _ut_get_unit_by_name(_ud_system, 'bar')
bar = _ut_get_unit_by_name(_ud_system, b'bar')
result = _ut_are_convertible(self.ut_unit, bar) != 0
if not result:
meter = _ut_get_unit_by_name(_ud_system, 'meter')
meter = _ut_get_unit_by_name(_ud_system, b'meter')
result = _ut_are_convertible(self.ut_unit, meter) != 0
return result

Expand Down Expand Up @@ -1286,7 +1330,7 @@ def format(self, option=None):
ctypes.sizeof(string_buffer), bitmask)
if depth < 0:
self._raise_error('Failed to format %r' % self)
return string_buffer.value
return str(string_buffer.value.decode('ascii'))

@property
def name(self):
Expand Down Expand Up @@ -1386,7 +1430,7 @@ def offset_by_time(self, origin):
"""

if not isinstance(origin, (int, float, long)):
if not isinstance(origin, (float, six.integer_types)):
raise TypeError('a numeric type for the origin argument is'
' required')
ut_unit = _ut_offset_by_time(self.ut_unit, ctypes.c_double(origin))
Expand Down Expand Up @@ -1429,7 +1473,7 @@ def root(self, root):
Args:
* root (int/long): Value by which the unit root is taken.
* root (int): Value by which the unit root is taken.
Returns:
None.
Expand All @@ -1449,7 +1493,7 @@ def root(self, root):
try:
root = ctypes.c_int(root)
except TypeError:
raise TypeError('An int or long type for the root argument'
raise TypeError('An int type for the root argument'
' is required')

if self.is_unknown():
Expand All @@ -1475,7 +1519,7 @@ def log(self, base):
Args:
* base (int/float/long): Value of the logorithmic base.
* base (int/float): Value of the logorithmic base.
Returns:
None.
Expand Down Expand Up @@ -1614,7 +1658,7 @@ def __mul__(self, other):
Args:
* other (int/float/long/string/Unit): Multiplication scale
* other (int/float/string/Unit): Multiplication scale
factor or unit.
Returns:
Expand All @@ -1641,7 +1685,7 @@ def __div__(self, other):
Args:
* other (int/float/long/string/Unit): Division scale factor or unit.
* other (int/float/string/Unit): Division scale factor or unit.
Returns:
Unit.
Expand All @@ -1667,7 +1711,7 @@ def __truediv__(self, other):
Args:
* other (int/float/long/string/Unit): Division scale factor or unit.
* other (int/float/string/Unit): Division scale factor or unit.
Returns:
Unit.
Expand All @@ -1694,7 +1738,7 @@ def __pow__(self, power):
Args:
* power (int/float/long): Value by which the unit power is raised.
* power (int/float): Value by which the unit power is raised.
Returns:
Unit.
Expand Down Expand Up @@ -1725,15 +1769,15 @@ def __pow__(self, power):
# But if the power is of the form 1/N, where N is an integer
# (within a certain acceptable accuracy) then we can find the Nth
# root.
if not util.approx_equal(power, 0.0) and abs(power) < 1:
if not util.approx_equal(1 / power, round(1 / power)):
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.')
root = int(round(1 / power))
result = self.root(root)
else:
# Failing that, check for powers which are (very nearly) simple
# integer values.
if not util.approx_equal(power, round(power)):
if not approx_equal(power, round(power)):
msg = 'Cannot raise a unit by a decimal (got %s).' % power
raise ValueError(msg)
power = int(round(power))
Expand All @@ -1746,7 +1790,7 @@ def __pow__(self, power):

def _identity(self):
# Redefine the comparison/hash/ordering identity as used by
# util._OrderedHashable.
# _OrderedHashable.
return (self.name, self.calendar)

def __eq__(self, other):
Expand Down Expand Up @@ -1814,7 +1858,7 @@ def convert(self, value, other, ctype=FLOAT64):
Args:
* value (int/float/long/numpy.ndarray):
* value (int/float/numpy.ndarray):
Value/s to be converted.
* other (string/Unit):
Target unit to convert to.
Expand All @@ -1832,8 +1876,8 @@ def convert(self, value, other, ctype=FLOAT64):
>>> import numpy as np
>>> c = cf_units.Unit('deg_c')
>>> f = cf_units.Unit('deg_f')
>>> print(c.convert(0, f))
32.0
>>> c.convert(0, f)
31.999999999999886
>>> c.convert(0, f, cf_units.FLOAT32)
32.0
>>> a64 = np.arange(10, dtype=np.float64)
Expand Down Expand Up @@ -1881,8 +1925,8 @@ def convert(self, value, other, ctype=FLOAT64):
if issubclass(value_copy.dtype.type, np.integer):
value_copy = value_copy.astype(
_ctypes2numpy[ctype])
# strict type check of numpy array
if value_copy.dtype.type not in _numpy2ctypes.keys():
# Strict type check of numpy array.
if value_copy.dtype.type not in _numpy2ctypes:
raise TypeError(
"Expect a numpy array of '%s' or '%s'" %
tuple(sorted(_numpy2ctypes.keys())))
Expand All @@ -1895,7 +1939,7 @@ def convert(self, value, other, ctype=FLOAT64):
value_copy.size, pointer)
result = value_copy
else:
if ctype not in _cv_convert_scalar.keys():
if ctype not in _cv_convert_scalar:
raise ValueError('Invalid target type. Can only '
'convert to float or double.')
# Utilise global convenience dictionary
Expand Down
10 changes: 7 additions & 3 deletions cf_units/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@

from __future__ import (absolute_import, division, print_function)

import ConfigParser
try:
import ConfigParser as configparser
except ImportError:
import configparser

import os.path

# Load the optional "site.cfg" file if it exists.
config = ConfigParser.SafeConfigParser()
config = configparser.SafeConfigParser()


# Returns simple string options.
Expand All @@ -44,5 +48,5 @@ def get_option(section, option, default=None):
CONFIG_PATH = os.path.join(ROOT_PATH, 'etc')

# Load the optional "site.cfg" file if it exists.
config = ConfigParser.SafeConfigParser()
config = configparser.SafeConfigParser()
config.read([os.path.join(CONFIG_PATH, 'site.cfg')])
Loading

0 comments on commit ac6e8a4

Please sign in to comment.