Commit 2622f3a5 authored by sim's avatar sim

Refactor Node with SQLAlchemy's polymorphism

parent e066a73d
...@@ -6,7 +6,7 @@ from datetime import datetime ...@@ -6,7 +6,7 @@ from datetime import datetime
from .users import User from .users import User
__all__ = ['Node', 'NodeNode'] __all__ = ['Node', 'NodeNode', 'CorpusNode']
class NodeType(TypeDecorator): class NodeType(TypeDecorator):
"""Define a new type of column to describe a Node's type. """Define a new type of column to describe a Node's type.
...@@ -42,6 +42,24 @@ class Node(Base): ...@@ -42,6 +42,24 @@ class Node(Base):
user = relationship(User) user = relationship(User)
parent = relationship('Node', remote_side=[id]) parent = relationship('Node', remote_side=[id])
__mapper_args__ = {
'polymorphic_on': typename
}
def __new__(cls, *args, **kwargs):
"""Automagically cast a new Node to its sub-class!
>>> Node(name='without-type')
<Node(typename=None, user_id=None, parent_id=None, name='without-type', date=None)>
>>> Node(typename='CORPUS')
<CorpusNode(typename='CORPUS', user_id=None, parent_id=None, name=None, date=None)>
"""
if cls is Node and kwargs.get('typename'):
typename = kwargs.pop('typename')
return _NODE_MODELS[typename](*args, **kwargs)
return super(Node, cls).__new__(cls)
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Node's constructor. """Node's constructor.
Initialize the `hyperdata` as a dictionary if no value was given. Initialize the `hyperdata` as a dictionary if no value was given.
...@@ -61,7 +79,7 @@ class Node(Base): ...@@ -61,7 +79,7 @@ class Node(Base):
self.hyperdata[key] = value self.hyperdata[key] = value
def __repr__(self): def __repr__(self):
return '<Node(typename={0.typename}, user_id={0.user_id}, parent_id={0.parent_id}, ' \ return '<{0.__class__.__name__}(typename={0.typename!r}, user_id={0.user_id}, parent_id={0.parent_id}, ' \
'name={0.name!r}, date={0.date})>'.format(self) 'name={0.name!r}, date={0.date})>'.format(self)
@property @property
...@@ -129,36 +147,6 @@ class Node(Base): ...@@ -129,36 +147,6 @@ class Node(Base):
**kwargs **kwargs
) )
def resources(self):
"""Return all the resources attached to a given node.
Mainly used for corpora.
example:
[{'extracted': True,
'path': '/home/me/gargantext/uploads/corpora/0c/0c5b/0c5b50/0c5b50ad8ebdeb2ae33d8e54141a52ee_Corpus_Europresse-Français-2015-12-11.zip',
'type': 1,
'url': None}]
"""
if 'resources' not in self.hyperdata:
self['resources'] = MutableList()
return self['resources']
def add_resource(self, type, path=None, url=None):
"""Attach a resource to a given node.
Mainly used for corpora.
this just adds metadata to the CORPUS node (NOT for adding documents)
example:
{'extracted': True,
'path': '/home/me/gargantext/uploads/corpora/0c/0c5b/0c5b50/0c5b50ad8ebdeb2ae33d8e54141a52ee_Corpus_Europresse-Français-2015-12-11.zip',
'type': 1,
'url': None}
"""
self.resources().append(MutableDict(
{'type': type, 'path':path, 'url':url, 'extracted': False}
))
def status(self, action=None, progress=0, complete=False, error=None): def status(self, action=None, progress=0, complete=False, error=None):
"""Get or update the status of the given action. """Get or update the status of the given action.
If no action is given, the status of the first uncomplete or last item If no action is given, the status of the first uncomplete or last item
...@@ -197,6 +185,40 @@ class Node(Base): ...@@ -197,6 +185,40 @@ class Node(Base):
return self['statuses'][-1] return self['statuses'][-1]
class CorpusNode(Node):
__mapper_args__ = {
'polymorphic_identity': 'CORPUS'
}
def resources(self):
"""Return all the resources attached to a given node.
example:
[{'extracted': True,
'path': '/home/me/gargantext/uploads/corpora/0c/0c5b/0c5b50/0c5b50ad8ebdeb2ae33d8e54141a52ee_Corpus_Europresse-Français-2015-12-11.zip',
'type': 1,
'url': None}]
"""
if 'resources' not in self.hyperdata:
self['resources'] = MutableList()
return self['resources']
def add_resource(self, type, path=None, url=None):
"""Attach a resource to a given node.
this just adds metadata to the CORPUS node (NOT for adding documents)
example:
{'extracted': True,
'path': '/home/me/gargantext/uploads/corpora/0c/0c5b/0c5b50/0c5b50ad8ebdeb2ae33d8e54141a52ee_Corpus_Europresse-Français-2015-12-11.zip',
'type': 1,
'url': None}
"""
self.resources().append(MutableDict(
{'type': type, 'path':path, 'url':url, 'extracted': False}
))
class NodeNode(Base): class NodeNode(Base):
__tablename__ = 'nodes_nodes' __tablename__ = 'nodes_nodes'
...@@ -209,3 +231,35 @@ class NodeNode(Base): ...@@ -209,3 +231,35 @@ class NodeNode(Base):
def __repr__(self): def __repr__(self):
return '<NodeNode(node1_id={0.node1_id}, node2_id={0.node2_id}, score={0.score})>'.format(self) return '<NodeNode(node1_id={0.node1_id}, node2_id={0.node2_id}, score={0.score})>'.format(self)
# --8<-- Begin hack ------
# XXX Hack to automatically defines subclasses of Node for every NODETYPES,
# in order to avoid SQLAlchemy complaints -- and subsequent exceptions.
#
# We could manually write a class for every NodeType, or find a way to
# tell SQLAlchemy that it should stick to instantiate a Node when a
# class is not defined for the wanted typename.
_ALREADY_IMPLEMENTED_NODE_TYPES = \
set(cls.__mapper_args__.get('polymorphic_identity') for cls in Node.__subclasses__())
for nodetype in NODETYPES:
if nodetype and nodetype not in _ALREADY_IMPLEMENTED_NODE_TYPES:
# Convert nodetype to a CamelCase class name, assuming it's possible...
class_name = ''.join(nodetype.title().split("-")) + 'Node'
# Create new class and add it to global scope
globals()[class_name] = type(class_name, (Node,), {
"__mapper_args__": {
"polymorphic_identity": nodetype
}
})
# ------ End of hack ------
_NODE_MODELS = {
mapper.polymorphic_identity: mapper.class_
for mapper in Node.__mapper__.self_and_descendants
if mapper.class_ is not Node
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment