Merge lp:~thisfred/desktopcouch/fix-unique-id-test into lp:desktopcouch
- fix-unique-id-test
- Merge into trunk
Proposed by
Eric Casteleijn
Status: | Merged |
---|---|
Approved by: | Vincenzo Di Somma |
Approved revision: | 202 |
Merged at revision: | 200 |
Proposed branch: | lp:~thisfred/desktopcouch/fix-unique-id-test |
Merge into: | lp:desktopcouch |
Diff against target: |
444 lines (+128/-90) 2 files modified
desktopcouch/pair/couchdb_pairing/couchdb_io.py (+73/-50) desktopcouch/pair/tests/test_couchdb_io.py (+55/-40) |
To merge this branch: | bzr merge lp:~thisfred/desktopcouch/fix-unique-id-test |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Manuel de la Peña (community) | Approve | ||
Vincenzo Di Somma (community) | Approve | ||
dobey (community) | Approve | ||
Review via email: mp+40461@code.launchpad.net |
Commit message
This fixes get_my_
Description of the change
This fixes get_my_
To post a comment you must log in.
Revision history for this message
Manuel de la Peña (mandel) wrote : | # |
Revision history for this message
dobey (dobey) : | # |
review:
Approve
Revision history for this message
Manuel de la Peña (mandel) wrote : | # |
me stupid :P, I forgot to approve
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'desktopcouch/pair/couchdb_pairing/couchdb_io.py' |
2 | --- desktopcouch/pair/couchdb_pairing/couchdb_io.py 2010-11-03 02:47:32 +0000 |
3 | +++ desktopcouch/pair/couchdb_pairing/couchdb_io.py 2010-11-09 17:39:48 +0000 |
4 | @@ -33,22 +33,29 @@ |
5 | PAIRED_SERVER_RECORD_TYPE = RECTYPE_BASE + "paired_server" |
6 | MY_ID_RECORD_TYPE = RECTYPE_BASE + "server_identity" |
7 | |
8 | -def obsfuscate(d): |
9 | - def maybe_hide(k, v): |
10 | - if hasattr(k, "endswith") and k.endswith("secret"): |
11 | - return "".join(rep for rep, vi in zip(cycle('Hidden'), v)) |
12 | + |
13 | +def obsfuscate(dictionary): |
14 | + """Obfuscate a string by replacing all secret values.""" |
15 | + def maybe_hide(key, value): |
16 | + """Replace with gibberish where necessary.""" |
17 | + if hasattr(key, "endswith") and key.endswith("secret"): |
18 | + return "".join(rep for rep, vi in zip(cycle('Hidden'), value)) |
19 | else: |
20 | - return v |
21 | - |
22 | - if not hasattr(d, "iteritems"): |
23 | - return d |
24 | - return dict((k, maybe_hide(k, obsfuscate(v))) for k, v in d.iteritems()) |
25 | + return value |
26 | + |
27 | + if not hasattr(dictionary, "iteritems"): |
28 | + return dictionary |
29 | + return dict( |
30 | + (key, maybe_hide(key, obsfuscate(value))) for key, value |
31 | + in dictionary.iteritems()) |
32 | + |
33 | |
34 | def mkuri(hostname, port, has_ssl=False, path="", auth_pair=None): |
35 | """Create a URI from parts.""" |
36 | protocol = "https" if has_ssl else "http" |
37 | if auth_pair: |
38 | - auth = (":".join(map(urllib.quote, auth_pair)) + "@") |
39 | + auth = (":".join( |
40 | + map(urllib.quote, auth_pair)) + "@") # pylint: disable-msg=W0141 |
41 | else: |
42 | auth = "" |
43 | if (protocol, port) in (("http", 80), ("https", 443)): |
44 | @@ -57,13 +64,14 @@ |
45 | port = str(port) |
46 | return "%s://%s%s:%s/%s" % (protocol, auth, hostname, port, path) |
47 | |
48 | -def _get_db(name, create=True, uri=None, |
49 | - ctx=local_files.DEFAULT_CONTEXT): |
50 | + |
51 | +def _get_db(name, create=True, uri=None, ctx=local_files.DEFAULT_CONTEXT): |
52 | """Get (and create?) a database.""" |
53 | return server.CouchDatabase(name, create=create, uri=uri, ctx=ctx) |
54 | |
55 | + |
56 | def put_paired_host(oauth_data, uri=None, |
57 | - ctx=local_files.DEFAULT_CONTEXT, **kwargs): |
58 | + ctx=local_files.DEFAULT_CONTEXT, **kwargs): |
59 | """Create a new paired-host record. OAuth information is required, and |
60 | after the uri, keyword parameters are added to the record.""" |
61 | pairing_id = str(uuid.uuid4()) |
62 | @@ -80,25 +88,28 @@ |
63 | "token_secret": str(oauth_data["token_secret"]), |
64 | } |
65 | data.update(kwargs) |
66 | - d = _get_db("management", uri=uri, ctx=ctx) |
67 | - r = Record(data) |
68 | - record_id = d.put_record(r) |
69 | + database = _get_db("management", uri=uri, ctx=ctx) |
70 | + record = Record(data) |
71 | + record_id = database.put_record(record) |
72 | return record_id |
73 | |
74 | + |
75 | def put_static_paired_service(oauth_data, service_name, uri=None, |
76 | - ctx=local_files.DEFAULT_CONTEXT): |
77 | + ctx=local_files.DEFAULT_CONTEXT): |
78 | """Create a new service record.""" |
79 | return put_paired_host(oauth_data, uri=uri, service_name=service_name, |
80 | pull_from_server=True, push_to_server=True, ctx=ctx) |
81 | |
82 | + |
83 | def put_dynamic_paired_host(hostname, remote_uuid, oauth_data, uri=None, |
84 | - ctx=local_files.DEFAULT_CONTEXT): |
85 | + ctx=local_files.DEFAULT_CONTEXT): |
86 | """Create a new dynamic-host record.""" |
87 | return put_paired_host(oauth_data, uri=uri, pairing_identifier=remote_uuid, |
88 | push_to_server=True, server=hostname, ctx=ctx) |
89 | |
90 | -def get_static_paired_hosts(uri=None, |
91 | - ctx=local_files.DEFAULT_CONTEXT, port=None): |
92 | + |
93 | +def get_static_paired_hosts(uri=None, # pylint: disable-msg=R0914 |
94 | + ctx=local_files.DEFAULT_CONTEXT, port=None): |
95 | """Retreive a list of static hosts' information in the form of |
96 | (ID, service name, to_push, to_pull) .""" |
97 | if not uri and port is not None: |
98 | @@ -121,6 +132,7 @@ |
99 | logging.debug("static pairings are %s", unique_hosts) |
100 | return unique_hosts |
101 | |
102 | + |
103 | def get_database_names_replicatable(uri, oauth_tokens=None, service=False, |
104 | ctx=local_files.DEFAULT_CONTEXT, user_id=None): |
105 | """Find a list of local databases, minus dbs that we do not want to |
106 | @@ -143,7 +155,7 @@ |
107 | "Got list of databases from %s/_all_dbs?user_id=%s:\n %s", |
108 | couchdb_server, user_id, all_dbs) |
109 | |
110 | - except socket.error, e: |
111 | + except socket.error: |
112 | logging.error("Can't get list of databases from %s", couchdb_server) |
113 | return set() |
114 | |
115 | @@ -158,14 +170,17 @@ |
116 | |
117 | return set([n for n in all_dbs - excluded if not n.startswith("_")]) |
118 | |
119 | + |
120 | def get_my_host_unique_id(uri=None, create=True, |
121 | - ctx=local_files.DEFAULT_CONTEXT): |
122 | + ctx=local_files.DEFAULT_CONTEXT): |
123 | """Returns a list of ids we call ourselves. We complain in the log if it's |
124 | more than one, but it's really no error. If there are zero (id est, we've |
125 | never paired with anyone), then returns None.""" |
126 | |
127 | db = _get_db("management", uri=uri, ctx=ctx) |
128 | - ids = _get_management_data(MY_ID_RECORD_TYPE, "self_identity", uri=uri) |
129 | + db.ensure_full_commit() |
130 | + ids = _get_management_data( |
131 | + MY_ID_RECORD_TYPE, "self_identity", uri=uri, ctx=ctx) |
132 | ids = list(set(ids)) # uniqify |
133 | if len(ids) > 1: |
134 | logging.error("DANGER! We have more than one record claiming to be " |
135 | @@ -178,16 +193,15 @@ |
136 | if not create: |
137 | return None |
138 | else: |
139 | - data = { "self_identity": str(uuid.uuid4()), |
140 | - "record_type": MY_ID_RECORD_TYPE, |
141 | - } |
142 | + data = {"self_identity": str(uuid.uuid4()), |
143 | + "record_type": MY_ID_RECORD_TYPE} |
144 | data["_id"] = data["self_identity"] |
145 | db.put_record(Record(data)) |
146 | logging.debug("set new self-identity value: %r", data["self_identity"]) |
147 | return [data["self_identity"]] |
148 | |
149 | -def get_all_known_pairings(uri=None, |
150 | - ctx=local_files.DEFAULT_CONTEXT): |
151 | + |
152 | +def get_all_known_pairings(uri=None, ctx=local_files.DEFAULT_CONTEXT): |
153 | """Info dicts about all pairings, even if marked "unpaired", keyed on |
154 | hostid with another dict as the value.""" |
155 | d = {} |
156 | @@ -204,8 +218,10 @@ |
157 | d[hostid] = v |
158 | return d |
159 | |
160 | + |
161 | def _get_management_data(record_type, key, uri=None, |
162 | - ctx=local_files.DEFAULT_CONTEXT): |
163 | + ctx=local_files.DEFAULT_CONTEXT): |
164 | + """Get the management data from couchdb.""" |
165 | db = _get_db("management", uri=uri, ctx=ctx) |
166 | results = db.get_all_records(record_type=record_type) |
167 | values = list() |
168 | @@ -226,19 +242,20 @@ |
169 | logging.debug("found %d %s records", len(values), key) |
170 | return values |
171 | |
172 | + |
173 | def create_database(dst_host, dst_port, dst_name, use_ssl=False, |
174 | - oauth_tokens=None): |
175 | + oauth_tokens=None): |
176 | """Given parts, create a database.""" |
177 | dst_url = mkuri(dst_host, dst_port, use_ssl) |
178 | return server.CouchDatabase(dst_name, dst_url, create=True, |
179 | oauth_tokens=oauth_tokens) |
180 | |
181 | -def replicate(source_database, target_database, target_host=None, |
182 | - target_port=None, source_host=None, source_port=None, |
183 | - source_ssl=False, target_ssl=False, source_oauth=None, |
184 | - target_oauth=None, local_uri=None): |
185 | + |
186 | +def replicate(source_database, target_database, # pylint: disable-msg=R0914 |
187 | + target_host=None, target_port=None, source_host=None, |
188 | + source_port=None, source_ssl=False, target_ssl=False, |
189 | + source_oauth=None, target_oauth=None, local_uri=None): |
190 | """This replication is instant and blocking, and does not persist. """ |
191 | - |
192 | try: |
193 | if target_host: |
194 | # Target databases must exist before replicating to them. |
195 | @@ -251,9 +268,10 @@ |
196 | else: |
197 | server.CouchDatabase(target_database, create=True, uri=local_uri) |
198 | logging.debug("db exists, and we're ready to replicate") |
199 | - except: |
200 | - logging.exception("can't create/verify %r %s:%d oauth=%s", |
201 | - target_database, target_host, target_port, obsfuscate(target_oauth)) |
202 | + except: # pylint: disable-msg=W0702 |
203 | + logging.exception( |
204 | + "can't create/verify %r %s:%d oauth=%s", target_database, |
205 | + target_host, target_port, obsfuscate(target_oauth)) |
206 | if source_host: |
207 | source = mkuri(source_host, source_port, source_ssl, urllib.quote( |
208 | source_database, safe="")) |
209 | @@ -285,20 +303,20 @@ |
210 | "asking %r to replicate %s to %s", obsfuscate(local_uri), |
211 | obsfuscate(source), obsfuscate(target),) |
212 | |
213 | - ### All until python-couchdb gets a Server.replicate() function |
214 | + # All until python-couchdb gets a Server.replicate() function |
215 | local_server = server.OAuthCapableServer(local_uri) |
216 | resp, data = local_server.resource.post(path='/_replicate', |
217 | content=record) |
218 | - |
219 | - logging.debug("replicate result: %r %r", obsfuscate(resp), obsfuscate(data)) |
220 | - ### |
221 | - except: |
222 | + logging.debug( |
223 | + "replicate result: %r %r", obsfuscate(resp), obsfuscate(data)) |
224 | + except: # pylint: disable-msg=W0702 |
225 | logging.exception("can't replicate %r %r <== %r", source_database, |
226 | local_uri, obsfuscate(record)) |
227 | |
228 | + |
229 | def get_pairings(uri=None, ctx=local_files.DEFAULT_CONTEXT): |
230 | """Get a list of paired servers.""" |
231 | - db = _get_db("management", create=True, uri=None, ctx=ctx) |
232 | + db = _get_db("management", create=True, uri=uri, ctx=ctx) |
233 | |
234 | design_doc = "paired_servers" |
235 | if not db.view_exists("paired_servers", design_doc): |
236 | @@ -306,8 +324,10 @@ |
237 | if (doc.record_type == %r && ! doc.unpaired) // unset or False |
238 | if (doc.application_annotations && |
239 | doc.application_annotations["Ubuntu One"] && |
240 | - doc.application_annotations["Ubuntu One"].private_application_annotations && |
241 | - doc.application_annotations["Ubuntu One"].private_application_annotations.deleted) { |
242 | + doc.application_annotations[ |
243 | + "Ubuntu One"].private_application_annotations && |
244 | + doc.application_annotations[ |
245 | + "Ubuntu One"].private_application_annotations.deleted) { |
246 | // don't emit deleted or unpaired items |
247 | } else { |
248 | if (doc.server) { |
249 | @@ -316,22 +336,25 @@ |
250 | emit(doc.service_name, doc); |
251 | } |
252 | } |
253 | - }""" % (PAIRED_SERVER_RECORD_TYPE,) |
254 | + }""" % (PAIRED_SERVER_RECORD_TYPE,) |
255 | db.add_view("paired_servers", map_js, None, design_doc) |
256 | |
257 | return db.execute_view("paired_servers") |
258 | |
259 | + |
260 | def remove_pairing(record_id, is_reconciled, uri=None, |
261 | - ctx=local_files.DEFAULT_CONTEXT): |
262 | + ctx=local_files.DEFAULT_CONTEXT): |
263 | """Remove a pairing record (or mark it as dead so it can be cleaned up |
264 | properly later).""" |
265 | - db = _get_db("management", create=True, uri=None, ctx=ctx) |
266 | + db = _get_db("management", create=True, uri=uri, ctx=ctx) |
267 | if is_reconciled: |
268 | db.delete_record(record_id) |
269 | else: |
270 | - db.update_fields(record_id, { "unpaired": True }) |
271 | + db.update_fields(record_id, {"unpaired": True}) |
272 | + |
273 | |
274 | def expunge_pairing(host_id, uri=None): |
275 | + """Remove pairing record for host_id.""" |
276 | try: |
277 | d = get_all_known_pairings(uri) |
278 | record_id = d[host_id]["record_id"] |
279 | |
280 | === modified file 'desktopcouch/pair/tests/test_couchdb_io.py' |
281 | --- desktopcouch/pair/tests/test_couchdb_io.py 2010-11-02 21:20:35 +0000 |
282 | +++ desktopcouch/pair/tests/test_couchdb_io.py 2010-11-09 17:39:48 +0000 |
283 | @@ -1,3 +1,4 @@ |
284 | +"""Test Couchdb IO""" |
285 | # Copyright 2009 Canonical Ltd. |
286 | # |
287 | # This file is part of desktopcouch. |
288 | @@ -31,7 +32,9 @@ |
289 | import socket |
290 | URI = None # use autodiscovery that desktopcouch.tests permits. |
291 | |
292 | + |
293 | class TestCouchdbIo(unittest.TestCase): |
294 | + """Test case for Couchdb IO""" |
295 | |
296 | def setUp(self): |
297 | """setup each test""" |
298 | @@ -64,22 +67,39 @@ |
299 | |
300 | def tearDown(self): |
301 | """tear down each test""" |
302 | - del self.mgt_database._server['management'] |
303 | - del self.mgt_database._server['foo'] |
304 | - |
305 | + del self.mgt_database._server['management'] # pylint: disable-msg=W0212 |
306 | + del self.mgt_database._server['foo'] # pylint: disable-msg=W0212 |
307 | |
308 | def test_obsfuscation(self): |
309 | - t = {'url': 'https://couchdb.one.ubuntu.com/u%2Fb2%2Fc8%2F276%2Ftest', 'auth': {'oauth': {'consumer_secret': 'SeCrEtSe', 'token': '3XRjQrWX92TTTJFDTWJJ', 'consumer_key': 'ubuntuone', 'token_secret': 'jBmSeCrEtawkefwklefliwuregqwlkeh347wq87w4fiuq4fyu3q4fiqwu4fqwfiqufM6xjsPwSeCrEt4'}}} |
310 | + """Test the obfuscation of sensitive data.""" |
311 | + t = { |
312 | + 'url': |
313 | + 'https://couchdb.one.ubuntu.com/u%2Fb2%2Fc8%2F276%2Ftest', |
314 | + 'auth': { |
315 | + 'oauth': { |
316 | + 'consumer_secret': 'SeCrEtSe', |
317 | + 'token': '3XRjQrWX92TTTJFDTWJJ', |
318 | + 'consumer_key': 'ubuntuone', |
319 | + 'token_secret': 'jBmSeCrEtawkefwklefliwuregqwlkeh347wq87w4f' |
320 | + 'iuq4fyu3q4fiqwu4fqwfiqufM6xjsPwSeCrEt4'}}} |
321 | cleaned_t = couchdb_io.obsfuscate(t) |
322 | - self.failIf("SeCrEt" in str(cleaned_t), {'url': 'https://couchdb.one.ubuntu.com/u%2Fb2%2Fc8%2F276%2Ftest', 'auth': {'oauth': {'consumer_secret': 'HiddenHidd', 'token': '3XRjQrWX92TTTJFDTWJJ', 'consumer_key': 'ubuntuone', 'token_secret': 'HiddenHiddenHiddenHiddenHiddenHiddenHiddenHiddenHiddenHiddenHiddenHiddenHiddenHi'}}}) |
323 | - |
324 | + self.failIf( |
325 | + "SeCrEt" in str(cleaned_t), { |
326 | + 'url': |
327 | + 'https://couchdb.one.ubuntu.com/u%2Fb2%2Fc8%2F276%2Ftest', |
328 | + 'auth': {'oauth': { |
329 | + 'consumer_secret': 'HiddenHidd', |
330 | + 'token': '3XRjQrWX92TTTJFDTWJJ', |
331 | + 'consumer_key': 'ubuntuone', |
332 | + 'token_secret': 'HiddenHiddenHiddenHiddenHiddenHiddenHidden' |
333 | + 'HiddenHiddenHiddenHiddenHiddenHiddenHi'}}}) |
334 | self.assertEqual(couchdb_io.obsfuscate(""), "") |
335 | self.assertEqual(couchdb_io.obsfuscate({}), {}) |
336 | - self.assertEqual(couchdb_io.obsfuscate({1:{}}), {1:{}}) |
337 | - self.assertEqual(couchdb_io.obsfuscate({1:1}), {1:1}) |
338 | - |
339 | - |
340 | - def test_put_static_paired_service(self): |
341 | + self.assertEqual(couchdb_io.obsfuscate({1: {}}), {1: {}}) |
342 | + self.assertEqual(couchdb_io.obsfuscate({1: 1}), {1: 1}) |
343 | + |
344 | + def test_put_static_paired_service(self): # pylint: disable-msg=R0201 |
345 | + """Test putting a static paired service.""" |
346 | service_name = "dummyfortest" |
347 | oauth_data = { |
348 | "consumer_key": str("abcdef"), |
349 | @@ -87,10 +107,14 @@ |
350 | "token": str("opqrst"), |
351 | "token_secret": str("uvwxyz"), |
352 | } |
353 | - couchdb_io.put_static_paired_service(oauth_data, service_name, uri=URI, ctx=test_environment.test_context) |
354 | - pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
355 | + couchdb_io.put_static_paired_service( |
356 | + oauth_data, service_name, uri=URI, |
357 | + ctx=test_environment.test_context) |
358 | + couchdb_io.get_pairings(ctx=test_environment.test_context) |
359 | + # Assert something? |
360 | |
361 | def test_put_dynamic_paired_host(self): |
362 | + """Test putting a dynamically paired host.""" |
363 | hostname = "host%d" % (os.getpid(),) |
364 | remote_uuid = str(uuid.uuid4()) |
365 | oauth_data = { |
366 | @@ -107,20 +131,24 @@ |
367 | couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
368 | uri=URI, ctx=test_environment.test_context) |
369 | |
370 | - pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
371 | - self.assertEqual(4, len(pairings)) # 3, plus 1 from setUp() |
372 | + pairings = list(couchdb_io.get_pairings( |
373 | + ctx=test_environment.test_context)) |
374 | + # 3, plus 1 from setUp() |
375 | + self.assertEqual(4, len(pairings)) |
376 | self.assertEqual(pairings[0].value["oauth"], oauth_data) |
377 | self.assertEqual(pairings[0].value["server"], hostname) |
378 | self.assertEqual(pairings[0].value["pairing_identifier"], remote_uuid) |
379 | |
380 | for i, row in enumerate(pairings): |
381 | - couchdb_io.remove_pairing(row.id, i == 1, ctx=test_environment.test_context) |
382 | + couchdb_io.remove_pairing( |
383 | + row.id, i == 1, ctx=test_environment.test_context) |
384 | |
385 | - pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
386 | + pairings = list( |
387 | + couchdb_io.get_pairings(ctx=test_environment.test_context)) |
388 | self.assertEqual(0, len(pairings)) |
389 | |
390 | - |
391 | def test_get_database_names_replicatable_bad_server(self): |
392 | + """Test get database names from the wrong url.""" |
393 | hostname = "test.desktopcouch.example.com" |
394 | try: |
395 | socket.gethostbyname(hostname) |
396 | @@ -136,39 +164,26 @@ |
397 | pass |
398 | |
399 | def test_get_database_names_replicatable(self): |
400 | - names = couchdb_io.get_database_names_replicatable(uri=URI, ctx=test_environment.test_context) |
401 | + """Test get the names of replicatable databases.""" |
402 | + names = couchdb_io.get_database_names_replicatable( |
403 | + uri=URI, ctx=test_environment.test_context) |
404 | self.assertFalse('management' in names) |
405 | self.assertTrue('foo' in names) |
406 | self.assertFalse('bar' in names, names) # is excluded |
407 | |
408 | def test_get_my_host_unique_id(self): |
409 | - got = couchdb_io.get_my_host_unique_id(uri=URI, ctx=test_environment.test_context) |
410 | - again = couchdb_io.get_my_host_unique_id(uri=URI, ctx=test_environment.test_context) |
411 | + """Test get the unique id of the host record.""" |
412 | + got = couchdb_io.get_my_host_unique_id( |
413 | + uri=URI, ctx=test_environment.test_context) |
414 | + again = couchdb_io.get_my_host_unique_id( |
415 | + uri=URI, ctx=test_environment.test_context) |
416 | self.assertEquals(len(got), 1) |
417 | self.assertEquals(got, again) |
418 | |
419 | def test_mkuri(self): |
420 | + """Test creating a URI.""" |
421 | uri = couchdb_io.mkuri( |
422 | 'fnord.org', 55241, has_ssl=True, path='a/b/c', |
423 | auth_pair=('f o o', 'b=a=r')) |
424 | self.assertEquals( |
425 | 'https://f%20o%20o:b%3Da%3Dr@fnord.org:55241/a/b/c', uri) |
426 | - |
427 | - def Xtest_replication_good(self): |
428 | - pass |
429 | - |
430 | - def Xtest_replication_no_oauth_remote(self): |
431 | - pass |
432 | - |
433 | - def Xtest_replication_bad_oauth_remote(self): |
434 | - pass |
435 | - |
436 | - def Xtest_replication_no_oauth_local(self): |
437 | - pass |
438 | - |
439 | - def Xtest_replication_bad_oauth_local(self): |
440 | - pass |
441 | - |
442 | - |
443 | -if __name__ == "__main__": |
444 | - unittest.main() |
+1 I like those pylint error out too :)