diff --git a/goblin/abc.py b/goblin/abc.py index 54c43e52e7b0d6c8fda34f7c3edf090ab9f66b0e..5abb4c9c355e3c69ce96293902bd8574fd2db370 100644 --- a/goblin/abc.py +++ b/goblin/abc.py @@ -27,7 +27,7 @@ class DataType(abc.ABC): self._val = val @abc.abstractmethod - def validate(self): + def validate(self, val): """Validate property value""" raise NotImplementedError @@ -37,13 +37,18 @@ class DataType(abc.ABC): Convert property value to db compatible format. If no value passed, try to use default bound value """ - if not val: + if val is None: val = self._val return val @abc.abstractmethod def to_ogm(self, val): """Convert property value to a Python compatible format""" + try: + self.validate(val) + except exception.ValidationError: + logger.warning( + "DB val {} Fails OGM validation for {}".format(val, self)) return val def validate_cardinality(self, val, cardinality): diff --git a/goblin/properties.py b/goblin/properties.py index 75624cac90a223c1d352170a5d51ad3b21519fb4..344446d5989c22e0267885c4f5026a2c3b945665 100644 --- a/goblin/properties.py +++ b/goblin/properties.py @@ -21,6 +21,7 @@ import logging from goblin import abc, exception + logger = logging.getLogger(__name__) @@ -103,7 +104,7 @@ class String(abc.DataType): return str(val) except ValueError as e: raise exception.ValidationError( - '{} is not a valid string'.format(val)) from e + 'Not a valid string: {}'.format(val)) from e def to_db(self, val=None): return super().to_db(val=val) @@ -121,7 +122,7 @@ class Integer(abc.DataType): return int(val) except ValueError as e: raise exception.ValidationError( - '{} is not a valid integer'.format(val)) from e + 'Not a valid integer: {}'.format(val)) from e def to_db(self, val=None): return super().to_db(val=val) @@ -131,8 +132,34 @@ class Integer(abc.DataType): class Float(abc.DataType): - pass + """Simple float datatype""" + def validate(self, val): + try: + val = float(val) + except ValueError: + raise exception.ValidationError( + "Not a valid float: {}".format(val)) from e + return val + + def to_db(self, val=None): + return super().to_db(val=val) + + def to_ogm(self, val): + return super().to_ogm(val) -class Bool(abc.DataType): - pass +class Boolean(abc.DataType): + """Simple boolean datatype""" + def validate(self, val): + try: + val = bool(val) + except ValueError: + raise exception.ValidationError( + "Not a valid boolean: {val}".format(val)) from e + return val + + def to_db(self, val=None): + return super().to_db(val=val) + + def to_ogm(self, val): + return super().to_ogm(val) diff --git a/goblin/session.py b/goblin/session.py index 92be7357373c4bdce699fafa98b0fe911e433ed6..4a3c832a6c7a9f920c1368949e9aa516f4d7d8cd 100644 --- a/goblin/session.py +++ b/goblin/session.py @@ -134,18 +134,22 @@ class Session(connection.AbstractConnection): async def _receive(self, async_iter, response_queue): async for result in async_iter: - current = self.current.get(result['id'], None) - if not current: - element_type = result['type'] - label = result['label'] - if element_type == 'vertex': - current = self.app.vertices[label]() - else: - current = self.app.edges[label]() - current.source = GenericVertex() - current.target = GenericVertex() - element = current.__mapping__.mapper_func(result, current) - response_queue.put_nowait(element) + if (isinstance(result, dict) and + result.get('type', '') in ['vertex', 'edge']): + current = self.current.get(result['id'], None) + if not current: + element_type = result['type'] + label = result['label'] + if element_type == 'vertex': + current = self.app.vertices[label]() + else: + current = self.app.edges[label]() + current.source = GenericVertex() + current.target = GenericVertex() + element = current.__mapping__.mapper_func(result, current) + response_queue.put_nowait(element) + else: + response_queue.put_nowait(result) response_queue.put_nowait(None) # Creation API diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..a010443e102bc85c702ff1115c4d6b22cfafee38 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,17 @@ +aenum==1.4.5 +aiohttp==0.22.1 +alabaster==0.7.8 +Babel==2.3.4 +chardet==2.3.0 +docutils==0.12 +imagesize==0.7.1 +inflection==0.3.1 +Jinja2==2.8 +MarkupSafe==0.23 +multidict==1.2.0 +Pygments==2.1.3 +pytz==2016.6.1 +six==1.10.0 +snowballstemmer==1.2.1 +Sphinx==1.4.5 +wheel==0.24.0 diff --git a/setup.py b/setup.py index a00500665c04ad0a1c98d1ee1957f9f56c569ee8..c5d1ae01a84358fa55e50358893041c3ae183357 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( "gremlin_python.structure", "tests"], install_requires=[ "aenum==1.4.5", - "aiohttp==0.21.6", + "aiohttp==0.22.1", "inflection==0.3.1" ], test_suite="tests", diff --git a/tests/conftest.py b/tests/conftest.py index 6cc93c44e278104ea678d2750b21f67645910bf1..fc516c5dee3d6bacf05627dde126ab4bf3b38bdd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,6 +93,16 @@ def integer(): return properties.Integer() +@pytest.fixture +def flt(): + return properties.Float() + + +@pytest.fixture +def boolean(): + return properties.Boolean() + + @pytest.fixture def person(): return Person() @@ -162,3 +172,13 @@ def string_class(): @pytest.fixture def integer_class(): return properties.Integer + + +@pytest.fixture +def flt_class(): + return properties.Float + + +@pytest.fixture +def boolean_class(): + return properties.Boolean diff --git a/tests/test_properties.py b/tests/test_properties.py index 311f53c1f2f21ab20fe4ead6d7ce655bcdb69c00..bcba03b917c1eba7c5631a81cdc5e56b4c659322 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -84,3 +84,50 @@ class TestInteger: def test_initval_to_db(self, integer_class): integer = integer_class(1) assert integer.to_db() == 1 + + +class TestFloat: + + def test_validation(self, flt): + assert flt.validate(1.2) == 1.2 + with pytest.raises(Exception): + flt.validate('hello') + + def test_to_db(self, flt): + assert flt.to_db(1.2) == 1.2 + + def test_to_ogm(self, flt): + assert flt.to_db(1.2) == 1.2 + + def test_initval_to_db(self, flt_class): + flt = flt_class(1.2) + assert flt.to_db() == 1.2 + + +class TestBoolean: + + def test_validation_true(self, boolean): + assert boolean.validate(True) == True + + def test_validation_false(self, boolean): + assert boolean.validate(False) == False + + def test_to_db_true(self, boolean): + assert boolean.to_db(True) == True + + def test_to_db_false(self, boolean): + assert boolean.to_db(False) == False + + def test_to_ogm_true(self, boolean): + assert boolean.to_ogm(True) == True + + def test_to_ogm_false(self, boolean): + assert boolean.to_ogm(False) == False + + def test_initval_to_db_true(self, boolean_class): + boolean = boolean_class(True) + assert boolean.to_db() == True + + def test_initval_to_db_true(self, boolean_class): + boolean = boolean_class(False) + assert boolean.to_db() == False diff --git a/tests/test_session.py b/tests/test_session.py index 001548b2a99e263ff0e961222d7503ff77f20d60..1e89acc355f7c38d6009bb71a421eaf0e620df71 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -18,6 +18,7 @@ import pytest from goblin import element +from goblin.traversal import bindprop @pytest.mark.asyncio @@ -245,6 +246,16 @@ class TestTraversalApi: resp = await session.traversal(person_class).one_or_none() assert isinstance(resp, person_class) + @pytest.mark.asyncio + async def test_traversal_bindprop(self, session, person_class): + async with session: + itziri = person_class() + itziri.name = 'itziri' + result1 = await session.save(itziri) + bound_name = bindprop(person_class, 'name', 'itziri', binding='v1') + p1 = await session.traversal(person_class).has( + *bound_name).one_or_none() + @pytest.mark.asyncio async def test_one_or_none_none(self, session): async with session: @@ -293,3 +304,20 @@ class TestTraversalApi: assert isinstance(e1, element.GenericEdge) assert e1.how_long == 1 assert e1.__label__ == 'unregistered' + + @pytest.mark.asyncio + async def test_property_deserialization(self, session): + async with session: + p1 = await session.g.addV('person').property( + 'name', 'leif').one_or_none() + name = await session.g.V(p1.id).properties('name').one_or_none() + assert name['value'] == 'leif' + assert name['label'] == 'name' + + @pytest.mark.asyncio + async def test_non_element_deserialization(self, session): + async with session: + p1 = await session.g.addV('person').property( + 'name', 'leif').one_or_none() + one = await session.g.V(p1.id).count().one_or_none() + assert one == 1