Repository: capsulecorplab/mindgraph

Issue:
Name changes for Graph, Node, and Node attributes
To implement the following changes to the current naming convention:
`Graph` to `Project`
`Node` to `Task`
`Node.threads` to `Task.subtasks`
`Node.dependencies` to `Task.blockers`
`Node.weight` to `Task.priority`

Comments:

Patch:
diff --git a/README.md b/README.md
index c80ec3f..644f1cd 100644
--- a/README.md
+++ b/README.md
@@ -20,14 +20,14 @@ $ pip install git+https://github.com/capsulecorplab/mindgraph.git
 ```
 >>> import mindgraph as mg
 
->>> graph = mg.Graph('learn all the things')
->>> thing1 = graph.append('1st thing')
->>> thing2 = graph.append('2nd thing')
->>> thing3 = graph.append('3rd thing')
+>>> project = mg.Project('learn all the things')
+>>> thing1 = project.append('1st thing')
+>>> thing2 = project.append('2nd thing')
+>>> thing3 = project.append('3rd thing')
 
->>> graph.remove(2)
+>>> project.remove(2)
 
->>> thing1 = graph[0]
+>>> thing1 = project[0]
 >>> thing1_1 = thing1.append('thing within a thing')
 >>> thing1_2 = thing1.append('thing blocking a thing')
 >>> thing1_1.blockedby(thing1_2)
@@ -36,7 +36,7 @@ $ pip install git+https://github.com/capsulecorplab/mindgraph.git
 >>> thing2_2 = thing2.append('another thing blocking a thing')
 >>> thing2_2.blocking(thing2_1)
 
->>> print(graph)
+>>> print(project)
 learn all the things:
 - 1st thing:
   - thing within a thing
@@ -46,11 +46,11 @@ learn all the things:
   - another thing blocking a thing
 ```
 
-`Graph` objects can be exported to, and imported from, a yaml file for storage:
+Projects can be exported to, or imported from, a yaml file for external storage:
 
 ```
->>> graph.to_yaml('mygraph.yaml')
->>> graph2 = mg.read_yaml('mygraph.yaml')
+>>> project.to_yaml('myproject.yaml')
+>>> revivedproject = mg.read_yaml('myproject.yaml')
 ```
 
 ## Contribute
diff --git a/mindgraph/graph.py b/mindgraph/graph.py
index d7b1769..be55272 100644
--- a/mindgraph/graph.py
+++ b/mindgraph/graph.py
@@ -4,109 +4,106 @@ from typing import (Any, Callable, Generator, Iterator, List, Optional, Set,
 from yaml import dump, load
 
 
-class Node(object):
-    """node class"""
+class Task(object):
+    """Task class"""
 
-    def __init__(self, name: str = '', weight: int = 1) -> None:
-        self._dependencies = list()  # type: List[Node]
-        self._threads = list()  # type: List[Node]
+    def __init__(self, name: str = '', priority: int = 1) -> None:
+        self._blockers = list()  # type: List[Task]
+        self._subtasks = list()  # type: List[Task]
         self._name = ''  # type: str
-        self._weight = weight  # type: int
+        self._priority = priority  # type: int
         if type(name) is str:
             self._name = name
         else:
             raise TypeError
 
-    def append(self, newnode) -> "Node":
-        """ Creates a new Node and appends it to threads """
-        if type(newnode) is str:
-            newnode = Node(newnode)
-        elif type(newnode) is not Node:
+    def append(self, newtask) -> "Task":
+        """ Creates a new Task and appends it to subtasks """
+        if type(newtask) is str:
+            newtask = Task(newtask)
+        elif type(newtask) is not Task:
             raise TypeError
-        self._threads.append(newnode)
-        return newnode
+        self._subtasks.append(newtask)
+        return newtask
 
-    def pop(self, index: int) -> "Node":
-        """ Pops the Node from threads[index] """
-        return self._threads.pop(index)
+    def pop(self, index: int) -> "Task":
+        """ Pops the Task from subtasks[index] """
+        return self._subtasks.pop(index)
 
-    def blockedby(self, node: "Node") -> None:
-        """ Adds a Node to the dependenceis list """
-        if type(node) is Node:
-            self._dependencies.append(node)
+    def blockedby(self, task: "Task") -> None:
+        """ Adds a task to the subtasks list """
+        if type(task) is Task:
+            self._blockers.append(task)
             return None
         else:
             raise TypeError
 
-    def blocking(self, node: "Node") -> None:
-        """ Adds this Node to another node's dependencies list """
-        if type(node) is Node:
-            node._dependencies.append(self)
+    def blocking(self, task: "Task") -> None:
+        """ Adds this task to another task's blockers list """
+        if type(task) is Task:
+            task._blockers.append(self)
             return None
         else:
             raise TypeError
 
-    def __getitem__(self, key: int) -> "Node":
-        return self._threads[key]
+    def __getitem__(self, key: int) -> "Task":
+        return self._subtasks[key]
 
     def __repr__(self) -> str:
-        return '\n'.join(self.format_tree())
+        return '\n'.join(self._format_tree())
 
-    def format_tree(self: "Node", depth: int = 0) -> Iterator[str]:
-        """Format node and dependents in tree format, emitting lines
-
-        Assumes no cycles in graph
-        """
+    def _format_tree(self: "Task", depth: int = 0) -> Iterator[str]:
+        """Generates task and subtasks into a string formatted tree"""
         indent = '    ' * depth
         bullet = '- ' if depth != 0 else ''
-        suffix = ':' if self.threads else ''
+        suffix = ':' if self.subtasks else ''
         line = '{indent}{bullet}{self.name}{suffix}'.format(**locals())
 
         yield line
-        for n in self.threads:
-            yield from n.format_tree(depth+1)
+        for n in self.subtasks:
+            yield from n._format_tree(depth+1)
 
     def _postorder(self,
                    depth: int = 0,
-                   visited: Set["Node"] = None,
-                   node_key: Callable[["Node"], Any]=None,
-                   ) -> Generator[Tuple[int, "Node"], None, Set["Node"]]:
-        """Post-order traversal of graph rooted at node"""
+                   visited: Set["Task"] = None,
+                   taskkey: Callable[["Task"], Any]=None,
+                   ) -> Generator[Tuple[int, "Task"], None, Set["Task"]]:
+        """Post-order traversal of Project rooted at Task"""
         if visited is None:
             visited = set()
 
-        children = self._threads
-        if node_key is not None:
-            children = sorted(self._threads, key=node_key)
+        children = self._subtasks
+        if taskkey is not None:
+            children = sorted(self._subtasks, key=taskkey)
 
         for child in children:
             if child not in visited:
                 visited = yield from child._postorder(depth+1,
                                                       visited,
-                                                      node_key)
+                                                      taskkey)
 
         yield (depth, self)
         visited.add(self)
 
         return visited
 
-    def todo(self) -> Iterator["Node"]:
-        """Generate nodes in todo order
+    def todo(self) -> Iterator["Task"]:
+        """Generate Tasks in todo order
 
-        Nodes are scheduled by weight and to resolve blocking tasks
+        Tasks are scheduled by priority and to resolve blocking tasks
         """
-        # sorts by weight (2 before 1), then alphabetical
-        def node_key(node):
-            return (-node.weight, node.name)
-        return (x[1] for x in self._postorder(node_key=node_key))
+        # sorts by priority (2 before 1), then alphabetical
+        def taskkey(Task):
+            return (-Task.priority, Task.name)
+        return (x[1] for x in self._postorder(taskkey=taskkey))
 
     def __str__(self) -> str:
         return dump(load(str(self.__repr__())), default_flow_style=False)
 
     @property
-    def dependencies(self) -> List["Node"]:
-        """ dependencies getter """
-        return self._dependencies
+    def blockers(self) -> List["Task"]:
+        """ blockers getter """
+        return self._blockers
 
     @property
     def name(self) -> str:
@@ -119,38 +116,37 @@ class Node(object):
         self._name = name
 
     @property
-    def threads(self) -> List["Node"]:
-        """ threads getter """
-        return self._threads
+    def subtasks(self) -> List["Task"]:
+        """ subtasks getter """
+        return self._subtasks
 
     @property
-    def weight(self) -> int:
-        """ weight getter """
-        return self._weight
-
-    @weight.setter
-    def weight(self, value: int) -> None:
-        """ weight setter """
-        self._weight = value
-
+    def priority(self) -> int:
+        """ priority getter """
+        return self._priority
 
-class Graph(Node):
-    """A Graph model of the mind"""
-
-    def __init__(self, name=None) -> None:
-        Node.__init__(self, name)
+    @priority.setter
+    def priority(self, value: int) -> None:
+        """ priority setter """
+        self._priority = value
 
     def to_yaml(self, filename=None) -> None:
-        """ Write this Graph to a yaml file """
+        """ Write this Project to a yaml file """
         with open(filename, 'w') as f:
             f.write(dump(self))
 
 
-def read_yaml(filename: str = "") -> Graph:
-    """ Load a Graph from a yaml file """
+class Project(object):
+    """Returns a task representing the root of your project"""
+    def __new__(cls, name: str=None) -> Task:
+        return Task(name)
+
+
+def read_yaml(filename: str = "") -> Task:
+    """ Load a project from a yaml file """
     with open(filename, 'r') as f:
         rv = load(f.read())
-        if type(rv) is Graph:
+        if type(rv) is Task:
             return rv
         else:
             raise TypeError(type(rv))


Test patch:
diff --git a/test/test_mindgraph.py b/test/test_mindgraph.py
index 1fc5c76..3a375fa 100644
--- a/test/test_mindgraph.py
+++ b/test/test_mindgraph.py
@@ -11,25 +11,25 @@ from mindgraph import *
 
 
 @pytest.fixture(scope="module")
-def graph():
-    graph = Graph('learn all the things')
-    return graph
+def project():
+    project = Project('learn all the things')
+    return project
 
 
 @pytest.fixture
-def task_graph():
-    # setup example graph from issue #14
-    g = Graph('build a thing')
+def task_project():
+    # setup example project from issue #14
+    g = Project('build a thing')
 
     t1 = g.append('task 1')
-    t1.weight = 3
+    t1.priority = 3
     t11 = t1.append('task 1.1')
     t12 = t1.append('task 1.2')
     t13 = t1.append('task 1.3')
-    t13.weight = 3
+    t13.priority = 3
 
     t2 = g.append('task 2')
-    t2.weight = 2
+    t2.priority = 2
     t21 = t2.append('task 2.1')
     t22 = t2.append('task 2.2')
     t221 = t22.append('task 2.2.1')
@@ -39,111 +39,123 @@ def task_graph():
     t31 = t3.append('task 3.1')
     t32 = t3.append('task 3.2')
 
-    t32.threads.append(t22)
-    t12.threads.append(t22)
+    t32.subtasks.append(t22)
+    t12.subtasks.append(t22)
     return g
 
 
-def test_todo_high_weights_win(task_graph):
-    """High weights are scheduled before low weights"""
-    todo = [n.name for n in task_graph.todo()]
+def test_todo_high_priorities_win(task_project):
+    """High priorities are scheduled before low priorities"""
+    todo = [n.name for n in task_project.todo()]
     assert todo.index('task 1') < todo.index('task 2')
     assert todo.index('task 1') < todo.index('task 3')
     assert todo.index('task 1.3') < todo.index('task 1.1')
 
 
-def test_todo_blocking_tasks_win(task_graph):
+def test_todo_blocking_tasks_win(task_project):
     """Blocking tasks are scheduled before blocked tasks"""
-    todo = [n.name for n in task_graph.todo()]
+    todo = [n.name for n in task_project.todo()]
     assert todo.index('task 2.2') < todo.index('task 3.2')
     assert todo.index('task 2.2') < todo.index('task 1.2')
     assert todo.index('task 1.1') < todo.index('task 1.2')
 
 
-def test_postorder_default_weights_ignored(task_graph):
-    """Post-order traversal ignores node weights by default"""
-    po = [n.name for _, n in task_graph._postorder()]
+def test_postorder_default_priorities_ignored(task_project):
+    """Post-order traversal ignores task priorities by default"""
+    po = [n.name for _, n in task_project._postorder()]
     assert po.index('task 1.1') < po.index('task 1.3')
 
 
-def test_node_init_typeerror():
+def test_task_init_typeerror():
     with pytest.raises(TypeError) as info:
-        node = Node(47)
+        task = Task(47)
         assert "" in str(info.value)
 
 
-def test_node_append_node():
-    rootNode = Node('root node')
-    subNode1 = rootNode.append(Node('sub node'))
-    subNode2 = rootNode.append(Node('sub node 2'))
-    assert rootNode[0] is subNode1
-    assert rootNode[1] is subNode2
+def test_task_append_task():
+    rootTask = Task('root task')
+    subTask1 = rootTask.append(Task('sub task'))
+    subTask2 = rootTask.append(Task('sub task 2'))
+    assert rootTask[0] is subTask1
+    assert rootTask[1] is subTask2
 
 
-def test_node_append(graph):
-    thing1 = graph.append('1st thing')
-    thing2 = graph.append('2nd thing')
-    thing3 = graph.append('3rd thing')
+def test_task_append(project):
+    thing1 = project.append('1st thing')
+    thing2 = project.append('2nd thing')
+    thing3 = project.append('3rd thing')
 
-    assert thing1 is graph[0]
-    assert thing2 is graph[1]
-    assert thing3 is graph[2]
+    assert thing1 is project[0]
+    assert thing2 is project[1]
+    assert thing3 is project[2]
 
     assert thing1.name == '1st thing'
     assert thing2.name == '2nd thing'
     assert thing3.name == '3rd thing'
 
 
-def test_node_pop(graph):
-    assert graph[2].name == '3rd thing'
-    graph.pop(2)
+def test_task_pop(project):
+    assert project[2].name == '3rd thing'
+    project.pop(2)
     with pytest.raises(IndexError) as info:
-        thing3 = graph[2]
+        thing3 = project[2]
         assert "" in str(info.value)
 
 
-def test_node_pop_fail1(graph):
+def test_task_pop_fail1(project):
     with pytest.raises(IndexError):
-        graph.pop(20000)
+        project.pop(20000)
 
 
-def test_node_append_TypeError():
+def test_task_append_TypeError():
     with pytest.raises(TypeError) as info:
-        node = Node('mynode')
-        node.append(47)
+        task = Task('mytask')
+        task.append(47)
         assert "" in str(info.value)
 
 
-def test_blockedby(graph):
-    thing1 = graph[0]
+def test_blockedby(project):
+    thing1 = project[0]
     thing1_1 = thing1.append('thing within a thing')
     thing1_2 = thing1.append('thing blocking a thing')
     thing1_1.blockedby(thing1_2)
-    assert thing1_1.dependencies[0].name == 'thing blocking a thing'
+    assert thing1_1.blockers[0].name == 'thing blocking a thing'
 
 
-def test_blocking(graph):
-    thing2 = graph[1]
+def test_blockedby_TypeError():
+    with pytest.raises(TypeError):
+        task = Task('mytask')
+        task.blockedby(47)
+
+
+def test_blocking(project):
+    thing2 = project[1]
     thing2_1 = thing2.append('another thing within a thing')
     thing2_2 = thing2.append('another thing blocking a thing')
     thing2_2.blocking(thing2_1)
-    assert thing2_1.dependencies[0].name == 'another thing blocking a thing'
+    assert thing2_1.blockers[0].name == 'another thing blocking a thing'
+
+
+def test_blocking_TypeError():
+    with pytest.raises(TypeError):
+        task = Task('mytask')
+        task.blocking(47)
 
 
-def test_repr(graph):
-    assert graph.name == 'learn all the things'
+def test_repr(project):
+    assert project.name == 'learn all the things'
 
-    thing1 = graph[0]
-    thing2 = graph[1]
+    thing1 = project[0]
+    thing2 = project[1]
 
     with pytest.raises(IndexError) as info:
-        thing3 = graph[2]
+        thing3 = project[2]
         assert "" in str(info.value)
 
     assert thing1.name == '1st thing'
     assert thing2.name == '2nd thing'
 
-    assert str(graph) == "".join([
+    assert str(project) == "".join([
         "learn all the things:\n",
         "- 1st thing:\n",
         "  - thing within a thing\n",
@@ -154,14 +166,14 @@ def test_repr(graph):
     ])
 
 
-def test_deep_repr(graph):
+def test_deep_repr(project):
 
-    thing2_1 = graph[1][0]
+    thing2_1 = project[1][0]
     assert thing2_1.name == 'another thing within a thing'
 
     thing2_1.append('super deep thing')
 
-    assert str(graph) == "".join([
+    assert str(project) == "".join([
         "learn all the things:\n",
         "- 1st thing:\n",
         "  - thing within a thing\n",
@@ -175,42 +187,42 @@ def test_deep_repr(graph):
     thing2_1.pop(0)
 
 
-def test_weight_getter_setter():
-    node = Node('myNode')
-    default_weight = node.weight
-    node.weight = 5
+def test_priority_getter_setter():
+    task = Task('myTask')
+    default_priority = task.priority
+    task.priority = 5
 
-    assert default_weight == 1
-    assert node.weight == 5
+    assert default_priority == 1
+    assert task.priority == 5
 
 
 def test_name_getter_setter():
-    node = Node()
-    default_name = node.name
-    node.name = 'a new name'
+    task = Task()
+    default_name = task.name
+    task.name = 'a new name'
 
     assert default_name == ''
-    assert node.name == 'a new name'
+    assert task.name == 'a new name'
 
 
-def test_to_yaml(graph):
-    assert graph.name == 'learn all the things'
-    assert graph[0].name == '1st thing'
-    graph.to_yaml('mindgraph.yaml')
-    graph2 = read_yaml('mindgraph.yaml')
-    test_repr(graph2)
-    assert repr(graph) == repr(graph2)
-    os.remove('mindgraph.yaml')
+def test_to_yaml(project):
+    assert project.name == 'learn all the things'
+    assert project[0].name == '1st thing'
+    project.to_yaml('project.yaml')
+    project2 = read_yaml('project.yaml')
+    test_repr(project2)
+    assert repr(project) == repr(project2)
+    os.remove('project.yaml')
 
 
 def test_to_yaml_TypeError():
-    not_a_graph = yaml.dump("not a graph")
-    with open('not_a_graph.yaml', 'w') as f:
-        f.write(not_a_graph)
+    not_a_project = yaml.dump("not a project")
+    with open('not_a_project.yaml', 'w') as f:
+        f.write(not_a_project)
     with pytest.raises(TypeError) as info:
-        read_yaml('not_a_graph.yaml')
+        read_yaml('not_a_project.yaml')
         assert "" in str(info.value)
-    os.remove('not_a_graph.yaml')
+    os.remove('not_a_project.yaml')
 
 
 def test_parser():