Merge lp:~robru/friends/csv-model-schema into lp:friends

Proposed by Robert Bruce Park
Status: Merged
Approved by: Ken VanDine
Approved revision: 185
Merged at revision: 181
Proposed branch: lp:~robru/friends/csv-model-schema
Merge into: lp:friends
Diff against target: 383 lines (+113/-93)
10 files modified
data/model-schema.csv (+23/-0)
debian/friends.install (+1/-0)
friends/tests/mocks.py (+9/-2)
friends/tests/test_protocols.py (+5/-5)
friends/utils/base.py (+13/-13)
friends/utils/model.py (+39/-44)
service/src/service.vala (+14/-26)
setup.py (+3/-1)
tools/debug_live.py (+3/-0)
tools/debug_slave.py (+3/-2)
To merge this branch: bzr merge lp:~robru/friends/csv-model-schema
Reviewer Review Type Date Requested Status
Ken VanDine Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+157246@code.launchpad.net

Commit message

Unify model schema definition into a CSV.

Description of the change

The holy grail ;-)

Define the model schema in one unified location, and parse it in from both model.py and service.vala. This way schema changes are much less error-prone.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:179
http://jenkins.qa.ubuntu.com/job/friends-ci/19/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-raring-amd64-ci/19

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/19/rebuild

review: Approve (continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote :

You should try to read from the source tree before trying to read the installed file.

Revision history for this message
Ken VanDine (ken-vandine) wrote :

Whoops, forgot to set it to needs fixing.

review: Needs Fixing
lp:~robru/friends/csv-model-schema updated
180. By Robert Bruce Park

Stop parsing CSV at import-time.

This commit makes it a lot easier to force the reading of the schema
from the source tree before installing Friends.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:180
http://jenkins.qa.ubuntu.com/job/friends-ci/20/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-raring-amd64-ci/20

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/20/rebuild

review: Approve (continuous-integration)
lp:~robru/friends/csv-model-schema updated
181. By Robert Bruce Park

Unify some import logic.

182. By Robert Bruce Park

Make a comment more explicit.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:181
http://jenkins.qa.ubuntu.com/job/friends-ci/21/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-raring-amd64-ci/21

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/21/rebuild

review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:182
http://jenkins.qa.ubuntu.com/job/friends-ci/22/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-raring-amd64-ci/22

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/22/rebuild

review: Approve (continuous-integration)
lp:~robru/friends/csv-model-schema updated
183. By Robert Bruce Park

Regex is overkill.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:183
http://jenkins.qa.ubuntu.com/job/friends-ci/23/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-raring-amd64-ci/23

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/23/rebuild

review: Approve (continuous-integration)
lp:~robru/friends/csv-model-schema updated
184. By Robert Bruce Park

Rebase on trunk.

185. By Robert Bruce Park

Flush revision queue every 5 minutes.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:184
http://jenkins.qa.ubuntu.com/job/friends-ci/24/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-raring-amd64-ci/24

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/24/rebuild

review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:185
http://jenkins.qa.ubuntu.com/job/friends-ci/25/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-raring-amd64-ci/25

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/25/rebuild

review: Approve (continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'data/model-schema.csv'
--- data/model-schema.csv 1970-01-01 00:00:00 +0000
+++ data/model-schema.csv 2013-04-05 18:49:21 +0000
@@ -0,0 +1,23 @@
1protocol,s
2account_id,t
3message_id,s
4stream,s
5sender,s
6sender_id,s
7sender_nick,s
8from_me,b
9timestamp,s
10message,s
11icon_uri,s
12url,s
13likes,t
14liked,b
15link_picture,s
16link_name,s
17link_url,s
18link_desc,s
19link_caption,s
20link_icon,s
21location,s
22latitude,d
23longitude,d
024
=== modified file 'debian/friends.install'
--- debian/friends.install 2013-02-07 22:05:09 +0000
+++ debian/friends.install 2013-04-05 18:49:21 +0000
@@ -1,3 +1,4 @@
1usr/bin/friends-service1usr/bin/friends-service
2usr/share/dbus-1/services/com.canonical.Friends.Service.service2usr/share/dbus-1/services/com.canonical.Friends.Service.service
3usr/share/friends/model-schema.csv
3usr/share/glib-2.0/schemas/com.canonical.friends.gschema.xml4usr/share/glib-2.0/schemas/com.canonical.friends.gschema.xml
45
=== modified file 'friends/tests/mocks.py'
--- friends/tests/mocks.py 2013-03-14 19:44:13 +0000
+++ friends/tests/mocks.py 2013-04-05 18:49:21 +0000
@@ -40,9 +40,16 @@
40from urllib.parse import urlsplit40from urllib.parse import urlsplit
41from gi.repository import Dee41from gi.repository import Dee
4242
43# By default, Schema.FILES will look for the system-installed schema
44# file first, and then failing that will look for the one in the
45# source tree, for performance reasons. During testing though, we want
46# to look at the source tree first, so we reverse the list here.
47from friends.utils.model import Schema
48Schema.FILES = list(reversed(Schema.FILES))
49SCHEMA = Schema()
50
43from friends.utils.base import Base51from friends.utils.base import Base
44from friends.utils.logging import LOG_FORMAT52from friends.utils.logging import LOG_FORMAT
45from friends.utils.model import COLUMN_TYPES
4653
4754
48try:55try:
@@ -58,7 +65,7 @@
58# Create a test model that will not interfere with the user's environment.65# Create a test model that will not interfere with the user's environment.
59# We'll use this object as a mock of the real model.66# We'll use this object as a mock of the real model.
60TestModel = Dee.SharedModel.new('com.canonical.Friends.TestSharedModel')67TestModel = Dee.SharedModel.new('com.canonical.Friends.TestSharedModel')
61TestModel.set_schema_full(COLUMN_TYPES)68TestModel.set_schema_full(SCHEMA.TYPES)
6269
6370
64@mock.patch('friends.utils.http._soup', mock.Mock())71@mock.patch('friends.utils.http._soup', mock.Mock())
6572
=== modified file 'friends/tests/test_protocols.py'
--- friends/tests/test_protocols.py 2013-04-04 19:44:01 +0000
+++ friends/tests/test_protocols.py 2013-04-05 18:49:21 +0000
@@ -26,10 +26,10 @@
2626
27from friends.protocols.flickr import Flickr27from friends.protocols.flickr import Flickr
28from friends.protocols.twitter import Twitter28from friends.protocols.twitter import Twitter
29from friends.tests.mocks import FakeAccount, LogMock, TestModel, mock29from friends.tests.mocks import SCHEMA, FakeAccount, LogMock, TestModel, mock
30from friends.utils.base import Base, feature, linkify_string30from friends.utils.base import Base, feature, linkify_string
31from friends.utils.manager import ProtocolManager31from friends.utils.manager import ProtocolManager
32from friends.utils.model import COLUMN_INDICES, Model32from friends.utils.model import Model
3333
3434
35class TestProtocolManager(unittest.TestCase):35class TestProtocolManager(unittest.TestCase):
@@ -282,7 +282,7 @@
282 self.assertEqual(1, TestModel.get_n_rows())282 self.assertEqual(1, TestModel.get_n_rows())
283 # The first published message wins.283 # The first published message wins.
284 row = TestModel.get_row(0)284 row = TestModel.get_row(0)
285 self.assertEqual(row[COLUMN_INDICES['message']], 'hello, @jimmy')285 self.assertEqual(row[SCHEMA.INDICES['message']], 'hello, @jimmy')
286286
287 @mock.patch('friends.utils.base.Model', TestModel)287 @mock.patch('friends.utils.base.Model', TestModel)
288 @mock.patch('friends.utils.base._seen_ids', {})288 @mock.patch('friends.utils.base._seen_ids', {})
@@ -364,10 +364,10 @@
364 # See? Two rows in the table.364 # See? Two rows in the table.
365 self.assertEqual(2, TestModel.get_n_rows())365 self.assertEqual(2, TestModel.get_n_rows())
366 # The first row is the message from fred.366 # The first row is the message from fred.
367 self.assertEqual(TestModel.get_row(0)[COLUMN_INDICES['sender']],367 self.assertEqual(TestModel.get_row(0)[SCHEMA.INDICES['sender']],
368 'fred')368 'fred')
369 # The second row is the message from tedtholomew.369 # The second row is the message from tedtholomew.
370 self.assertEqual(TestModel.get_row(1)[COLUMN_INDICES['sender']],370 self.assertEqual(TestModel.get_row(1)[SCHEMA.INDICES['sender']],
371 'tedtholomew')371 'tedtholomew')
372372
373 @mock.patch('friends.utils.base.Model', TestModel)373 @mock.patch('friends.utils.base.Model', TestModel)
374374
=== modified file 'friends/utils/base.py'
--- friends/utils/base.py 2013-04-04 22:33:23 +0000
+++ friends/utils/base.py 2013-04-05 18:49:21 +0000
@@ -35,22 +35,22 @@
3535
36from friends.errors import FriendsError, ContactsError36from friends.errors import FriendsError, ContactsError
37from friends.utils.authentication import Authentication37from friends.utils.authentication import Authentication
38from friends.utils.model import COLUMN_INDICES, SCHEMA, DEFAULTS38from friends.utils.model import Schema, Model, persist_model
39from friends.utils.model import Model, persist_model
40from friends.utils.notify import notify39from friends.utils.notify import notify
41from friends.utils.time import ISO8601_FORMAT40from friends.utils.time import ISO8601_FORMAT
4241
4342
44STUB = lambda *ignore, **kwignore: None43STUB = lambda *ignore, **kwignore: None
45COMMA_SPACE = ', '44COMMA_SPACE = ', '
46AVATAR_IDX = COLUMN_INDICES['icon_uri']45SCHEMA = Schema()
47FROM_ME_IDX = COLUMN_INDICES['from_me']46AVATAR_IDX = SCHEMA.INDICES['icon_uri']
48STREAM_IDX = COLUMN_INDICES['stream']47FROM_ME_IDX = SCHEMA.INDICES['from_me']
49SENDER_IDX = COLUMN_INDICES['sender']48STREAM_IDX = SCHEMA.INDICES['stream']
50MESSAGE_IDX = COLUMN_INDICES['message']49SENDER_IDX = SCHEMA.INDICES['sender']
51ID_IDX = COLUMN_INDICES['message_id']50MESSAGE_IDX = SCHEMA.INDICES['message']
52ACCT_IDX = COLUMN_INDICES['account_id']51ID_IDX = SCHEMA.INDICES['message_id']
53TIME_IDX = COLUMN_INDICES['timestamp']52ACCT_IDX = SCHEMA.INDICES['account_id']
53TIME_IDX = SCHEMA.INDICES['timestamp']
5454
55# See friends/tests/test_protocols.py for further documentation55# See friends/tests/test_protocols.py for further documentation
56LINKIFY_REGEX = re.compile(56LINKIFY_REGEX = re.compile(
@@ -355,8 +355,8 @@
355 # the order which they appear in the SCHEMA. If any are left355 # the order which they appear in the SCHEMA. If any are left
356 # over at the end of this, raise a TypeError indicating the356 # over at the end of this, raise a TypeError indicating the
357 # unexpected column names.357 # unexpected column names.
358 for column_name, column_type in SCHEMA:358 for column_name, column_type in SCHEMA.COLUMNS:
359 args.append(kwargs.pop(column_name, DEFAULTS[column_type]))359 args.append(kwargs.pop(column_name, SCHEMA.DEFAULTS[column_type]))
360 if len(kwargs) > 0:360 if len(kwargs) > 0:
361 raise TypeError('Unexpected keyword arguments: {}'.format(361 raise TypeError('Unexpected keyword arguments: {}'.format(
362 COMMA_SPACE.join(sorted(kwargs))))362 COMMA_SPACE.join(sorted(kwargs))))
@@ -506,7 +506,7 @@
506 def _calculate_row_cell(self, message_id, column_name):506 def _calculate_row_cell(self, message_id, column_name):
507 """Find x,y coords in the model based on message_id and column_name."""507 """Find x,y coords in the model based on message_id and column_name."""
508 row_id = _seen_ids.get(message_id)508 row_id = _seen_ids.get(message_id)
509 col_idx = COLUMN_INDICES.get(column_name)509 col_idx = SCHEMA.INDICES.get(column_name)
510 if None in (row_id, col_idx):510 if None in (row_id, col_idx):
511 raise FriendsError('Cell could not be found.')511 raise FriendsError('Cell could not be found.')
512 return row_id, col_idx512 return row_id, col_idx
513513
=== modified file 'friends/utils/model.py'
--- friends/utils/model.py 2013-03-13 04:36:33 +0000
+++ friends/utils/model.py 2013-04-05 18:49:21 +0000
@@ -24,11 +24,8 @@
2424
2525
26__all__ = [26__all__ = [
27 'Schema',
27 'Model',28 'Model',
28 'COLUMN_NAMES',
29 'COLUMN_TYPES',
30 'COLUMN_INDICES',
31 'DEFAULTS',
32 'MODEL_DBUS_NAME',29 'MODEL_DBUS_NAME',
33 'persist_model',30 'persist_model',
34 'prune_model',31 'prune_model',
@@ -41,46 +38,44 @@
41log = logging.getLogger(__name__)38log = logging.getLogger(__name__)
4239
4340
44# DO NOT EDIT THIS WITHOUT ADJUSTING service.vala IN LOCKSTEP41class Schema:
45SCHEMA = (42 """Represents the DeeModel schema data that we defined in CSV."""
46 ('protocol', 's'), # Same as UOA 'provider_name'43 DEFAULTS = {
47 ('account_id', 't'), # Same as UOA account id44 'b': False,
48 ('message_id', 's'),45 's': '',
49 ('stream', 's'),46 'd': 0,
50 ('sender', 's'),47 't': 0,
51 ('sender_id', 's'),48 }
52 ('sender_nick', 's'),49
53 ('from_me', 'b'),50 FILES = [
54 ('timestamp', 's'),51 'data/model-schema.csv',
55 ('message', 's'),52 '/usr/share/friends/model-schema.csv',
56 ('icon_uri', 's'),53 ]
57 ('url', 's'),54
58 ('likes', 't'),55 def __init__(self):
59 ('liked', 'b'),56 """Parse CSV from disk."""
60 ('link_picture', 's'),57 self.COLUMNS = []
61 ('link_name', 's'),58 self.NAMES = []
62 ('link_url', 's'),59 self.TYPES = []
63 ('link_desc', 's'),60 self.INDICES = {}
64 ('link_caption', 's'),61
65 ('link_icon', 's'),62 files = self.FILES.copy()
66 ('location', 's'),63 while files:
67 ('latitude', 'd'),64 filename = files.pop()
68 ('longitude', 'd'),65 log.debug('Looking for SCHEMA in {}'.format(filename))
69 )66 try:
7067 with open(filename) as schema:
7168 for col in schema:
72# It's useful to have separate lists of the column names and types.69 name, variant = col.rstrip().split(',')
73COLUMN_NAMES, COLUMN_TYPES = zip(*SCHEMA)70 self.COLUMNS.append((name, variant))
74# A reverse mapping from column name to the column index. This is useful for71 self.NAMES.append(name)
75# pulling column values out of a row of data.72 self.TYPES.append(variant)
76COLUMN_INDICES = {name: i for i, name in enumerate(COLUMN_NAMES)}73 log.debug(
77# This defines default values for the different data types74 'Found {} columns for SCHEMA'.format(len(self.COLUMNS)))
78DEFAULTS = {75 break
79 'b': False,76 except IOError:
80 's': '',77 pass
81 'd': 0,78 self.INDICES = {name: i for i, name in enumerate(self.NAMES)}
82 't': 0,
83 }
8479
8580
86MODEL_DBUS_NAME = 'com.canonical.Friends.Streams'81MODEL_DBUS_NAME = 'com.canonical.Friends.Streams'
8782
=== modified file 'service/src/service.vala'
--- service/src/service.vala 2013-03-13 04:36:33 +0000
+++ service/src/service.vala 2013-04-05 18:49:21 +0000
@@ -74,29 +74,17 @@
74 debug ("Failed to load model from resource manager: %s", e.message);74 debug ("Failed to load model from resource manager: %s", e.message);
75 }75 }
7676
77 string[] SCHEMA = {"s",77 string[] SCHEMA = {};
78 "t",78
79 "s",79 var file = FileStream.open("/usr/share/friends/model-schema.csv", "r");
80 "s",80 string line = null;
81 "s",81 while (true)
82 "s",82 {
83 "s",83 line = file.read_line();
84 "b",84 if (line == null) break;
85 "s",85 SCHEMA += line.split(",")[1];
86 "s",86 }
87 "s",87 debug ("Found %u schema columns.", SCHEMA.length);
88 "s",
89 "t",
90 "b",
91 "s",
92 "s",
93 "s",
94 "s",
95 "s",
96 "s",
97 "s",
98 "d",
99 "d"};
10088
101 bool schemaReset = false;89 bool schemaReset = false;
10290
@@ -109,7 +97,7 @@
109 schemaReset = true;97 schemaReset = true;
110 else98 else
111 {99 {
112 for (int i=0; i < _SCHEMA.length;i++ )100 for (int i=0; i < _SCHEMA.length; i++)
113 {101 {
114 if (_SCHEMA[i] != SCHEMA[i])102 if (_SCHEMA[i] != SCHEMA[i])
115 {103 {
@@ -165,11 +153,11 @@
165 debug ("NOT LEADER");153 debug ("NOT LEADER");
166 });154 });
167155
168 Timeout.add_seconds (30, () => {156 Timeout.add_seconds (300, () => {
169 shared_model.flush_revision_queue();157 shared_model.flush_revision_queue();
170 debug ("Storing model with %u rows", model.get_n_rows());158 debug ("Storing model with %u rows", model.get_n_rows());
171 resources.store ((Dee.SequenceModel)model, "com.canonical.Friends.Streams");159 resources.store ((Dee.SequenceModel)model, "com.canonical.Friends.Streams");
172 return false;160 return true;
173 });161 });
174 }162 }
175163
176164
=== modified file 'setup.py'
--- setup.py 2013-03-14 20:25:42 +0000
+++ setup.py 2013-04-05 18:49:21 +0000
@@ -33,7 +33,9 @@
33 },33 },
34 data_files = [34 data_files = [
35 ('/usr/share/glib-2.0/schemas',35 ('/usr/share/glib-2.0/schemas',
36 ['data/com.canonical.friends.gschema.xml'])36 ['data/com.canonical.friends.gschema.xml']),
37 ('/usr/share/friends',
38 ['data/model-schema.csv']),
37 ],39 ],
38 entry_points = {40 entry_points = {
39 'console_scripts': ['friends-dispatcher = friends.main:main'],41 'console_scripts': ['friends-dispatcher = friends.main:main'],
4042
=== modified file 'tools/debug_live.py'
--- tools/debug_live.py 2013-03-13 18:05:13 +0000
+++ tools/debug_live.py 2013-04-05 18:49:21 +0000
@@ -24,6 +24,9 @@
2424
25sys.path.insert(0, '.')25sys.path.insert(0, '.')
2626
27# Ignore system-installed schema.
28from friends.tests.mocks import SCHEMA
29
27from gi.repository import GLib30from gi.repository import GLib
28from friends.utils.logging import initialize31from friends.utils.logging import initialize
2932
3033
=== modified file 'tools/debug_slave.py'
--- tools/debug_slave.py 2013-04-04 22:33:06 +0000
+++ tools/debug_slave.py 2013-04-05 18:49:21 +0000
@@ -11,7 +11,8 @@
11from gi.repository import Dee11from gi.repository import Dee
12from gi.repository import GLib12from gi.repository import GLib
1313
14from friends.utils.model import COLUMN_NAMES14from friends.tests.mocks import SCHEMA
15
1516
16class Slave:17class Slave:
17 def __init__(self):18 def __init__(self):
@@ -25,7 +26,7 @@
25 row = self.model.get_row(itr)26 row = self.model.get_row(itr)
26 print('\n' * 5)27 print('\n' * 5)
27 for i, col in enumerate(row):28 for i, col in enumerate(row):
28 print('{:12}: {}'.format(COLUMN_NAMES[i], col))29 print('{:12}: {}'.format(SCHEMA.NAMES[i], col))
29 print('ROWS: ', len(self.model))30 print('ROWS: ', len(self.model))
3031
3132

Subscribers

People subscribed via source and target branches