Merge lp:~allenap/maas/find-early-imports into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5487
Proposed branch: lp:~allenap/maas/find-early-imports
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 353 lines (+89/-53)
12 files modified
Makefile (+6/-4)
src/maasserver/api/tests/test_ipaddresses.py (+4/-3)
src/maasserver/forms_iprange.py (+1/-2)
src/maasserver/models/migrations/create_default_storage_layout.py (+0/-9)
src/maasserver/models/tests/test_template.py (+4/-6)
src/maasserver/rpc/rackcontrollers.py (+2/-3)
src/maasserver/urls_api.py (+4/-5)
src/maasserver/utils/dns.py (+4/-5)
src/maasserver/utils/tests/test_dns.py (+4/-7)
src/provisioningserver/templates/commissioning-user-data/snippets/maas_api_helper.py (+5/-6)
src/provisioningserver/utils/isc.py (+1/-3)
utilities/find-early-imports (+54/-0)
To merge this branch: bzr merge lp:~allenap/maas/find-early-imports
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+308602@code.launchpad.net

Commit message

Check for imports that appear before __all__ and signal them as lint.

Strictly this isn't a problem for Python, it's a problem for MAAS's import formatter. Until we update it this simple script will help keep the "front matter" consistent.

Description of the change

Something I wrote over the weekend while nursing a cold and feeling sorry for myself.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Surprised that so many files had that after imports.

review: Approve
Revision history for this message
Mike Pontillo (mpontillo) wrote :

+1; thanks for doing this. To be honest, most of these are probably my fault. PyCharm automatically imports things above __all__ and I constantly have to re-review my code and move them down so the imports formatter sorts them properly.

Revision history for this message
Gavin Panella (allenap) wrote :

> +1; thanks for doing this. To be honest, most of these are probably my
> fault. PyCharm automatically imports things above __all__ and I
> constantly have to re-review my code and move them down so the imports
> formatter sorts them properly.

I don't think PyCharm's doing anything wrong, it just doesn't gel with
our (wonky) tooling right now. I should have invested my time fixing
that rather than writing this, but I wasn't all with it this weekend.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2016-10-12 15:26:17 +0000
3+++ Makefile 2016-10-17 06:51:39 +0000
4@@ -285,8 +285,7 @@
5
6 # Python lint checks are time-intensive, but flake8 now knows how to run
7 # parallel jobs, and does so by default.
8-lint-py: sources = \
9- setup.py $(wildcard contrib/*.py) src templates twisted utilities etc
10+lint-py: sources = setup.py src templates twisted
11 lint-py: bin/flake8
12 @find $(sources) -name '*.py' \
13 ! -path '*/migrations/*' ! -path '*/south_migrations/*' -print0 \
14@@ -296,8 +295,7 @@
15 # be close to 10 but MAAS has many functions that are over that so we
16 # start with a much higher number. Over time we can ratchet it down.
17 lint-py-complexity: maximum=26
18-lint-py-complexity: sources = \
19- setup.py $(wildcard contrib/*.py) src templates twisted utilities etc
20+lint-py-complexity: sources = setup.py src templates twisted
21 lint-py-complexity: bin/flake8
22 @find $(sources) -name '*.py' \
23 ! -path '*/migrations/*' ! -path '*/south_migrations/*' \
24@@ -306,8 +304,12 @@
25 --isolated --max-complexity=$(maximum)
26
27 # Statically check imports against policy.
28+lint-py-imports: sources = setup.py src templates twisted
29 lint-py-imports:
30 @utilities/check-imports
31+ @find $(sources) -name '*.py' \
32+ ! -path '*/migrations/*' ! -path '*/south_migrations/*' \
33+ -print0 | xargs -r0 utilities/find-early-imports
34
35 lint-doc:
36 @utilities/doc-lint
37
38=== modified file 'src/maasserver/api/tests/test_ipaddresses.py'
39--- src/maasserver/api/tests/test_ipaddresses.py 2016-10-12 17:50:40 +0000
40+++ src/maasserver/api/tests/test_ipaddresses.py 2016-10-17 06:51:39 +0000
41@@ -2,8 +2,6 @@
42 # GNU Affero General Public License version 3 (see the file LICENSE).
43
44 """Tests for IP addresses API."""
45-from testtools.matchers import HasLength
46-
47
48 __all__ = []
49
50@@ -26,7 +24,10 @@
51 from maasserver.utils.orm import reload_object
52 from maastesting.matchers import DocTestMatches
53 from netaddr import IPAddress
54-from testtools.matchers import Equals
55+from testtools.matchers import (
56+ Equals,
57+ HasLength,
58+)
59
60
61 class TestIPAddressesAPI(APITestCase.ForUserAndAdmin):
62
63=== modified file 'src/maasserver/forms_iprange.py'
64--- src/maasserver/forms_iprange.py 2016-05-12 19:07:37 +0000
65+++ src/maasserver/forms_iprange.py 2016-10-17 06:51:39 +0000
66@@ -2,14 +2,13 @@
67 # GNU Affero General Public License version 3 (see the file LICENSE).
68
69 """IPRange form."""
70-from maasserver.models import Subnet
71-
72
73 __all__ = [
74 "IPRangeForm",
75 ]
76
77 from maasserver.forms import MAASModelForm
78+from maasserver.models import Subnet
79 from maasserver.models.iprange import IPRange
80
81
82
83=== modified file 'src/maasserver/models/migrations/create_default_storage_layout.py'
84--- src/maasserver/models/migrations/create_default_storage_layout.py 2016-06-22 17:03:02 +0000
85+++ src/maasserver/models/migrations/create_default_storage_layout.py 2016-10-17 06:51:39 +0000
86@@ -16,15 +16,6 @@
87 do it, to ensure that the migrations meet validation requirements.)
88 """
89
90-from __future__ import (
91- absolute_import,
92- print_function,
93- unicode_literals,
94- )
95-
96-str = None
97-
98-__metaclass__ = type
99 __all__ = [
100 "clear_full_storage_configuration",
101 ]
102
103=== modified file 'src/maasserver/models/tests/test_template.py'
104--- src/maasserver/models/tests/test_template.py 2016-03-25 13:52:27 +0000
105+++ src/maasserver/models/tests/test_template.py 2016-10-17 06:51:39 +0000
106@@ -2,15 +2,13 @@
107 # GNU Affero General Public License version 3 (see the file LICENSE).
108
109 """Tests for the Template model."""
110+
111+__all__ = []
112+
113 from maasserver.models import VersionedTextFile
114-from testtools.matchers import Equals
115-
116-
117-__all__ = []
118-
119-
120 from maasserver.models.template import Template
121 from maasserver.testing.testcase import MAASServerTestCase
122+from testtools.matchers import Equals
123
124
125 class TemplateTest(MAASServerTestCase):
126
127=== modified file 'src/maasserver/rpc/rackcontrollers.py'
128--- src/maasserver/rpc/rackcontrollers.py 2016-10-12 15:26:17 +0000
129+++ src/maasserver/rpc/rackcontrollers.py 2016-10-17 06:51:39 +0000
130@@ -2,8 +2,6 @@
131 # GNU Affero General Public License version 3 (see the file LICENSE).
132
133 """RPC helpers relating to rack controllers."""
134-from provisioningserver.rpc.exceptions import NoSuchNode
135-
136
137 __all__ = [
138 "handle_upgrade",
139@@ -16,9 +14,9 @@
140
141 from django.db.models import Q
142 from maasserver import (
143+ is_master_process,
144 locks,
145 worker_user,
146- is_master_process,
147 )
148 from maasserver.enum import NODE_TYPE
149 from maasserver.models import (
150@@ -36,6 +34,7 @@
151 with_connection,
152 )
153 from provisioningserver.logger import get_maas_logger
154+from provisioningserver.rpc.exceptions import NoSuchNode
155 from provisioningserver.utils import typed
156 from provisioningserver.utils.twisted import synchronous
157
158
159=== modified file 'src/maasserver/urls_api.py'
160--- src/maasserver/urls_api.py 2016-09-02 16:20:15 +0000
161+++ src/maasserver/urls_api.py 2016-10-17 06:51:39 +0000
162@@ -2,11 +2,6 @@
163 # GNU Affero General Public License version 3 (see the file LICENSE).
164
165 """URL API routing configuration."""
166-from maasserver.api.discoveries import (
167- DiscoveriesHandler,
168- DiscoveryHandler,
169-)
170-
171
172 __all__ = []
173
174@@ -53,6 +48,10 @@
175 DHCPSnippetHandler,
176 DHCPSnippetsHandler,
177 )
178+from maasserver.api.discoveries import (
179+ DiscoveriesHandler,
180+ DiscoveryHandler,
181+)
182 from maasserver.api.dnsresourcerecords import (
183 DNSResourceRecordHandler,
184 DNSResourceRecordsHandler,
185
186=== modified file 'src/maasserver/utils/dns.py'
187--- src/maasserver/utils/dns.py 2016-03-28 13:54:47 +0000
188+++ src/maasserver/utils/dns.py 2016-10-17 06:51:39 +0000
189@@ -3,6 +3,10 @@
190
191 """DNS-related utilities."""
192
193+__all__ = [
194+ 'validate_hostname',
195+ ]
196+
197 import re
198
199 from django.core.exceptions import ValidationError
200@@ -12,11 +16,6 @@
201 )
202
203
204-__all__ = [
205- 'validate_hostname',
206- ]
207-
208-
209 def validate_domain_name(name):
210 """Validator for domain names.
211
212
213=== modified file 'src/maasserver/utils/tests/test_dns.py'
214--- src/maasserver/utils/tests/test_dns.py 2016-03-28 13:54:47 +0000
215+++ src/maasserver/utils/tests/test_dns.py 2016-10-17 06:51:39 +0000
216@@ -3,6 +3,8 @@
217
218 """Test DNS utilities."""
219
220+__all__ = []
221+
222 from math import pow
223
224 from django.core.exceptions import ValidationError
225@@ -11,19 +13,14 @@
226 validate_domain_name,
227 validate_hostname,
228 )
229+from maastesting.factory import factory
230+from maastesting.testcase import MAASTestCase
231 from testtools.matchers import (
232 Equals,
233 HasLength,
234 )
235
236
237-__all__ = []
238-
239-
240-from maastesting.factory import factory
241-from maastesting.testcase import MAASTestCase
242-
243-
244 class TestHostnameValidator(MAASTestCase):
245 """Tests for the validation of hostnames.
246
247
248=== modified file 'src/provisioningserver/templates/commissioning-user-data/snippets/maas_api_helper.py'
249--- src/provisioningserver/templates/commissioning-user-data/snippets/maas_api_helper.py 2015-12-15 16:55:41 +0000
250+++ src/provisioningserver/templates/commissioning-user-data/snippets/maas_api_helper.py 2016-10-17 06:51:39 +0000
251@@ -1,3 +1,8 @@
252+__all__ = [
253+ 'geturl',
254+ 'read_config',
255+ ]
256+
257 from email.utils import parsedate
258 import sys
259 import time
260@@ -9,12 +14,6 @@
261 import yaml
262
263
264-__all__ = [
265- 'geturl',
266- 'read_config',
267- ]
268-
269-
270 def read_config(url, creds):
271 """Read cloud-init config from given `url` into `creds` dict.
272
273
274=== modified file 'src/provisioningserver/utils/isc.py'
275--- src/provisioningserver/utils/isc.py 2016-03-28 13:54:47 +0000
276+++ src/provisioningserver/utils/isc.py 2016-10-17 06:51:39 +0000
277@@ -28,9 +28,6 @@
278 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
279 # POSSIBILITY OF SUCH DAMAGE.
280
281-from collections import OrderedDict
282-
283-
284 __all__ = [
285 'ISCParseException',
286 'make_isc_string',
287@@ -38,6 +35,7 @@
288 'read_isc_file',
289 ]
290
291+from collections import OrderedDict
292 import copy
293
294
295
296=== added file 'utilities/find-early-imports'
297--- utilities/find-early-imports 1970-01-01 00:00:00 +0000
298+++ utilities/find-early-imports 2016-10-17 06:51:39 +0000
299@@ -0,0 +1,54 @@
300+#!/usr/bin/env python3.5
301+# -*- mode: python -*-
302+# Copyright 2016 Canonical Ltd. This software is licensed under the
303+# GNU Affero General Public License version 3 (see the file LICENSE).
304+
305+"""Find imports that occur before `__all__`, if the latter is present.
306+
307+Exits non-zero if any are found.
308+"""
309+
310+import argparse
311+import sys
312+import tokenize
313+
314+
315+def find_early_imports(filepath):
316+ has_early_import = has_all_declaration = False
317+ with open(filepath, "rb") as fd:
318+ for token in tokenize.tokenize(fd.readline):
319+ if token.type == tokenize.NAME:
320+ if token.string in {"import", "from"}:
321+ if not has_all_declaration:
322+ has_early_import = True
323+ elif token.string == "__all__":
324+ has_all_declaration = True
325+ if not has_early_import:
326+ break # Short-circuit.
327+ else:
328+ pass # Keep looking.
329+ return has_all_declaration and has_early_import
330+
331+
332+argument_parser = argparse.ArgumentParser(
333+ formatter_class=argparse.RawDescriptionHelpFormatter,
334+ description=__doc__)
335+argument_parser.add_argument(
336+ "filenames", nargs="+", metavar="FILENAME")
337+
338+
339+if __name__ == '__main__':
340+ options = argument_parser.parse_args()
341+
342+ has_early_imports = False
343+ for filename in options.filenames:
344+ if find_early_imports(filename):
345+ if has_early_imports:
346+ print(filename)
347+ else:
348+ has_early_imports = True
349+ print("Early imports found:", file=sys.stderr)
350+ print(filename)
351+
352+ raise SystemExit(
353+ 1 if has_early_imports else 0)