Merge lp:~thisfred/u1db/u1todo-example-1 into lp:u1db

Proposed by Eric Casteleijn on 2012-04-18
Status: Merged
Approved by: Eric Casteleijn on 2012-04-19
Approved revision: 248
Merged at revision: 249
Proposed branch: lp:~thisfred/u1db/u1todo-example-1
Merge into: lp:u1db
Diff against target: 244 lines (+229/-0)
2 files modified
u1todo/test_u1todo.py (+118/-0)
u1todo/u1todo.py (+111/-0)
To merge this branch: bzr merge lp:~thisfred/u1db/u1todo-example-1
Reviewer Review Type Date Requested Status
Lucio Torre (community) 2012-04-18 Approve on 2012-04-18
Review via email: mp+102571@code.launchpad.net

Description of the Change

This has no UI yet, but is the bare bones back end of an application that will store todo items.

To post a comment you must log in.
Lucio Torre (lucio.torre) wrote :

1- i am not comfortable with all init methods having side effects. This code for example looks strange:
27 + def test_indexes_are_added(self):
28 + """New indexes are added when a new store is created."""
29 + TodoStore(self.db)
30 + INDEXES['foo'] = ['bar']
31 + self.assertNotIn('foo', dict(self.db.list_indexes()))
32 + TodoStore(self.db)
33 + self.assertIn('foo', dict(self.db.list_indexes()))

Maybe we should have factories that create the objects.

2- There is no relation between the todoStore and the Tasks, which make me thing that there no need for the todo store. If we move to having no side effects on init, we could have store.get() and store.new() to produce the desired side effects.

Eric Casteleijn (thisfred) wrote :

suggested fixes applied

lp:~thisfred/u1db/u1todo-example-1 updated on 2012-04-18
246. By Eric Casteleijn on 2012-04-18

refactored to remove side effects from init, and make store responsible for creating, getting and saving tasks.

247. By Eric Casteleijn on 2012-04-18

Took the initialization step out of the init.

248. By Eric Casteleijn on 2012-04-18

removed _trial_temp

Lucio Torre (lucio.torre) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'u1todo'
2=== added file 'u1todo/__init__.py'
3=== added directory 'u1todo/_trial_temp'
4=== added directory 'u1todo/_trial_temp/xdg_cache'
5=== added directory 'u1todo/_trial_temp/xdg_config'
6=== added directory 'u1todo/_trial_temp/xdg_data'
7=== added file 'u1todo/test_u1todo.py'
8--- u1todo/test_u1todo.py 1970-01-01 00:00:00 +0000
9+++ u1todo/test_u1todo.py 2012-04-18 20:45:23 +0000
10@@ -0,0 +1,118 @@
11+from testtools import TestCase
12+from u1todo import Task, TodoStore, INDEXES, EMPTY_TASK, DONE, NOT_DONE
13+from u1db.backends import inmemory
14+
15+
16+class TodoStoreTestCase(TestCase):
17+
18+ def setUp(self):
19+ super(TodoStoreTestCase, self).setUp()
20+ self.db = inmemory.InMemoryDatabase("u1todo")
21+
22+ def test_initialize_db(self):
23+ """Creates indexes."""
24+ store = TodoStore(self.db)
25+ store.initialize_db()
26+ self.assertEqual(INDEXES, dict(self.db.list_indexes()))
27+
28+ def test_indexes_are_added(self):
29+ """New indexes are added when a new store is created."""
30+ store = TodoStore(self.db)
31+ store.initialize_db()
32+ INDEXES['foo'] = ['bar']
33+ self.assertNotIn('foo', dict(self.db.list_indexes()))
34+ store = TodoStore(self.db)
35+ store.initialize_db()
36+ self.assertIn('foo', dict(self.db.list_indexes()))
37+
38+ def test_indexes_are_updated(self):
39+ """Indexes are updated when a new store is created."""
40+ store = TodoStore(self.db)
41+ store.initialize_db()
42+ new_expression = ['newtags']
43+ INDEXES['tags'] = new_expression
44+ self.assertNotEqual(
45+ new_expression, dict(self.db.list_indexes())['tags'])
46+ store = TodoStore(self.db)
47+ store.initialize_db()
48+ self.assertEqual(new_expression, dict(self.db.list_indexes())['tags'])
49+
50+ def test_tag_task(self):
51+ """Sets the tags for a task."""
52+ store = TodoStore(self.db)
53+ task = store.new_task()
54+ tag = "you're it"
55+ store.tag_task(task, [tag])
56+ self.assertEqual([tag], task.tags)
57+
58+ def test_new_task(self):
59+ """Creates a new task."""
60+ store = TodoStore(self.db)
61+ task = store.new_task()
62+ self.assertTrue(isinstance(task, Task))
63+ self.assertIsNotNone(task.document.doc_id)
64+
65+ def test_save_task_get_task(self):
66+ """Saves a modified task and retrieves it from the db."""
67+ store = TodoStore(self.db)
68+ task = store.new_task()
69+ task.title = "This is the title."
70+ store.save_task(task)
71+ task_copy = store.get_task(task.document.doc_id)
72+ self.assertEqual(task.title, task_copy.title)
73+
74+
75+class TaskTestCase(TestCase):
76+ """Tests for Task."""
77+
78+ def setUp(self):
79+ super(TaskTestCase, self).setUp()
80+ self.db = inmemory.InMemoryDatabase("u1todo")
81+ self.document = self.db.create_doc(EMPTY_TASK)
82+
83+ def test_task(self):
84+ """Initializing a task."""
85+ task = Task(self.document)
86+ self.assertEqual("", task.title)
87+ self.assertEqual([], task.tags)
88+ self.assertEqual(False, task.done)
89+
90+ def test_set_title(self):
91+ """Changing the title is persistent."""
92+ task = Task(self.document)
93+ title = "new task"
94+ task.title = title
95+ self.assertEqual(title, task._content['title'])
96+
97+ def test_set_done(self):
98+ """Changing the done property changes the underlying content."""
99+ task = Task(self.document)
100+ self.assertEqual(NOT_DONE, task._content['done'])
101+ task.done = True
102+ self.assertEqual(DONE, task._content['done'])
103+
104+ def test_tags(self):
105+ """Tags property returns a list."""
106+ task = Task(self.document)
107+ self.assertEqual([], task.tags)
108+
109+ def set_tags(self):
110+ """Setting the tags property changes the underlying content."""
111+ task = Task(self.document)
112+ task.tags = ["foo", "bar"]
113+ self.assertEqual(["foo", "bar"], task._content['tags'])
114+
115+ def test_add_tag(self):
116+ """Tag is added to task's tags."""
117+ task = Task(self.document)
118+ task.add_tag("foo")
119+ self.assertEqual(["foo"], task.tags)
120+
121+ def test_remove_tag(self):
122+ """Tag is removed from task's tags."""
123+ task = Task(self.document)
124+ task.add_tag("foo")
125+ task.add_tag("bar")
126+ self.assertEqual(["foo", "bar"], task.tags)
127+ task.remove_tag("foo")
128+ self.assertEqual(["bar"], task.tags)
129
130=== added file 'u1todo/u1todo.py'
131--- u1todo/u1todo.py 1970-01-01 00:00:00 +0000
132+++ u1todo/u1todo.py 2012-04-18 20:45:23 +0000
133@@ -0,0 +1,111 @@
134+import json
135+
136+DONE = 'true'
137+NOT_DONE = 'false'
138+
139+EMPTY_TASK = json.dumps({"title": "", "done": NOT_DONE, "tags": []})
140+
141+INDEXES = {
142+ 'tags': ['tags'],
143+ 'done': ['done'],
144+}
145+
146+
147+class TodoStore(object):
148+ """The todo application backend."""
149+
150+ def __init__(self, db):
151+ self.db = db
152+
153+ def initialize_db(self):
154+ db_indexes = dict(self.db.list_indexes())
155+ for name, expression in INDEXES.items():
156+ if name not in db_indexes:
157+ # The index does not yet exist.
158+ self.db.create_index(name, expression)
159+ continue
160+ if expression == db_indexes[name]:
161+ # The index exists and is up to date.
162+ continue
163+ # The index exists but the definition is out of date.
164+ self.db.delete_index(name)
165+ self.db.create_index(name, expression)
166+
167+ def tag_task(self, task, tags):
168+ """Set the tags of a task."""
169+ task.tags = tags
170+
171+ def get_task(self, task_id):
172+ """Get a task from the database."""
173+ document = self.db.get_doc(task_id)
174+ if document is None:
175+ raise KeyError("No task with id '%s'." % (task_id,))
176+ return Task(document)
177+
178+ def new_task(self):
179+ document = self.db.create_doc(content=EMPTY_TASK)
180+ return Task(document)
181+
182+ def save_task(self, task):
183+ """Save task to the database."""
184+ self.db.put_doc(task.document)
185+
186+
187+class Task(object):
188+ """A todo item."""
189+
190+ def __init__(self, document):
191+ self._document = document
192+ self._content = json.loads(document.content)
193+
194+ def _get_title(self):
195+ """Get the task title."""
196+ return self._content['title']
197+
198+ def _set_title(self, title):
199+ """Set the task title and save to db."""
200+ self._content['title'] = title
201+
202+ title = property(_get_title, _set_title, doc="Title of the task.")
203+
204+ def _get_done(self):
205+ """Get the status of the task."""
206+ # Indexes on booleans are not currently possible, so we convert to and
207+ # from strings.
208+ return True if self._content['done'] == DONE else False
209+
210+ def _set_done(self, value):
211+ # Indexes on booleans are not currently possible, so we convert to and
212+ # from strings.
213+ self._content['done'] = DONE if value else NOT_DONE
214+
215+ done = property(_get_done, _set_done, doc="Done flag.")
216+
217+ def _get_tags(self):
218+ """Get tags associated with the task."""
219+ return self._content['tags']
220+
221+ def _set_tags(self, tags):
222+ self._content['tags'] = list(set(tags))
223+
224+ tags = property(_get_tags, _set_tags, doc="Task tags.")
225+
226+ def add_tag(self, tag):
227+ tags = self._content['tags']
228+ if tag in tags:
229+ # Tasks cannot have the same tag more than once, so ignore the
230+ # request to add it again.
231+ return
232+ tags.append(tag)
233+
234+ def remove_tag(self, tag):
235+ tags = self._content['tags']
236+ if tag not in tags:
237+ # Can't remove a tag that the task does not have.
238+ raise KeyError("Task has no tag '%s'." % (tag,))
239+ tags.remove(tag)
240+
241+ @property
242+ def document(self):
243+ self._document.content = json.dumps(self._content)
244+ return self._document

Subscribers

People subscribed via source and target branches