From 16eb2c6c995b20229f43e67de2362c394359751e Mon Sep 17 00:00:00 2001 From: davebshow <davebshow@gmail.com> Date: Wed, 6 Jul 2016 21:22:24 -0400 Subject: [PATCH] WIP working on VertexProperties --- goblin/api.py | 46 ++++++++++----------------- goblin/meta.py | 36 +++++++++++++++++++++ goblin/properties.py | 68 +++++++++++++++++++++++++++++++--------- tests/test_properties.py | 38 ++++++++++++++++++++++ 4 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 goblin/meta.py create mode 100644 tests/test_properties.py diff --git a/goblin/api.py b/goblin/api.py index ce9d70b..bdddbd1 100644 --- a/goblin/api.py +++ b/goblin/api.py @@ -3,10 +3,10 @@ import collections import logging from goblin import gremlin_python +from goblin import driver from goblin import mapper -from goblin import properties +from goblin import meta from goblin import query -from goblin import driver logger = logging.getLogger(__name__) @@ -268,37 +268,12 @@ class Session: raise NotImplementedError -# Graph elements -class ElementMeta(type): - """Metaclass for graph elements. Responsible for creating the - the :py:class:`mapper.Mapping` object and replacing user defined - :py:class:`property.Property` with - :py:class:`property.PropertyDescriptor`""" - def __new__(cls, name, bases, namespace, **kwds): - if bases: - namespace['__type__'] = bases[0].__name__.lower() - props = {} - new_namespace = {} - for k, v in namespace.items(): - if isinstance(v, properties.Property): - props[k] = v - data_type = v.data_type - v = properties.PropertyDescriptor(k, data_type, - default=v._default) - new_namespace[k] = v - new_namespace['__mapping__'] = mapper.create_mapping(namespace, - props) - logger.warning("Creating new Element class: {}".format(name)) - result = type.__new__(cls, name, bases, new_namespace) - return result - - -class Vertex(metaclass=ElementMeta): +class Vertex(metaclass=meta.ElementMeta): """Base class for user defined Vertex classes""" pass -class Edge(metaclass=ElementMeta): +class Edge(metaclass=meta.ElementMeta): """Base class for user defined Edge classes""" def __init__(self, source=None, target=None): @@ -315,7 +290,7 @@ class Edge(metaclass=ElementMeta): self._source = val def delsource(self): - raise ValueError("Cant") + del self._source source = property(getsource, setsource, delsource) @@ -330,3 +305,14 @@ class Edge(metaclass=ElementMeta): del self._target target = property(gettarget, settarget, deltarget) + + +class VertexProperty(metaclass=meta.ElementMeta): + + def __init__(self, value): + self._value = value + + def __repr__(self): + return '<{}(type={}, value={})'.format(self.__class__.__name__, + self.__data_type__, + self._value) diff --git a/goblin/meta.py b/goblin/meta.py new file mode 100644 index 0000000..b698e3c --- /dev/null +++ b/goblin/meta.py @@ -0,0 +1,36 @@ +import logging + +from goblin import mapper +from goblin import properties + + +logger = logging.getLogger(__name__) + + +# Graph elements +class ElementMeta(type): + """Metaclass for graph elements. Responsible for creating the + the :py:class:`mapper.Mapping` object and replacing user defined + :py:class:`property.Property` with + :py:class:`property.PropertyDescriptor`""" + def __new__(cls, name, bases, namespace, **kwds): + if bases: + namespace['__type__'] = bases[0].__name__.lower() + props = {} + new_namespace = {} + for k, v in namespace.items(): + if isinstance(v, properties.Property): + props[k] = v + if v.vertex_property: + v = properties.VertexPropertyDescriptor( + k, v.vertex_property, default=v.default) + else: + v = properties.PropertyDescriptor( + k, v.data_type, default=v.default) + new_namespace[k] = v + new_namespace['__mapping__'] = mapper.create_mapping(namespace, + props) + logger.warning("Creating new Element class {}: {}".format( + name, new_namespace['__mapping__'])) + result = type.__new__(cls, name, bases, new_namespace) + return result diff --git a/goblin/properties.py b/goblin/properties.py index 77e7096..793e6d6 100644 --- a/goblin/properties.py +++ b/goblin/properties.py @@ -2,24 +2,11 @@ import abc import logging +from goblin import mapper logger = logging.getLogger(__name__) -class Property: - """API class used to define properties. Replaced with - :py:class:`PropertyDescriptor` by :py:class:`api.ElementMeta`.""" - def __init__(self, data_type, *, default=None): - if isinstance(data_type, type): - data_type = data_type() - self._data_type = data_type - self._default = default - - @property - def data_type(self): - return self._data_type - - class PropertyDescriptor: """Descriptor that validates user property input and gets/sets properties as instance attributes.""" @@ -44,6 +31,59 @@ class PropertyDescriptor: del attr +class VertexPropertyDescriptor(PropertyDescriptor): + """Descriptor that validates user property input and gets/sets properties + as instance attributes.""" + + def __get__(self, obj, objtype): + if obj is None: + return self._data_type + default = self._default + if default: + default = self._data_type(self._default) + return getattr(obj, self._name, default) + + def __set__(self, obj, val): + if isinstance(val, (list, tuple , set)): + vertex_property = [] + for v in val: + v = self._data_type.__data_type__.validate(v) + vertex_property.append(self._data_type(v)) + + else: + val = self._data_type.__data_type__.validate(val) + vertex_property = self._data_type(val) + setattr(obj, self._name, vertex_property) + + +class Property: + """API class used to define properties. Replaced with + :py:class:`PropertyDescriptor` by :py:class:`api.ElementMeta`.""" + + descriptor = PropertyDescriptor + + def __init__(self, data_type, *, vertex_property=None, default=None): + if isinstance(data_type, type): + data_type = data_type() + self._data_type = data_type + if vertex_property: + vertex_property.__data_type__ = data_type + self._vertex_property = vertex_property + self._default = default + + @property + def data_type(self): + return self._data_type + + @property + def vertex_property(self): + return self._vertex_property + + @property + def default(self): + return self._default + + class DataType(abc.ABC): @abc.abstractmethod diff --git a/tests/test_properties.py b/tests/test_properties.py new file mode 100644 index 0000000..4c46870 --- /dev/null +++ b/tests/test_properties.py @@ -0,0 +1,38 @@ +import asyncio +import unittest + +from goblin.api import create_engine, Vertex, Edge, VertexProperty +from goblin.properties import Property, String + + +class TestVertexProperty(VertexProperty): + notes = Property(String) + + +class TestVertex(Vertex): + __label__ = 'test_vertex' + name = Property(String, vertex_property=TestVertexProperty) + address = Property(String) + + +class TestProperties(unittest.TestCase): + + def setUp(self): + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(None) + + def tearDown(self): + self.loop.close() + + def test_vertex_property(self): + + t = TestVertex() + self.assertIsNone(t.name) + t.name = 'leif' + self.assertEqual(t.name._value, 'leif') + self.assertIsNone(t.name.notes) + t.name.notes = 'notes' + self.assertEqual(t.name.notes, 'notes') + t.name = ['leif', 'jon'] + self.assertEqual(t.name[0]._value, 'leif') + self.assertEqual(t.name[1]._value, 'jon') -- GitLab