فهرست منبع

calcex.Figure: added scalar yaml construction and representation for float and int values

Fabian Peter Hammerle 8 سال پیش
والد
کامیت
81e993f985
3فایلهای تغییر یافته به همراه111 افزوده شده و 33 حذف شده
  1. 54 10
      ioex/calcex.py
  2. 3 2
      tests/calcex/test_figure.py
  3. 54 21
      tests/calcex/test_figure_yaml.py

+ 54 - 10
ioex/calcex.py

@@ -1,6 +1,7 @@
 import copy
 try:
     import yaml
+    import yaml.nodes
 except ImportError:
     yaml = None
 
@@ -50,18 +51,43 @@ class Figure(object):
 
     @classmethod
     def from_yaml(cls, loader, node):
-        return cls(**loader.construct_mapping(node, deep=True))
+        if isinstance(node, yaml.nodes.ScalarNode):
+            seg = loader.construct_scalar(node).split(' ')
+            if seg[0] == '?':
+                value = None
+            else:
+                try:
+                    value = int(seg[0])
+                except ValueError:
+                    value = float(seg[0])
+            return cls(
+                value=value,
+                unit=' '.join(seg[1:]) if len(seg) > 1 else None,
+            )
+        else:
+            return cls(**loader.construct_mapping(node, deep=True))
 
     @classmethod
-    def register_yaml_constructor(cls, loader, tag=yaml_tag):
-        loader.add_constructor(tag, cls.from_yaml)
+    def register_yaml_constructor(cls, loader, tag=None):
+        loader.add_constructor(
+            cls.yaml_tag if tag is None else tag,
+            cls.from_yaml,
+        )
 
     @classmethod
     def to_yaml(cls, dumper, figure, tag=yaml_tag):
-        return dumper.represent_mapping(
-            tag=tag,
-            mapping={'value': figure.value, 'unit': figure.unit},
-        )
+        if figure.value is None or type(figure.value) in [int, float]:
+            value_text = u'?' if figure.value is None else u'{}'.format(figure.value)
+            if figure.unit is None:
+                figure_text = value_text
+            else:
+                figure_text = u'{} {}'.format(value_text, figure.unit)
+            return dumper.represent_scalar(tag=tag, value=figure_text)
+        else:
+            return dumper.represent_mapping(
+                tag=tag,
+                mapping={'value': figure.value, 'unit': figure.unit},
+            )
 
     @classmethod
     def register_yaml_representer(cls, dumper):
@@ -109,15 +135,33 @@ class Figure(object):
         else:
             return self * Figure(value=factor, unit=None)
 
-    def __div__(self, divisor):
+    """
+    $ python2
+    >>> 3/2
+    1
+    $ python3
+    >>> 3/2
+    1.5
+    >>> 4/2
+    2.0
+    """
+
+    def __truediv__(self, divisor):
         if isinstance(divisor, Figure):
             assert not self.value is None
             assert not divisor.value is None
+            if isinstance(self.value, int):
+                value = float(self.value) / divisor.value
+            else:
+                value = self.value / divisor.value
             if self.unit == divisor.unit:
-                return Figure(value = self.value / divisor.value, unit = None)
+                return Figure(value=value, unit=None)
             elif divisor.unit is None:
-                return type(self)(value=self.value / divisor.value, unit=self.unit)
+                return type(self)(value=value, unit=self.unit)
             else:
                 raise NotImplementedError('{!r} / {!r}'.format(self, divisor))
         else:
             return self / Figure(value=divisor, unit=None)
+
+    def __div__(self, divisor):
+        return self.__truediv__(divisor)

+ 3 - 2
tests/calcex/test_figure.py

@@ -209,13 +209,14 @@ def test_mult(a, b, expected_product):
     assert expected_product == a * b
 
 
-@pytest.mark.skipif(sys.version_info >= (3, 0), reason='__truediv__ is not defined')
 @pytest.mark.parametrize(('a', 'b', 'expected_quotient'), [
+    [Figure(1, 'm'), Figure(2), Figure(0.5, 'm')],
     [Figure(1.0, 'm'), Figure(2), Figure(0.5, 'm')],
     [Figure(1.0, 'm'), 2, Figure(0.5, 'm')],
     [Figure(2.0, 'm'), -1.5, Figure(-4. / 3, 'm')],
-    [Figure(2.0, 'm'), Figure(0.5, 'm'), Figure(4)],
+    [Figure(2.0, 'm'), Figure(0.5, 'm'), Figure(4.0)],
 ])
 def test_div(a, b, expected_quotient):
     generated_quotient = a / b
     assert expected_quotient == generated_quotient
+    assert isinstance(generated_quotient.value, type(expected_quotient.value))

+ 54 - 21
tests/calcex/test_figure_yaml.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 import pytest
 
-from ioex.calcex import Figure, UnitMismatchError
+from ioex.calcex import Figure
 yaml = pytest.importorskip('yaml')
 
 
@@ -13,38 +13,76 @@ yaml = pytest.importorskip('yaml')
     ['!fig {value: null, unit: kg}', Figure(unit='kg')],
     ['!fig {value: 123.4, unit: km/h}', Figure(123.4, 'km/h')],
     ['!fig {value: [1, 2], unit: km/h}', Figure([1, 2], 'km/h')],
+    ['!fig "?"', Figure()],
+    ['!fig 123.4', Figure(123.4)],
+    ['!fig 123.0', Figure(123.0)],
+    ['!fig "? kg"', Figure(unit='kg')],
+    ['!fig 123.4 km/h', Figure(123.4, 'km/h')],
+    ['!fig 1234', Figure(1234)],
+    ['!fig -123', Figure(-123)],
+    [u'!fig 1234 米/s²', Figure(1234, u'米/s²')],
 ])
 def test_register_yaml_constructor(figure_yaml, expected_figure, yaml_loader):
     class TestLoader(yaml_loader):
         pass
     Figure.register_yaml_constructor(TestLoader, tag='!fig')
-    assert expected_figure == yaml.load(figure_yaml, Loader=TestLoader)
+    generated_figure = yaml.load(figure_yaml, Loader=TestLoader)
+    assert expected_figure == generated_figure
+    assert isinstance(generated_figure.value, type(expected_figure.value))
+
+
+@pytest.mark.parametrize(('yaml_dumper'), [yaml.Dumper, yaml.SafeDumper])
+@pytest.mark.parametrize(('figure', 'expected_text'), [
+    [Figure(), '?'],
+    [Figure(1234), '1234'],
+    [Figure(-123), '-123'],
+    [Figure(123.4), '123.4'],
+    [Figure(-12.3), '-12.3'],
+    [Figure(None, u'm/s²'), u'? m/s²'],
+    [Figure(None, u'm/(s·s)'), u'? m/(s·s)'],
+    [Figure(1234, u'米/s²'), u'1234 米/s²'],
+    [Figure(123.4, u'm/s²'), u'123.4 m/s²'],
+])
+def test_to_yaml_scalar(figure, expected_text, yaml_dumper):
+    class TestDumper(yaml_dumper):
+        pass
+    TestDumper.add_representer(
+        type(figure),
+        lambda d, f: figure.to_yaml(d, f, '!fig')
+    )
+    generated_yaml = yaml.dump(figure, Dumper=TestDumper)
+
+    class TestLoader(yaml.SafeLoader):
+        pass
+    TestLoader.add_constructor(
+        '!fig',
+        lambda loader, node: loader.construct_scalar(node),
+    )
+    assert expected_text == yaml.load(generated_yaml, Loader=TestLoader)
 
 
 @pytest.mark.parametrize(('yaml_dumper'), [yaml.Dumper, yaml.SafeDumper])
 @pytest.mark.parametrize(('figure'), [
-    Figure(),
-    Figure(123.4),
     Figure([1, 2]),
-    Figure(None, u'm/s²'),
-    Figure(123.4, u'm/s²'),
-    Figure(1234, u'米/s²'),
+    Figure([1, 2], u'm/s²'),
+    Figure({'x': 1, 'y': -2}, u'米/s²'),
 ])
-def test_to_yaml(figure, yaml_dumper):
+def test_to_yaml_mapping(figure, yaml_dumper):
     class TestDumper(yaml_dumper):
         pass
     TestDumper.add_representer(
         type(figure),
-        lambda d, f: figure.to_yaml(d, f, '!test-figure'))
-    figure_yaml = yaml.dump(figure, Dumper=TestDumper)
+        lambda d, f: figure.to_yaml(d, f, '!fig')
+    )
+    generated_yaml = yaml.dump(figure, Dumper=TestDumper)
 
     class TestLoader(yaml.SafeLoader):
         pass
     TestLoader.add_constructor(
-        '!test-figure',
+        '!fig',
         lambda loader, node: loader.construct_mapping(node),
     )
-    figure_attr = yaml.load(figure_yaml, Loader=TestLoader)
+    figure_attr = yaml.load(generated_yaml, Loader=TestLoader)
     assert set(['value', 'unit']) == set(figure_attr.keys())
     assert figure.value == figure_attr['value']
     assert figure.unit == figure_attr['unit']
@@ -58,15 +96,10 @@ def test_register_yaml_representer(figure, yaml_dumper):
     class TestDumper(yaml_dumper):
         pass
     figure.register_yaml_representer(TestDumper)
-    figure_yaml = yaml.dump(figure, Dumper=TestDumper)
+    generated_yaml = yaml.dump(figure, Dumper=TestDumper)
 
     class TestLoader(yaml.SafeLoader):
         pass
-    TestLoader.add_constructor(
-        '!figure',
-        lambda loader, node: loader.construct_mapping(node),
-    )
-    figure_attr = yaml.load(figure_yaml, Loader=TestLoader)
-    assert set(['value', 'unit']) == set(figure_attr.keys())
-    assert figure.value == figure_attr['value']
-    assert figure.unit == figure_attr['unit']
+    figure.register_yaml_constructor(TestLoader, tag='!figure')
+    loaded_figure = yaml.load(generated_yaml, Loader=TestLoader)
+    assert figure == loaded_figure