Skip to content

Commit

Permalink
WC-1298: Merge branch 'release/1.2.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
sjlongland committed Feb 13, 2018
2 parents 2dd7bce + f3cdcaf commit 0585171
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 93 deletions.
7 changes: 7 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
hszinc (1.2.1) unstable; urgency=low
* Add auto-upgrade of grid version; grids now default to v2.0 and
move to v3.0 if a v3.0 type is encountered.
* Add grid version type enforcement; if a version is specified for
a grid, raise an error if an unsupported type is used.
-- Samuel Toh <[email protected]> Tue, 13 Feb 2018 17:05:40 +1000

hszinc (1.2.0) unstable; urgency=low
* Re-worked on hszinc's parser to support Haystack v3 data structures.
* Added support for Haystack list.
Expand Down
2 changes: 1 addition & 1 deletion hszinc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@
__copyright__ = 'Copyright 2016, VRT Systems'
__credits__ = ['VRT Systems']
__license__ = 'BSD'
__version__ = '1.2.0'
__version__ = '1.2.1'
__maintainer__ = 'VRT Systems'
__email__ = '[email protected]'
67 changes: 51 additions & 16 deletions hszinc/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .metadata import MetadataObject
from .sortabledict import SortableDict
from collections import MutableSequence
from .version import Version, LATEST_VER
from .version import Version, VER_3_0, VER_2_0

class Grid(MutableSequence):
'''
Expand All @@ -17,18 +17,21 @@ class Grid(MutableSequence):
followed by zero or more rows.
'''

# Grid version number
DEFAULT_VERSION = LATEST_VER

def __init__(self, version=DEFAULT_VERSION, metadata=None, columns=None):
def __init__(self, version=None, metadata=None, columns=None):
'''
Create a new Grid.
'''
# Version
self._version = Version(version)
version_given = version is not None
if version_given:
version = Version(version)
else:
version = VER_2_0
self._version = version
self._version_given = version_given

# Metadata
self.metadata = MetadataObject()
self.metadata = MetadataObject(validate_fn=self._detect_or_validate)

# The columns
self.column = SortableDict()
Expand All @@ -44,22 +47,25 @@ def __init__(self, version=DEFAULT_VERSION, metadata=None, columns=None):
columns = list(columns.items())

for col_id, col_meta in columns:
if not isinstance(col_meta, MetadataObject):
# Convert sorted lists and dicts back to a list of items.
if isinstance(col_meta, dict) or \
isinstance(col_meta, SortableDict):
col_meta = list(col_meta.items())
# Convert sorted lists and dicts back to a list of items.
if isinstance(col_meta, dict) or \
isinstance(col_meta, SortableDict):
col_meta = list(col_meta.items())

mo = MetadataObject()
mo.extend(col_meta)
col_meta = mo
self.column.add_item(col_id, col_meta)
mo = MetadataObject(validate_fn=self._detect_or_validate)
mo.extend(col_meta)
self.column.add_item(col_id, mo)

@property
def version(self): # pragma: no cover
# Trivial function
return self._version

@property
def nearest_version(self): # pragma: no cover
# Trivial function
return Version.nearest(self._version)

@property
def ver_str(self): # pragma: no cover
# Trivial function
Expand Down Expand Up @@ -123,6 +129,8 @@ def __setitem__(self, index, value):
'''
if not isinstance(value, dict):
raise TypeError('value must be a dict')
for val in value.values():
self._detect_or_validate(val)
self._row[index] = value

def __delitem__(self, index):
Expand All @@ -137,4 +145,31 @@ def insert(self, index, value):
'''
if not isinstance(value, dict):
raise TypeError('value must be a dict')
for val in value.values():
self._detect_or_validate(val)
self._row.insert(index, value)

def _detect_or_validate(self, val):
'''
Detect the version used from the row content, or validate against
the version if given.
'''
if isinstance(val, list) \
or isinstance(val, dict) \
or isinstance(val, SortableDict) \
or isinstance(val, Grid):
# Project Haystack 3.0 type.
self._assert_version(VER_3_0)

def _assert_version(self, version):
'''
Assert that the grid version is equal to or above the given value.
If no version is set, set the version.
'''
if self.nearest_version < version:
if self._version_given:
raise ValueError(
'Data type requires version %s' \
% version)
else:
self._version = version
5 changes: 4 additions & 1 deletion hszinc/sortabledict.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ class SortableDict(MutableMapping):
A dict-like object that permits value ordering/re-ordering.
"""

def __init__(self, initial=None):
def __init__(self, initial=None, validate_fn=None):
self._values = {}
self._order = []
self._validate_fn = validate_fn
super(SortableDict, self).__init__()

# Copy initial values into dict.
Expand Down Expand Up @@ -65,6 +66,8 @@ def add_item(self, key, value, after=False, index=None, pos_key=None,
When replacing, the position will be left un-changed unless a location
is specified explicitly.
"""
if self._validate_fn:
self._validate_fn(value)

if (index is not None) and (pos_key is not None):
raise ValueError('Either specify index or pos_key, not both.')
Expand Down
51 changes: 51 additions & 0 deletions hszinc/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
"""

import re
import warnings

VERSION_RE = re.compile(r'^(\d[\d\.]*)([^\d].*)*$')

class Version(object):
"""
A Project Haystack version number
"""

def __init__(self, ver_str):
if isinstance(ver_str, Version):
# Clone constructor
Expand Down Expand Up @@ -121,8 +123,57 @@ def __ge__(self, other):
def __gt__(self, other):
return self._cmp(other) > 0

@classmethod
def nearest(self, ver):
"""
Retrieve the official version nearest the one given.
"""
if not isinstance(ver, Version):
ver = Version(ver)

if ver in OFFICIAL_VERSIONS:
return ver

# We might not have an exact match for that.
# See if we have one that's newer than the grid we're looking at.
versions = list(OFFICIAL_VERSIONS)
versions.sort(reverse=True)
best = None
for candidate in versions:
# Due to ambiguities, we might have an exact match and not know it.
# '2.0' will not hash to the same value as '2.0.0', but both are
# equivalent.
if candidate == ver:
# We can't beat this, make a note of the match for later
return candidate

# If we have not seen a better candidate, and this is older
# then we may have to settle for that.
if (best is None) and (candidate < ver):
warnings.warn('This version of hszinc does not yet '\
'support version %s, please seek a newer version '\
'or file a bug. Closest (older) version supported is %s.'\
% (ver, candidate))
return candidate

# Probably the best so far, but see if we can go closer
if candidate > ver:
best = candidate

# Unhappy path, no best option? This should not happen.
assert best is not None
warnings.warn('This version of hszinc does not yet '\
'support version %s, please seek a newer version '\
'or file a bug. Closest (newer) version supported is %s.'\
% (ver, best))
return best


# Standard Project Haystack versions
VER_2_0 = Version('2.0')
VER_3_0 = Version('3.0')
LATEST_VER = VER_3_0

OFFICIAL_VERSIONS = set([
VER_2_0, VER_3_0
])
42 changes: 4 additions & 38 deletions hszinc/zincparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,44 +71,10 @@ def __getitem__(self, ver):
except KeyError:
pass

def use(v):
g = self._known_grammars[v]
self._known_grammars[ver] = g
return g

# We might not have an exact match for that.
# See if we have one that's newer than the grid we're looking at.
versions = list(self._known_grammars.keys())
versions.sort(reverse=True)
best = None
for candidate in versions:
# Due to ambiguities, we might have an exact match and not know it.
# '2.0' will not hash to the same value as '2.0.0', but both are
# equivalent.
if candidate == ver:
# We can't beat this, make a note of the match for later
return use(candidate)

# If we have not seen a better candidate, and this is older
# then we may have to settle for that.
if (best is None) and (candidate < ver):
warnings.warn('This version of hszinc does not yet '\
'support version %s, please seek a newer version '\
'or file a bug. Closest (older) version supported is %s.'\
% (ver, candidate))
return use(candidate)

# Probably the best so far, but see if we can go closer
if candidate > ver:
best = candidate

# Unhappy path, no best option? This should not happen.
assert best is not None
warnings.warn('This version of hszinc does not yet '\
'support version %s, please seek a newer version '\
'or file a bug. Closest (newer) version supported is %s.'\
% (ver, best))
return use(best)
nearest = Version.nearest(ver)
g = self._known_grammars[nearest]
self._known_grammars[ver] = g
return g


class GenerateMatch(object):
Expand Down
1 change: 1 addition & 0 deletions tests/test_acid.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def gen_random_meta():
return meta

def dump_grid(g):
print ('Version: %s' % g.version)
print ('Metadata:')
for k, v in g.metadata.items():
print (' %s = %r' % (k, v))
Expand Down
36 changes: 36 additions & 0 deletions tests/test_datatype.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,21 @@ def _check_qty_op(fn, a, b):
lambda a, b : a > b):
yield _check_qty_op, fn, a, b

# Exponentiation, we can't use all the values above
# as some go out of range.
small_floats = tuple(filter(lambda f : abs(f) < 10, floats))
for a in small_floats:
for b in small_floats:
if a == b:
continue

# Python2 won't allow raising negative numbers
# to a fractional power
if a < 0:
continue

yield _check_qty_op, lambda a, b: a ** b, a, b

# Try some integer values
ints = (1, 2, -4, 141, -399, 0x10, 0xff, 0x55)
for a in ints:
Expand Down Expand Up @@ -203,6 +218,16 @@ def _check_qty_op(fn, a, b):
lambda a, b : a >> b):
yield _check_qty_op, fn, a, b

# Exponentiation, we can't use all the values above
# as some go out of range.
small_ints = tuple(filter(lambda f : abs(f) < 10, ints))
for a in small_ints:
for b in small_ints:
if a == b:
continue

yield _check_qty_op, lambda a, b: a ** b, a, b

def test_qty_cmp():
if 'cmp' not in set(locals().keys()):
def cmp(a, b):
Expand All @@ -213,10 +238,21 @@ def cmp(a, b):

a = hszinc.Quantity(-3)
b = hszinc.Quantity(432)
c = hszinc.Quantity(4, unit='A')
d = hszinc.Quantity(10, unit='A')
e = hszinc.Quantity(12, unit='V')

assert cmp(a, b) < 0
assert cmp(b, a) > 0
assert cmp(a, hszinc.Quantity(-3)) == 0
assert cmp(c, d) < 0
assert cmp(d, c) > 0
assert cmp(c, hszinc.Quantity(4, unit='A')) == 0

try:
cmp(c, e)
except TypeError as ex:
assert str(ex) == 'Quantity units differ: A vs V'


class MyCoordinate(object):
Expand Down
Loading

0 comments on commit 0585171

Please sign in to comment.