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