Merge lp:~thisfred/desktopcouch/remove-recordtype-from-dict into lp:desktopcouch

Proposed by Eric Casteleijn
Status: Merged
Approved by: Vincenzo Di Somma
Approved revision: 218
Merged at revision: 207
Proposed branch: lp:~thisfred/desktopcouch/remove-recordtype-from-dict
Merge into: lp:desktopcouch
Diff against target: 444 lines (+116/-69)
6 files modified
desktopcouch/contacts/tests/test_record.py (+2/-3)
desktopcouch/records/doc/an_example_application.txt (+1/-1)
desktopcouch/records/record.py (+93/-58)
desktopcouch/records/tests/test_record.py (+17/-5)
desktopcouch/records/tests/test_server.py (+2/-1)
utilities/lint.sh (+1/-1)
To merge this branch: bzr merge lp:~thisfred/desktopcouch/remove-recordtype-from-dict
Reviewer Review Type Date Requested Status
Vincenzo Di Somma (community) Approve
Nicola Larosa (community) Approve
Review via email: mp+40916@code.launchpad.net

Commit message

Fixes #674487 where the record_type ended up in the dictionary of the record, where almost all client code had to filter it out again.
Fixes #669133 because the record_type version was never saved to CouchDB.
Fixes #575772 where the couchdb attachments API was defined on the wrong class.
Fixes #675787 where MergeableList.pop() could not be called without an index.

Description of the change

Fixed too much on this branch because I got a little frustrated with the number of bugs I ran into. If it's impossible to review, please tell me and I'll try and split all the fixes out into their own branches...

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

Fixes #674487 where the record_type ended up in the dictionary of the record, where almost all client code had to filter it out again.
Fixes #669133 because the record_type version was never saved to CouchDB.
Fixes #575772 where the couchdb attachments API was defined on the wrong class.
Fixes #675787 where MergeableList.pop() could not be called without an index.

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Note that the frustration is mostly aimed at myself, since this shows that I suck at reviewing and writing code, since I did at least one of those for all of the code that was buggy.

Revision history for this message
Nicola Larosa (teknico) wrote :

I like this branch. A couple comments:

- since you don't need current_value anymore here:

- for index, current_value in enumerate(self):
+ for index, _ in enumerate(self):

you might as well write:

     for index in range(len(self)):

Also, what's with the double blank lines before or after functions and methods? You only need double between classes. Don't get overboard with that automatic pep8 thing. ;-)

review: Approve
Revision history for this message
Eric Casteleijn (thisfred) wrote :

> - since you don't need current_value anymore here:
>
> - for index, current_value in enumerate(self):
> + for index, _ in enumerate(self):
>
> you might as well write:
>
> for index in range(len(self)):

Yeah that's better

> Also, what's with the double blank lines before or after functions and
> methods? You only need double between classes. Don't get overboard with that
> automatic pep8 thing. ;-)

Actually I like having two lines between top level definitions no matter what they are (so functions or Classes, *not* methods.) It makes me have to think less, and improves readability. Also a top level function and a class definition are rather similar things IMO.

218. By Eric Casteleijn

microbeautification

Revision history for this message
Vincenzo Di Somma (vds) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'desktopcouch/contacts/tests/test_record.py'
--- desktopcouch/contacts/tests/test_record.py 2010-10-31 23:20:14 +0000
+++ desktopcouch/contacts/tests/test_record.py 2010-11-15 23:22:02 +0000
@@ -46,6 +46,5 @@
46 first_name = 'manuel'46 first_name = 'manuel'
47 contact.first_name = first_name47 contact.first_name = first_name
48 self.assertEqual(first_name, contact.first_name)48 self.assertEqual(first_name, contact.first_name)
49 self.assertEqual(set(['record_type', 'first_name']),49 self.assertEqual(set(['first_name']), set(contact.keys()))
50 set(contact.keys()))50
51
5251
=== modified file 'desktopcouch/records/doc/an_example_application.txt'
--- desktopcouch/records/doc/an_example_application.txt 2010-11-03 22:41:43 +0000
+++ desktopcouch/records/doc/an_example_application.txt 2010-11-15 23:22:02 +0000
@@ -116,7 +116,7 @@
116kinds of dynamic coding easier:116kinds of dynamic coding easier:
117117
118 >>> sorted([key for key in my_recipe])118 >>> sorted([key for key in my_recipe])
119 ['ingredients', 'name', 'record_type']119 ['ingredients', 'name']
120120
121There are more specific types of RecordField available if you want to121There are more specific types of RecordField available if you want to
122encourage that the value of one of the fields is of a certain122encourage that the value of one of the fields is of a certain
123123
=== modified file 'desktopcouch/records/record.py'
--- desktopcouch/records/record.py 2010-11-03 22:41:43 +0000
+++ desktopcouch/records/record.py 2010-11-15 23:22:02 +0000
@@ -27,9 +27,13 @@
2727
28RX = re.compile('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')28RX = re.compile('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
2929
30
30def is_internal(key):31def is_internal(key):
31 """Test whether this is an internal key."""32 """Test whether this is an internal key."""
32 return key.startswith(('_', 'application_annotations'))33 return (
34 key == 'record_type' or
35 key.startswith(('_', 'application_annotations')))
36
3337
34def is_uuid_dict(dictionary):38def is_uuid_dict(dictionary):
35 """Test whether the passed object is a dictionary like object with39 """Test whether the passed object is a dictionary like object with
@@ -38,12 +42,14 @@
38 return dictionary and not [42 return dictionary and not [
39 key for key in dictionary if (not RX.match(key) and key != '_order')]43 key for key in dictionary if (not RX.match(key) and key != '_order')]
4044
45
41def record_factory(data):46def record_factory(data):
42 """Create a RecordDict or RecordList from passed data."""47 """Create a RecordDict or RecordList from passed data."""
43 if isinstance(data, dict):48 if isinstance(data, dict):
44 return _build_from_dict(data)49 return _build_from_dict(data)
45 return data50 return data
4651
52
47def _build_from_dict(data):53def _build_from_dict(data):
48 """Create a RecordDict from passed data."""54 """Create a RecordDict from passed data."""
49 result = RecordDict({})55 result = RecordDict({})
@@ -54,6 +60,7 @@
54 result[key] = record_factory(value)60 result[key] = record_factory(value)
55 return result61 return result
5662
63
57def validate(data):64def validate(data):
58 """Validate underlying data for Record objects."""65 """Validate underlying data for Record objects."""
59 if isinstance(data, dict):66 if isinstance(data, dict):
@@ -73,7 +80,7 @@
73 def __init__(self, field_name):80 def __init__(self, field_name):
74 self.field_name = field_name81 self.field_name = field_name
7582
76 def __get__(self, obj, type=None):83 def __get__(self, obj, owner=None):
77 return obj.get(self.field_name)84 return obj.get(self.field_name)
7885
79 def __set__(self, obj, value):86 def __set__(self, obj, value):
@@ -109,6 +116,7 @@
109 """Exception when attempting to set a key we don't allow."""116 """Exception when attempting to set a key we don't allow."""
110 pass117 pass
111118
119
112class NoRecordTypeSpecified(Exception):120class NoRecordTypeSpecified(Exception):
113 """Exception when creating a record without specifying record type"""121 """Exception when creating a record without specifying record type"""
114 def __str__(self):122 def __str__(self):
@@ -118,6 +126,7 @@
118class Attachment(object):126class Attachment(object):
119 """This represents the value of the attachment. The name is127 """This represents the value of the attachment. The name is
120 stored as the key that points to this name."""128 stored as the key that points to this name."""
129
121 def __init__(self, content=None, content_type=None):130 def __init__(self, content=None, content_type=None):
122 super(Attachment, self).__init__()131 super(Attachment, self).__init__()
123 if content is not None:132 if content is not None:
@@ -132,7 +141,8 @@
132 self.content_type = content_type141 self.content_type = content_type
133 self.needs_synch = content is not None142 self.needs_synch = content is not None
134143
135 def get_content_and_type(self):144 def get_content_and_type(self): # WTF? pylint: disable=E0202
145 """Get content and type of the attachment."""
136 assert self.content is not None146 assert self.content is not None
137 if hasattr(self.content, "read"):147 if hasattr(self.content, "read"):
138 if hasattr(self.content, "seek"):148 if hasattr(self.content, "seek"):
@@ -165,7 +175,7 @@
165 if isinstance(item, dict):175 if isinstance(item, dict):
166 item = record_factory(item)176 item = record_factory(item)
167 if hasattr(item, '_data'):177 if hasattr(item, '_data'):
168 item = item._data178 item = item._data # pylint: disable=W0212
169 self._data[key] = item179 self._data[key] = item
170180
171 def __delitem__(self, key):181 def __delitem__(self, key):
@@ -174,44 +184,11 @@
174 def __len__(self):184 def __len__(self):
175 return len(self._data)185 return len(self._data)
176186
177 def attach_by_reference(self, filename, getter_function):
178 """This should only be called by the server code to refer to a BLOB
179 that is in the database, so that we do not have to download it until
180 the user wants it."""
181 a = Attachment(None, None)
182 a.get_content_and_type = getter_function
183 self._attachments[filename] = a
184
185 def attach(self, content, filename, content_type):
186 """Attach a file-like or string-like object, with a particular name and
187 content type, to a document to be stored in the database. One can not
188 clobber names that already exist."""
189 if filename in self._attachments:
190 raise KeyError("%r already exists" % (filename,))
191 self._attachments[filename] = Attachment(content, content_type)
192
193 def detach(self, filename):
194 """Remove a BLOB attached to a document."""
195 try:
196 self._attachments.pop(filename)
197 self._detached.add(filename)
198 except KeyError:
199 raise KeyError("%r is not attached to this document" % (filename,))
200
201 def list_attachments(self):
202 return self._attachments.keys()
203
204 def attachment_data(self, filename):
205 """Retreive the attached data, the BLOB and content_type."""
206 try:
207 a = self._attachments[filename]
208 except KeyError:
209 raise KeyError("%r is not attached to this document" % (filename,))
210 return a.get_content_and_type()
211187
212class RecordDict(RecordData):188class RecordDict(RecordData):
213 """An object that represents an desktop CouchDB record fragment,189 """An object that represents an desktop CouchDB record fragment,
214 but behaves like a dictionary.190 but behaves like a dictionary.
191
215 """192 """
216193
217 def __setitem__(self, key, item):194 def __setitem__(self, key, item):
@@ -229,48 +206,54 @@
229206
230 def __cmp__(self, value):207 def __cmp__(self, value):
231 if isinstance(value, RecordDict):208 if isinstance(value, RecordDict):
232 return cmp(self._data, value._data)209 return cmp(self._data, value._data) # pylint: disable=W0212
233 return -1210 return -1
234211
235 def get(self, key, default=None):212 def get(self, key, default=None):
236 """Override get method."""213 """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
237 if not key in self._data:214 if not key in self._data:
238 return default215 return default
239 return self[key]216 return self[key]
240217
241 def update(self, data):218 def update(self, data):
242 """Set items from a dict"""219 """D.update(E, **F) -> None. Update D from dict/iterable E and F.
220
221 If E has a .keys() method, does: for k in E: D[k] = E[k]
222 If E lacks .keys() method, does: for (k, v) in E: D[k] = v
223 In either case, this is followed by: for k in F: D[k] = F[k]
224
225 """
243 for key, val in data.items():226 for key, val in data.items():
244 self[key] = val227 self[key] = val
245228
246 def items(self):229 def items(self):
247 """Return all items."""230 """D.items() -> list of D's (key, value) pairs, as 2-tuples."""
248 return [(key, self[key]) for key in self]231 return [(key, self[key]) for key in self]
249232
250 def keys(self):233 def keys(self):
251 """Return all keys."""234 """D.keys() -> list of D's keys."""
252 return self._data.keys()235 return self._data.keys()
253236
254 def setdefault(self, key, default):237 def setdefault(self, key, default):
255 """remap setdefault"""238 """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D."""
256 if not key in self:239 if not key in self:
257 self[key] = default240 self[key] = default
258 return self[key]241 return self[key]
259242
260 def has_key(self, key):243 def has_key(self, key):
244 """D.has_key(k) -> True if D has a key k, else False."""
261 return key in self245 return key in self
262246
263247
264
265class MergeableList(RecordData):248class MergeableList(RecordData):
266 """An object that represents a list of complex values."""249 """An object that represents a list of complex values."""
267250
268 @classmethod251 @classmethod
269 def from_list(klass, data):252 def from_list(cls, data):
270 """Create a RecordList from passed data."""253 """Create a RecordList from passed data."""
271 if not data:254 if not data:
272 raise ValueError("Can't set empty list values.'")255 raise ValueError("Can't set empty list values.'")
273 result = klass({})256 result = cls({})
274 for value in data:257 for value in data:
275 result.append(record_factory(value))258 result.append(record_factory(value))
276 return result259 return result
@@ -309,7 +292,7 @@
309292
310 def __contains__(self, value):293 def __contains__(self, value):
311 if hasattr(value, '_data'):294 if hasattr(value, '_data'):
312 value = value._data295 value = value._data # pylint: disable=W0212
313 return value in self._data.values()296 return value in self._data.values()
314297
315 def __cmp__(self, value):298 def __cmp__(self, value):
@@ -321,9 +304,9 @@
321 and hasattr(value, "__getitem__")304 and hasattr(value, "__getitem__")
322 and hasattr(value, "__len__")):305 and hasattr(value, "__len__")):
323 # we should have the same value in the same order306 # we should have the same value in the same order
324 length = len(value)307 length = len(value)
325 if len(self) == length:308 if len(self) == length:
326 for index, current_value in enumerate(self):309 for index in range(len(self)):
327 cmp_value = cmp(self[index], value[index])310 cmp_value = cmp(self[index], value[index])
328 if cmp_value != 0:311 if cmp_value != 0:
329 return cmp_value312 return cmp_value
@@ -345,20 +328,25 @@
345 """Return uuid key for index."""328 """Return uuid key for index."""
346 return self._get_ordered_keys()[index]329 return self._get_ordered_keys()[index]
347330
348 def get_value_for_uuid(self, uuid):331 def get_value_for_uuid(self, lookup_uuid):
349 """Allow API consumers that do know about the UUIDs to get332 """Allow API consumers that do know about the UUIDs to get
350 values directly.333 values directly.
351 """334 """
352 return super(MergeableList, self).__getitem__(uuid)335 return super(MergeableList, self).__getitem__(lookup_uuid)
353336
354 def append(self, value):337 def append(self, value):
355 """Append a value to end of MergeableList."""338 """L.append(object) -- append object to end."""
356 new_uuid = str(uuid.uuid4())339 new_uuid = str(uuid.uuid4())
357 self._data.setdefault('_order', sorted(self._data.keys())).append(340 self._data.setdefault('_order', sorted(self._data.keys())).append(
358 new_uuid)341 new_uuid)
359 super(MergeableList, self).__setitem__(new_uuid, value)342 super(MergeableList, self).__setitem__(new_uuid, value)
360343
361 def remove(self, value):344 def remove(self, value):
345 """L.remove(value) -- remove first occurrence of value.
346
347 Raises ValueError if the value is not present.
348
349 """
362 if len(self) == 1:350 if len(self) == 1:
363 raise ValueError("MergeableList cannot be empty.")351 raise ValueError("MergeableList cannot be empty.")
364 index = 0352 index = 0
@@ -371,15 +359,26 @@
371 index += 1359 index += 1
372 raise ValueError("list.remove(x): x not in list")360 raise ValueError("list.remove(x): x not in list")
373361
374 def pop(self, index):362 def pop(self, index=None):
363 """L.pop([index]) -> item -- remove and return item at
364 index. (default last.)
365
366 Raises IndexError if list is empty or index is out of range.
367 """
375 if len(self) == 1:368 if len(self) == 1:
376 raise ValueError("MergeableList cannot be empty.")369 raise ValueError("MergeableList cannot be empty.")
370 if index is None:
371 index = -1
377 value = self[index]372 value = self[index]
378 del self[index]373 del self[index]
379 return value374 return value
380375
381 def index(self, key):376 def index(self, key):
382 """Get value by index."""377 """L.index(value, [start, [stop]]) -> integer -- return first
378 index of value.
379
380 Raises ValueError if the value is not present.
381 """
383 return self.__getitem__(key)382 return self.__getitem__(key)
384383
385384
@@ -397,6 +396,8 @@
397 raise NoRecordTypeSpecified396 raise NoRecordTypeSpecified
398 super(Record, self).__init__(data)397 super(Record, self).__init__(data)
399 self._data['record_type'] = record_type398 self._data['record_type'] = record_type
399 if record_type_version is not None:
400 self._data['_record_type_version'] = record_type_version
400401
401 if record_id is not None:402 if record_id is not None:
402 # Explicit setting, so try to use it.403 # Explicit setting, so try to use it.
@@ -407,8 +408,6 @@
407 # _id already set. Verify that the explicit value agrees.408 # _id already set. Verify that the explicit value agrees.
408 if self._data['_id'] != record_id:409 if self._data['_id'] != record_id:
409 raise ValueError("record_id doesn't match value in data.")410 raise ValueError("record_id doesn't match value in data.")
410 if record_type_version:
411 self._record_type_version = record_type_version
412411
413 def __getitem__(self, key):412 def __getitem__(self, key):
414 if is_internal(key):413 if is_internal(key):
@@ -481,4 +480,40 @@
481 @property480 @property
482 def record_type_version(self):481 def record_type_version(self):
483 """Get the record type version if it's there."""482 """Get the record type version if it's there."""
484 return getattr(self, '_record_type_version', None)483 return self._data.get('_record_type_version', None)
484
485 def attach_by_reference(self, filename, getter_function):
486 """This should only be called by the server code to refer to a BLOB
487 that is in the database, so that we do not have to download it until
488 the user wants it."""
489 a = Attachment(None, None)
490 a.get_content_and_type = getter_function
491 self._attachments[filename] = a
492
493 def attach(self, content, filename, content_type):
494 """Attach a file-like or string-like object, with a particular name and
495 content type, to a document to be stored in the database. One can not
496 clobber names that already exist."""
497 if filename in self._attachments:
498 raise KeyError("%r already exists" % (filename,))
499 self._attachments[filename] = Attachment(content, content_type)
500
501 def detach(self, filename):
502 """Remove a BLOB attached to a document."""
503 try:
504 self._attachments.pop(filename)
505 self._detached.add(filename)
506 except KeyError:
507 raise KeyError("%r is not attached to this document" % (filename,))
508
509 def list_attachments(self):
510 """List the record's attachments."""
511 return self._attachments.keys()
512
513 def attachment_data(self, filename):
514 """Retreive the attached data, the BLOB and content_type."""
515 try:
516 a = self._attachments[filename]
517 except KeyError:
518 raise KeyError("%r is not attached to this document" % (filename,))
519 return a.get_content_and_type()
485520
=== modified file 'desktopcouch/records/tests/test_record.py'
--- desktopcouch/records/tests/test_record.py 2010-11-12 14:34:21 +0000
+++ desktopcouch/records/tests/test_record.py 2010-11-15 23:22:02 +0000
@@ -98,7 +98,7 @@
98 def test_iter(self):98 def test_iter(self):
99 """Tests it is possible to iterate over the record fields"""99 """Tests it is possible to iterate over the record fields"""
100 self.assertEquals(sorted(list(iter(self.record))),100 self.assertEquals(sorted(list(iter(self.record))),
101 ['a', 'b', 'record_type', 'subfield', 'subfield_uuid'])101 ['a', 'b', 'subfield', 'subfield_uuid'])
102102
103 def test_setitem_internal(self):103 def test_setitem_internal(self):
104 """Test it is not possible to set a private field on a record"""104 """Test it is not possible to set a private field on a record"""
@@ -125,7 +125,7 @@
125 def test_keys(self):125 def test_keys(self):
126 """Test getting the keys."""126 """Test getting the keys."""
127 self.assertEqual(127 self.assertEqual(
128 ['a', 'b', 'record_type', 'subfield', 'subfield_uuid'],128 ['a', 'b', 'subfield', 'subfield_uuid'],
129 sorted(self.record.keys()))129 sorted(self.record.keys()))
130 self.assertIn("a", self.record)130 self.assertIn("a", self.record)
131 self.assertNotIn('f', self.record)131 self.assertNotIn('f', self.record)
@@ -248,10 +248,18 @@
248 def test_mergeable_list_pop_wrong_index(self):248 def test_mergeable_list_pop_wrong_index(self):
249 """Test pop when index is out or range."""249 """Test pop when index is out or range."""
250 value = [1, 2, 3, 4, 5]250 value = [1, 2, 3, 4, 5]
251 self.record["subfield_uuid"] = value251 self.record["subfield_uuid"] = MergeableList.from_list(value)
252 self.assertRaises(IndexError, self.record["subfield_uuid"].pop,252 self.assertRaises(IndexError, self.record["subfield_uuid"].pop,
253 len(value) * 2)253 len(value) * 2)
254254
255 def test_mergeable_list_pop_no_index(self):
256 """Test pop without an index argument."""
257 value = [1, 2, 3, 4, 5]
258 self.record["subfield_uuid"] = MergeableList.from_list(value)
259 popped_value = self.record["subfield_uuid"].pop()
260 self.assertEqual(value[-1], popped_value)
261
262
255 def test_mergeable_list_pop_last(self):263 def test_mergeable_list_pop_last(self):
256 """Test that exception is raised when poping last item"""264 """Test that exception is raised when poping last item"""
257 self.record["subfield_uuid"] = MergeableList.from_list([1])265 self.record["subfield_uuid"] = MergeableList.from_list([1])
@@ -353,9 +361,13 @@
353 """Test record type version support"""361 """Test record type version support"""
354 data = {"_id": "recordid"}362 data = {"_id": "recordid"}
355 record1 = Record(data, record_type="url")363 record1 = Record(data, record_type="url")
364 self.assertIs(None, record1._data.get( # pylint: disable=W0212
365 '_record_type_version'))
356 self.assertIs(None, record1.record_type_version)366 self.assertIs(None, record1.record_type_version)
357 record2 = Record(data, record_type="url", record_type_version=1)367 record2 = Record(data, record_type="url", record_type_version='1.0')
358 self.assertEqual(record2.record_type_version, 1)368 self.assertEqual(record2._data[ # pylint: disable=W0212
369 '_record_type_version'], '1.0')
370 self.assertEqual(record2.record_type_version, '1.0')
359371
360372
361class TestRecordFactory(TestCase):373class TestRecordFactory(TestCase):
362374
=== modified file 'desktopcouch/records/tests/test_server.py'
--- desktopcouch/records/tests/test_server.py 2010-11-10 21:17:11 +0000
+++ desktopcouch/records/tests/test_server.py 2010-11-15 23:22:02 +0000
@@ -385,7 +385,8 @@
385 results = self.database.get_all_records()385 results = self.database.get_all_records()
386386
387 self.assertTrue(8 <= len(results)) # our 8, plus callers' data387 self.assertTrue(8 <= len(results)) # our 8, plus callers' data
388 self.assertIn("record_type", results[0])388 self.assertNotIn("record_type", results[0])
389 self.assertTrue(hasattr(results[0], "record_type"))
389390
390 for result in results: # index notation391 for result in results: # index notation
391 if result.record_type == good_record_type:392 if result.record_type == good_record_type:
392393
=== modified file 'utilities/lint.sh'
--- utilities/lint.sh 2010-11-12 14:34:21 +0000
+++ utilities/lint.sh 2010-11-15 23:22:02 +0000
@@ -37,7 +37,7 @@
37fi37fi
3838
39export PYTHONPATH="/usr/share/pycentral/pylint/site-packages:desktopcouch:$PYTHONPATH"39export PYTHONPATH="/usr/share/pycentral/pylint/site-packages:desktopcouch:$PYTHONPATH"
40pylint="`which pylint` -r n -i y --variable-rgx=[a-z_][a-z0-9_]{0,30} --attr-rgx=[a-z_][a-z0-9_]{1,30} --method-rgx=[a-z_][a-z0-9_]{2,} -d I0011,R0902,R0904,R0913,W0142"40pylint="`which pylint` -r n -i y --variable-rgx=[a-z_][a-z0-9_]{0,30} --attr-rgx=[a-z_][a-z0-9_]{1,30} --method-rgx=[a-z_][a-z0-9_]{2,} -d I0011,R0902,R0903,R0904,R0913,W0142"
4141
42pylint_notices=`$pylint $pyfiles`42pylint_notices=`$pylint $pyfiles`
4343

Subscribers

People subscribed via source and target branches