Merge ~cjwatson/launchpadlib:remove-py2 into launchpadlib:main
- Git
- lp:~cjwatson/launchpadlib
- remove-py2
- Merge into main
Proposed by
Colin Watson
Status: | Needs review |
---|---|
Proposed branch: | ~cjwatson/launchpadlib:remove-py2 |
Merge into: | launchpadlib:main |
Diff against target: |
1032 lines (+125/-199) 26 files modified
.pre-commit-config.yaml (+4/-5) NEWS.rst (+4/-0) contrib/_pythonpath.py (+0/-2) contrib/close-my-bugs.py (+7/-7) contrib/commercial-member-api.py (+10/-10) contrib/delete_bugtasks.py (+4/-6) contrib/lp-bug-ifier.py (+3/-3) contrib/lpapi.py (+11/-12) contrib/nopriv-api.py (+6/-6) contrib/sample-person-api.py (+6/-6) contrib/upload_release_tarball.py (+12/-12) pyproject.toml (+1/-1) setup.py (+2/-3) src/launchpadlib/apps.py (+1/-1) src/launchpadlib/bin/launchpad-request-token (+1/-5) src/launchpadlib/credentials.py (+18/-45) src/launchpadlib/docs/conf.py (+7/-9) src/launchpadlib/launchpad.py (+4/-11) src/launchpadlib/testing/helpers.py (+2/-4) src/launchpadlib/testing/launchpad.py (+6/-16) src/launchpadlib/testing/tests/test_launchpad.py (+2/-2) src/launchpadlib/tests/test_credential_store.py (+2/-6) src/launchpadlib/tests/test_http.py (+2/-7) src/launchpadlib/tests/test_launchpad.py (+3/-9) src/launchpadlib/uris.py (+2/-5) tox.ini (+5/-6) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guruprasad | Approve | ||
Review via email: mp+461678@code.launchpad.net |
Commit message
Remove support for Python 2
Description of the change
I noticed that a number of the scripts in `contrib/` were Python-2-only, so I did a basic untested port of those while I was here.
I also took advantage of the opportunity to simplify coverage testing.
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
Revision history for this message
Matěj Cepl (mcepl) wrote : | # |
Two more dependencies, which can be removed.
Revision history for this message
Jürgen Gmach (jugmac00) wrote : | # |
I'd prefer to keep using pytest.
- 49f266b... by Colin Watson
-
Remove mock dependency
Revision history for this message
Colin Watson (cjwatson) : | # |
Unmerged commits
- 49f266b... by Colin Watson
-
Remove mock dependency
-
lint:0 (build) tests:0 (build) 1 → 2 of 2 results First • Previous • Next • Last - bcd20d9... by Colin Watson
-
Simplify coverage testing
We no longer need the more complex arrangements after dropping Python 2
support. -
lint:0 (build) tests:0 (build) 1 → 2 of 2 results First • Previous • Next • Last - 0b5b426... by Colin Watson
-
Apply pyupgrade --py3-plus
- f22d37e... by Colin Watson
-
Remove support for Python 2
I noticed that a number of the scripts in `contrib/` were Python-2-only,
so I did a basic untested port of those while I was here.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml | |||
2 | index ec0cbbd..e9cff43 100644 | |||
3 | --- a/.pre-commit-config.yaml | |||
4 | +++ b/.pre-commit-config.yaml | |||
5 | @@ -12,19 +12,18 @@ repos: | |||
6 | 12 | - id: check-yaml | 12 | - id: check-yaml |
7 | 13 | - id: debug-statements | 13 | - id: debug-statements |
8 | 14 | - repo: https://github.com/PyCQA/flake8 | 14 | - repo: https://github.com/PyCQA/flake8 |
10 | 15 | rev: 5.0.4 | 15 | rev: 7.0.0 |
11 | 16 | hooks: | 16 | hooks: |
12 | 17 | - id: flake8 | 17 | - id: flake8 |
13 | 18 | - repo: https://github.com/asottile/pyupgrade | 18 | - repo: https://github.com/asottile/pyupgrade |
15 | 19 | rev: v2.38.4 # v3 drops Python 2 support | 19 | rev: v3.15.1 |
16 | 20 | hooks: | 20 | hooks: |
17 | 21 | - id: pyupgrade | 21 | - id: pyupgrade |
19 | 22 | args: [--keep-percent-format] | 22 | args: [--keep-percent-format, --py3-plus] |
20 | 23 | - repo: https://github.com/psf/black | 23 | - repo: https://github.com/psf/black |
22 | 24 | rev: 21.12b0 # v22 drops Python 2 support | 24 | rev: 24.2.0 |
23 | 25 | hooks: | 25 | hooks: |
24 | 26 | - id: black | 26 | - id: black |
25 | 27 | additional_dependencies: ['click<8.1'] | ||
26 | 28 | - repo: https://github.com/get-woke/woke | 27 | - repo: https://github.com/get-woke/woke |
27 | 29 | rev: v0.19.0 | 28 | rev: v0.19.0 |
28 | 30 | hooks: | 29 | hooks: |
29 | diff --git a/NEWS.rst b/NEWS.rst | |||
30 | index 46c712a..aec5c42 100644 | |||
31 | --- a/NEWS.rst | |||
32 | +++ b/NEWS.rst | |||
33 | @@ -2,6 +2,10 @@ | |||
34 | 2 | NEWS for launchpadlib | 2 | NEWS for launchpadlib |
35 | 3 | ===================== | 3 | ===================== |
36 | 4 | 4 | ||
37 | 5 | 2.0.0 | ||
38 | 6 | ===== | ||
39 | 7 | - Remove support for Python 2. | ||
40 | 8 | |||
41 | 5 | 1.11.0 (2023-01-09) | 9 | 1.11.0 (2023-01-09) |
42 | 6 | =================== | 10 | =================== |
43 | 7 | - Move the ``keyring`` dependency to a new ``keyring`` extra. | 11 | - Move the ``keyring`` dependency to a new ``keyring`` extra. |
44 | diff --git a/contrib/_pythonpath.py b/contrib/_pythonpath.py | |||
45 | index 6bf7934..04b8147 100644 | |||
46 | --- a/contrib/_pythonpath.py | |||
47 | +++ b/contrib/_pythonpath.py | |||
48 | @@ -1,5 +1,3 @@ | |||
49 | 1 | __metaclass__ = type | ||
50 | 2 | |||
51 | 3 | import sys | 1 | import sys |
52 | 4 | import os | 2 | import os |
53 | 5 | 3 | ||
54 | diff --git a/contrib/close-my-bugs.py b/contrib/close-my-bugs.py | |||
55 | index ca925a2..bdf16c7 100755 | |||
56 | --- a/contrib/close-my-bugs.py | |||
57 | +++ b/contrib/close-my-bugs.py | |||
58 | @@ -1,4 +1,4 @@ | |||
60 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python3 |
61 | 2 | 2 | ||
62 | 3 | # Copyright (C) 2009-2013 Canonical Ltd. | 3 | # Copyright (C) 2009-2013 Canonical Ltd. |
63 | 4 | # | 4 | # |
64 | @@ -92,22 +92,22 @@ def main(args): | |||
65 | 92 | **extra_kwargs)] | 92 | **extra_kwargs)] |
66 | 93 | 93 | ||
67 | 94 | for task in committed_tasks: | 94 | for task in committed_tasks: |
69 | 95 | print "Bug #%s: %s" % (task.bug.id, task.bug.title) | 95 | print("Bug #%s: %s" % (task.bug.id, task.bug.title)) |
70 | 96 | 96 | ||
71 | 97 | if options.dry_run: | 97 | if options.dry_run: |
73 | 98 | print '\n*** Nothing changed. Re-run without --dry-run/-n to commit.' | 98 | print('\n*** Nothing changed. Re-run without --dry-run/-n to commit.') |
74 | 99 | else: | 99 | else: |
75 | 100 | if not options.force: | 100 | if not options.force: |
77 | 101 | answer = raw_input("Mark these bugs as Fix Released? [y/N]") | 101 | answer = input("Mark these bugs as Fix Released? [y/N]") |
78 | 102 | if answer in ("n", "N") or not answer: | 102 | if answer in ("n", "N") or not answer: |
80 | 103 | print "Ok, leaving them alone." | 103 | print("Ok, leaving them alone.") |
81 | 104 | return | 104 | return |
82 | 105 | 105 | ||
83 | 106 | for task in committed_tasks: | 106 | for task in committed_tasks: |
85 | 107 | print "Releasing %s" % task.bug.id | 107 | print("Releasing %s" % task.bug.id) |
86 | 108 | task.status = FIX_RELEASED | 108 | task.status = FIX_RELEASED |
87 | 109 | task.lp_save() | 109 | task.lp_save() |
89 | 110 | print "Done." | 110 | print("Done.") |
90 | 111 | 111 | ||
91 | 112 | return 0 | 112 | return 0 |
92 | 113 | 113 | ||
93 | diff --git a/contrib/commercial-member-api.py b/contrib/commercial-member-api.py | |||
94 | index 1e84b25..f5c0031 100755 | |||
95 | --- a/contrib/commercial-member-api.py | |||
96 | +++ b/contrib/commercial-member-api.py | |||
97 | @@ -1,4 +1,4 @@ | |||
99 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python3 |
100 | 2 | # -*-doctest-*- | 2 | # -*-doctest-*- |
101 | 3 | 3 | ||
102 | 4 | """ | 4 | """ |
103 | @@ -6,16 +6,16 @@ | |||
104 | 6 | >>> lp = lpapi.lp_factory('dev') | 6 | >>> lp = lpapi.lp_factory('dev') |
105 | 7 | >>> bzr = lp.projects['bzr'] | 7 | >>> bzr = lp.projects['bzr'] |
106 | 8 | >>> bzr.reviewer_whiteboard = "Check on licensing" | 8 | >>> bzr.reviewer_whiteboard = "Check on licensing" |
108 | 9 | >>> print bzr.reviewer_whiteboard | 9 | >>> print(bzr.reviewer_whiteboard) |
109 | 10 | Check on licensing | 10 | Check on licensing |
110 | 11 | >>> bzr.lp_save() | 11 | >>> bzr.lp_save() |
112 | 12 | >>> print bzr.reviewer_whiteboard | 12 | >>> print(bzr.reviewer_whiteboard) |
113 | 13 | Check on licensing | 13 | Check on licensing |
114 | 14 | 14 | ||
115 | 15 | >>> from operator import attrgetter | 15 | >>> from operator import attrgetter |
116 | 16 | >>> def print_projs(projs): | 16 | >>> def print_projs(projs): |
117 | 17 | ... for p in sorted(projs, key=attrgetter('name')): | 17 | ... for p in sorted(projs, key=attrgetter('name')): |
119 | 18 | ... print p.name | 18 | ... print(p.name) |
120 | 19 | 19 | ||
121 | 20 | >>> inactive = lp.projects.licensing_search(active=False) | 20 | >>> inactive = lp.projects.licensing_search(active=False) |
122 | 21 | >>> print_projs(inactive) | 21 | >>> print_projs(inactive) |
123 | @@ -133,11 +133,11 @@ | |||
124 | 133 | launchpad | 133 | launchpad |
125 | 134 | 134 | ||
126 | 135 | >>> l = projs[2] | 135 | >>> l = projs[2] |
128 | 136 | >>> print l.name | 136 | >>> print(l.name) |
129 | 137 | launchpad | 137 | launchpad |
131 | 138 | >>> print l.description | 138 | >>> print(l.description) |
132 | 139 | Launchpad's design is inspired by the Description of a Project (DOAP) framework by Edd Dumbill, with extensions for actual releases of products. | 139 | Launchpad's design is inspired by the Description of a Project (DOAP) framework by Edd Dumbill, with extensions for actual releases of products. |
134 | 140 | >>> print l.summary | 140 | >>> print(l.summary) |
135 | 141 | Launchpad is a catalogue of libre software projects and products. Projects registered in the Launchpad are linked to their translations in Rosetta, their bugs in Malone, their RCS imports in Bazaar, and their packages in Soyuz. | 141 | Launchpad is a catalogue of libre software projects and products. Projects registered in the Launchpad are linked to their translations in Rosetta, their bugs in Malone, their RCS imports in Bazaar, and their packages in Soyuz. |
136 | 142 | 142 | ||
137 | 143 | """ | 143 | """ |
138 | @@ -151,9 +151,9 @@ if __name__ == '__main__': | |||
139 | 151 | pass | 151 | pass |
140 | 152 | 152 | ||
141 | 153 | # Create correct credentials. | 153 | # Create correct credentials. |
145 | 154 | print "Login as 'commercial-member@canonical.com' in your browser." | 154 | print("Login as 'commercial-member@canonical.com' in your browser.") |
146 | 155 | print "Press <Enter> when done." | 155 | print("Press <Enter> when done.") |
147 | 156 | raw_input() | 156 | input() |
148 | 157 | 157 | ||
149 | 158 | # Import _pythonpath and the lpapi module. _pythonpath must | 158 | # Import _pythonpath and the lpapi module. _pythonpath must |
150 | 159 | # precede the import of lpapi as it redefines sys.path. | 159 | # precede the import of lpapi as it redefines sys.path. |
151 | diff --git a/contrib/delete_bugtasks.py b/contrib/delete_bugtasks.py | |||
152 | index a9eefe5..490d392 100755 | |||
153 | --- a/contrib/delete_bugtasks.py | |||
154 | +++ b/contrib/delete_bugtasks.py | |||
155 | @@ -1,6 +1,4 @@ | |||
159 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python3 |
157 | 2 | |||
158 | 3 | __metaclass__ = type | ||
160 | 4 | 2 | ||
161 | 5 | from collections import defaultdict | 3 | from collections import defaultdict |
162 | 6 | from optparse import OptionParser | 4 | from optparse import OptionParser |
163 | @@ -44,7 +42,7 @@ class SharedBugsFixer: | |||
164 | 44 | def log(self, message, leader=' ', error=False): | 42 | def log(self, message, leader=' ', error=False): |
165 | 45 | """Report to STDOUT.""" | 43 | """Report to STDOUT.""" |
166 | 46 | if error or self.verbose: | 44 | if error or self.verbose: |
168 | 47 | print '%s%s' % (leader, message) | 45 | print('%s%s' % (leader, message)) |
169 | 48 | 46 | ||
170 | 49 | def _get_target_type(self, bug_target): | 47 | def _get_target_type(self, bug_target): |
171 | 50 | """Return the bug target entity type.""" | 48 | """Return the bug target entity type.""" |
172 | @@ -105,9 +103,9 @@ class SharedBugsFixer: | |||
173 | 105 | self.log("! bug affects 1 pillar now.") | 103 | self.log("! bug affects 1 pillar now.") |
174 | 106 | except UnsupportedSeriesSplit: | 104 | except UnsupportedSeriesSplit: |
175 | 107 | self.log("! This script cannot split bugs that affect series.") | 105 | self.log("! This script cannot split bugs that affect series.") |
177 | 108 | except (KeyError, Unauthorized), e: | 106 | except (KeyError, Unauthorized): |
178 | 109 | self.log("! bug %s is owned by someone else" % pillar_name) | 107 | self.log("! bug %s is owned by someone else" % pillar_name) |
180 | 110 | except Exception, e: | 108 | except Exception as e: |
181 | 111 | # Something went very wrong. | 109 | # Something went very wrong. |
182 | 112 | self.log("!! %s" % str(e), error=True) | 110 | self.log("!! %s" % str(e), error=True) |
183 | 113 | 111 | ||
184 | diff --git a/contrib/lp-bug-ifier.py b/contrib/lp-bug-ifier.py | |||
185 | index d911c5c..5197a70 100755 | |||
186 | --- a/contrib/lp-bug-ifier.py | |||
187 | +++ b/contrib/lp-bug-ifier.py | |||
188 | @@ -1,4 +1,4 @@ | |||
190 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python3 |
191 | 2 | 2 | ||
192 | 3 | """ | 3 | """ |
193 | 4 | Scan stdin for text matching bug references and insert the bug title into the | 4 | Scan stdin for text matching bug references and insert the bug title into the |
194 | @@ -25,7 +25,7 @@ from launchpadlib.launchpad import Launchpad | |||
195 | 25 | 25 | ||
196 | 26 | 26 | ||
197 | 27 | bug_re = re.compile(r"[Bb]ug(?:\s|<br\s*/>)*(?:\#|report|number\.?|num\.?|no\.?)?" | 27 | bug_re = re.compile(r"[Bb]ug(?:\s|<br\s*/>)*(?:\#|report|number\.?|num\.?|no\.?)?" |
199 | 28 | "(?:\s|<br\s*/>)*(?P<bugnum>\d+)") | 28 | r"(?:\s|<br\s*/>)*(?P<bugnum>\d+)") |
200 | 29 | 29 | ||
201 | 30 | launchpad = Launchpad.login_with(os.path.basename(sys.argv[0]), 'production') | 30 | launchpad = Launchpad.login_with(os.path.basename(sys.argv[0]), 'production') |
202 | 31 | bugs = launchpad.bugs | 31 | bugs = launchpad.bugs |
203 | @@ -44,7 +44,7 @@ def add_summary_to_bug(match): | |||
204 | 44 | 44 | ||
205 | 45 | def main(): | 45 | def main(): |
206 | 46 | text = sys.stdin.read() | 46 | text = sys.stdin.read() |
208 | 47 | print bug_re.sub(add_summary_to_bug, text) | 47 | print(bug_re.sub(add_summary_to_bug, text)) |
209 | 48 | 48 | ||
210 | 49 | if __name__ == '__main__': | 49 | if __name__ == '__main__': |
211 | 50 | main() | 50 | main() |
212 | diff --git a/contrib/lpapi.py b/contrib/lpapi.py | |||
213 | index dfd1633..2c8730f 100644 | |||
214 | --- a/contrib/lpapi.py | |||
215 | +++ b/contrib/lpapi.py | |||
216 | @@ -1,8 +1,7 @@ | |||
218 | 1 | #!/usr/bin/python2.4 | 1 | #!/usr/bin/python3 |
219 | 2 | import os | 2 | import os |
220 | 3 | import sys | 3 | import sys |
223 | 4 | from urlparse import urljoin | 4 | from urllib.parse import urljoin |
222 | 5 | import commands | ||
224 | 6 | 5 | ||
225 | 7 | try: | 6 | try: |
226 | 8 | from launchpadlib.launchpad import ( | 7 | from launchpadlib.launchpad import ( |
227 | @@ -11,8 +10,8 @@ try: | |||
228 | 11 | from launchpadlib.errors import * | 10 | from launchpadlib.errors import * |
229 | 12 | import launchpadlib | 11 | import launchpadlib |
230 | 13 | except ImportError: | 12 | except ImportError: |
233 | 14 | print >> sys.stderr, "Usage:" | 13 | print("Usage:", file=sys.stderr) |
234 | 15 | print >> sys.stderr, " PYTHONPATH=somebranch/lib %s" % sys.argv[0] | 14 | print(" PYTHONPATH=somebranch/lib %s" % sys.argv[0], file=sys.stderr) |
235 | 16 | raise | 15 | raise |
236 | 17 | 16 | ||
237 | 18 | 17 | ||
238 | @@ -42,12 +41,12 @@ class LPSystem: | |||
239 | 42 | self.auth_file = os.path.join(home, self.auth_file_name) | 41 | self.auth_file = os.path.join(home, self.auth_file_name) |
240 | 43 | self.credentials = Credentials() | 42 | self.credentials = Credentials() |
241 | 44 | self.credentials.load(open(self.auth_file)) | 43 | self.credentials.load(open(self.auth_file)) |
243 | 45 | print >> sys.stderr, "Loading credentials..." | 44 | print("Loading credentials...", file=sys.stderr) |
244 | 46 | try: | 45 | try: |
245 | 47 | self.launchpad = Launchpad(self.credentials, self.endpoint, | 46 | self.launchpad = Launchpad(self.credentials, self.endpoint, |
246 | 48 | cache=cache_dir) | 47 | cache=cache_dir) |
247 | 49 | except launchpadlib.errors.HTTPError: | 48 | except launchpadlib.errors.HTTPError: |
249 | 50 | raise InvalidCredentials, ( | 49 | raise InvalidCredentials( |
250 | 51 | "Please remove %s and rerun %s to authenticate." % ( | 50 | "Please remove %s and rerun %s to authenticate." % ( |
251 | 52 | self.auth_file, sys.argv[0])) | 51 | self.auth_file, sys.argv[0])) |
252 | 53 | except IOError: | 52 | except IOError: |
253 | @@ -58,9 +57,9 @@ class LPSystem: | |||
254 | 58 | self.endpoint, | 57 | self.endpoint, |
255 | 59 | cache=cache_dir) | 58 | cache=cache_dir) |
256 | 60 | self.launchpad.credentials.save(open(self.auth_file, "w")) | 59 | self.launchpad.credentials.save(open(self.auth_file, "w")) |
260 | 61 | print >> sys.stderr, "Credentials saved" | 60 | print("Credentials saved", file=sys.stderr) |
261 | 62 | except launchpadlib.errors.HTTPError, err: | 61 | except launchpadlib.errors.HTTPError as err: |
262 | 63 | print >> sys.stderr, err.content | 62 | print(err.content, file=sys.stderr) |
263 | 64 | raise | 63 | raise |
264 | 65 | 64 | ||
265 | 66 | @property | 65 | @property |
266 | @@ -102,6 +101,6 @@ def lp_factory(system_name, app_name='just_testing'): | |||
267 | 102 | lpinstance = systems[system_name] | 101 | lpinstance = systems[system_name] |
268 | 103 | return lpinstance(app_name).launchpad | 102 | return lpinstance(app_name).launchpad |
269 | 104 | except KeyError: | 103 | except KeyError: |
272 | 105 | print >> sys.stderr, "System '%s' not supported." % system_name | 104 | print("System '%s' not supported." % system_name, file=sys.stderr) |
273 | 106 | print >> sys.stderr, "Use one of: ", systems.keys() | 105 | print("Use one of: ", systems.keys(), file=sys.stderr) |
274 | 107 | return None | 106 | return None |
275 | diff --git a/contrib/nopriv-api.py b/contrib/nopriv-api.py | |||
276 | index abe2b28..66cadc6 100755 | |||
277 | --- a/contrib/nopriv-api.py | |||
278 | +++ b/contrib/nopriv-api.py | |||
279 | @@ -1,4 +1,4 @@ | |||
281 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python3 |
282 | 2 | # -*-doctest-*- | 2 | # -*-doctest-*- |
283 | 3 | 3 | ||
284 | 4 | """ | 4 | """ |
285 | @@ -6,10 +6,10 @@ | |||
286 | 6 | >>> lp = lpapi.lp_factory('dev') | 6 | >>> lp = lpapi.lp_factory('dev') |
287 | 7 | 7 | ||
288 | 8 | >>> bzr = lp.projects['bzr'] | 8 | >>> bzr = lp.projects['bzr'] |
290 | 9 | >>> print bzr.reviewer_whiteboard | 9 | >>> print(bzr.reviewer_whiteboard) |
291 | 10 | tag:launchpad.net:2008:redacted | 10 | tag:launchpad.net:2008:redacted |
292 | 11 | >>> bzr.reviewer_whiteboard = "Check on licensing" | 11 | >>> bzr.reviewer_whiteboard = "Check on licensing" |
294 | 12 | >>> print bzr.reviewer_whiteboard | 12 | >>> print(bzr.reviewer_whiteboard) |
295 | 13 | Check on licensing | 13 | Check on licensing |
296 | 14 | >>> bzr.lp_save() | 14 | >>> bzr.lp_save() |
297 | 15 | ... | 15 | ... |
298 | @@ -78,9 +78,9 @@ if __name__ == '__main__': | |||
299 | 78 | pass | 78 | pass |
300 | 79 | 79 | ||
301 | 80 | # Create correct credentials. | 80 | # Create correct credentials. |
305 | 81 | print "Login as 'no-priv@canonical.com' in your browser." | 81 | print("Login as 'no-priv@canonical.com' in your browser.") |
306 | 82 | print "Press <Enter> when done." | 82 | print("Press <Enter> when done.") |
307 | 83 | raw_input() | 83 | input() |
308 | 84 | 84 | ||
309 | 85 | # Import _pythonpath and the lpapi module. _pythonpath must | 85 | # Import _pythonpath and the lpapi module. _pythonpath must |
310 | 86 | # precede the import of lpapi as it redefines sys.path. | 86 | # precede the import of lpapi as it redefines sys.path. |
311 | diff --git a/contrib/sample-person-api.py b/contrib/sample-person-api.py | |||
312 | index a7b3488..51118b4 100755 | |||
313 | --- a/contrib/sample-person-api.py | |||
314 | +++ b/contrib/sample-person-api.py | |||
315 | @@ -1,4 +1,4 @@ | |||
317 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python3 |
318 | 2 | # -*-doctest-*- | 2 | # -*-doctest-*- |
319 | 3 | 3 | ||
320 | 4 | """ | 4 | """ |
321 | @@ -6,10 +6,10 @@ | |||
322 | 6 | >>> lp = lpapi.lp_factory('dev') | 6 | >>> lp = lpapi.lp_factory('dev') |
323 | 7 | 7 | ||
324 | 8 | >>> bzr = lp.projects['bzr'] | 8 | >>> bzr = lp.projects['bzr'] |
326 | 9 | >>> print bzr.reviewer_whiteboard | 9 | >>> print(bzr.reviewer_whiteboard) |
327 | 10 | tag:launchpad.net:2008:redacted | 10 | tag:launchpad.net:2008:redacted |
328 | 11 | >>> bzr.reviewer_whiteboard = "Check on licensing" | 11 | >>> bzr.reviewer_whiteboard = "Check on licensing" |
330 | 12 | >>> print bzr.reviewer_whiteboard | 12 | >>> print(bzr.reviewer_whiteboard) |
331 | 13 | Check on licensing | 13 | Check on licensing |
332 | 14 | >>> bzr.lp_save() | 14 | >>> bzr.lp_save() |
333 | 15 | ... | 15 | ... |
334 | @@ -78,9 +78,9 @@ if __name__ == '__main__': | |||
335 | 78 | pass | 78 | pass |
336 | 79 | 79 | ||
337 | 80 | # Create correct credentials. | 80 | # Create correct credentials. |
341 | 81 | print "Login as 'test@canonical.com' in your browser." | 81 | print("Login as 'test@canonical.com' in your browser.") |
342 | 82 | print "Press <Enter> when done." | 82 | print("Press <Enter> when done.") |
343 | 83 | raw_input() | 83 | input() |
344 | 84 | 84 | ||
345 | 85 | # Import _pythonpath and the lpapi module. _pythonpath must | 85 | # Import _pythonpath and the lpapi module. _pythonpath must |
346 | 86 | # precede the import of lpapi as it redefines sys.path. | 86 | # precede the import of lpapi as it redefines sys.path. |
347 | diff --git a/contrib/upload_release_tarball.py b/contrib/upload_release_tarball.py | |||
348 | index 335987a..7a51370 100755 | |||
349 | --- a/contrib/upload_release_tarball.py | |||
350 | +++ b/contrib/upload_release_tarball.py | |||
351 | @@ -1,4 +1,4 @@ | |||
353 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python3 |
354 | 2 | # | 2 | # |
355 | 3 | # This script uploads a tarball as a file for a (possibly new) | 3 | # This script uploads a tarball as a file for a (possibly new) |
356 | 4 | # release. It takes these command-line arguments: | 4 | # release. It takes these command-line arguments: |
357 | @@ -61,14 +61,14 @@ if os.path.exists(signature_path): | |||
358 | 61 | else: | 61 | else: |
359 | 62 | # There is no signature. | 62 | # There is no signature. |
360 | 63 | if options.force: | 63 | if options.force: |
363 | 64 | print ('WARNING: Signature file "%s" is not present. Continuing ' | 64 | print('WARNING: Signature file "%s" is not present. Continuing ' |
364 | 65 | 'without it.' % signature_path) | 65 | 'without it.' % signature_path) |
365 | 66 | signature_name = None | 66 | signature_name = None |
366 | 67 | signature = None | 67 | signature = None |
367 | 68 | else: | 68 | else: |
371 | 69 | print 'ERROR: Signature file "%s" is not present.' % signature_path | 69 | print('ERROR: Signature file "%s" is not present.' % signature_path) |
372 | 70 | print 'Run "gpg --armor --sign --detach-sig" on the tarball.' | 70 | print('Run "gpg --armor --sign --detach-sig" on the tarball.') |
373 | 71 | print 'Or re-run this script with the --force option.' | 71 | print('Or re-run this script with the --force option.') |
374 | 72 | sys.exit(-1) | 72 | sys.exit(-1) |
375 | 73 | 73 | ||
376 | 74 | # Now we interact with Launchpad. | 74 | # Now we interact with Launchpad. |
377 | @@ -92,8 +92,8 @@ series = matching_series[0] | |||
378 | 92 | matching_milestones = [milestone for milestone in series.active_milestones | 92 | matching_milestones = [milestone for milestone in series.active_milestones |
379 | 93 | if milestone.name == version_name] | 93 | if milestone.name == version_name] |
380 | 94 | if len(matching_milestones) == 0: | 94 | if len(matching_milestones) == 0: |
383 | 95 | print 'No milestone "%s" for %s/%s. Creating it.' % ( | 95 | print('No milestone "%s" for %s/%s. Creating it.' % ( |
384 | 96 | version_name, project.name, series.name) | 96 | version_name, project.name, series.name)) |
385 | 97 | milestone = series.newMilestone(name=version_name) | 97 | milestone = series.newMilestone(name=version_name) |
386 | 98 | else: | 98 | else: |
387 | 99 | milestone = matching_milestones[0] | 99 | milestone = matching_milestones[0] |
388 | @@ -106,8 +106,8 @@ if len(matching_releases) == 0: | |||
389 | 106 | # | 106 | # |
390 | 107 | # The changelog and release notes could go into this operation | 107 | # The changelog and release notes could go into this operation |
391 | 108 | # invocation. | 108 | # invocation. |
394 | 109 | print "No release for %s/%s/%s. Creating it." % ( | 109 | print("No release for %s/%s/%s. Creating it." % ( |
395 | 110 | project.name, series.name, version_name) | 110 | project.name, series.name, version_name)) |
396 | 111 | release = milestone.createProductRelease( | 111 | release = milestone.createProductRelease( |
397 | 112 | date_released=datetime.now(pytz.UTC)) | 112 | date_released=datetime.now(pytz.UTC)) |
398 | 113 | else: | 113 | else: |
399 | @@ -124,5 +124,5 @@ if signature is not None: | |||
400 | 124 | result = release.add_file(**kwargs) | 124 | result = release.add_file(**kwargs) |
401 | 125 | 125 | ||
402 | 126 | # We know this succeeded because add_file didn't raise an exception. | 126 | # We know this succeeded because add_file didn't raise an exception. |
405 | 127 | print "Success!" | 127 | print("Success!") |
406 | 128 | print result.self_link | 128 | print(result.self_link) |
407 | diff --git a/pyproject.toml b/pyproject.toml | |||
408 | index 486bbe6..1f331da 100644 | |||
409 | --- a/pyproject.toml | |||
410 | +++ b/pyproject.toml | |||
411 | @@ -1,3 +1,3 @@ | |||
412 | 1 | [tool.black] | 1 | [tool.black] |
413 | 2 | line-length = 79 | 2 | line-length = 79 |
415 | 3 | target-version = ['py27'] | 3 | target-version = ['py35'] |
416 | diff --git a/setup.py b/setup.py | |||
417 | index 0f4d391..38bf7b5 100755 | |||
418 | --- a/setup.py | |||
419 | +++ b/setup.py | |||
420 | @@ -1,4 +1,4 @@ | |||
422 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python3 |
423 | 2 | 2 | ||
424 | 3 | # Copyright 2008-2022 Canonical Ltd. | 3 | # Copyright 2008-2022 Canonical Ltd. |
425 | 4 | # | 4 | # |
426 | @@ -46,7 +46,6 @@ install_requires = [ | |||
427 | 46 | 'importlib-metadata; python_version < "3.8"', | 46 | 'importlib-metadata; python_version < "3.8"', |
428 | 47 | "lazr.restfulclient>=0.14.2", | 47 | "lazr.restfulclient>=0.14.2", |
429 | 48 | "lazr.uri", | 48 | "lazr.uri", |
430 | 49 | "six", | ||
431 | 50 | ] | 49 | ] |
432 | 51 | 50 | ||
433 | 52 | setup( | 51 | setup( |
434 | @@ -64,6 +63,7 @@ setup( | |||
435 | 64 | description=open("README.rst").readline().strip(), | 63 | description=open("README.rst").readline().strip(), |
436 | 65 | long_description=generate("src/launchpadlib/docs/index.rst", "NEWS.rst"), | 64 | long_description=generate("src/launchpadlib/docs/index.rst", "NEWS.rst"), |
437 | 66 | license="LGPL v3", | 65 | license="LGPL v3", |
438 | 66 | python_requires=">=3.5", | ||
439 | 67 | install_requires=install_requires, | 67 | install_requires=install_requires, |
440 | 68 | url="https://help.launchpad.net/API/launchpadlib", | 68 | url="https://help.launchpad.net/API/launchpadlib", |
441 | 69 | project_urls={ | 69 | project_urls={ |
442 | @@ -99,7 +99,6 @@ setup( | |||
443 | 99 | # Dependencies only needed by launchpadlib's own tests. | 99 | # Dependencies only needed by launchpadlib's own tests. |
444 | 100 | "test": [ | 100 | "test": [ |
445 | 101 | "coverage", | 101 | "coverage", |
446 | 102 | 'mock; python_version < "3"', | ||
447 | 103 | "pytest", | 102 | "pytest", |
448 | 104 | ], | 103 | ], |
449 | 105 | }, | 104 | }, |
450 | diff --git a/src/launchpadlib/apps.py b/src/launchpadlib/apps.py | |||
451 | index e163a7b..df7720f 100644 | |||
452 | --- a/src/launchpadlib/apps.py | |||
453 | +++ b/src/launchpadlib/apps.py | |||
454 | @@ -30,7 +30,7 @@ from launchpadlib.credentials import Credentials | |||
455 | 30 | from launchpadlib.uris import lookup_web_root | 30 | from launchpadlib.uris import lookup_web_root |
456 | 31 | 31 | ||
457 | 32 | 32 | ||
459 | 33 | class RequestTokenApp(object): | 33 | class RequestTokenApp: |
460 | 34 | """An application that creates request tokens.""" | 34 | """An application that creates request tokens.""" |
461 | 35 | 35 | ||
462 | 36 | def __init__(self, web_root, consumer_name, context): | 36 | def __init__(self, web_root, consumer_name, context): |
463 | diff --git a/src/launchpadlib/bin/launchpad-request-token b/src/launchpadlib/bin/launchpad-request-token | |||
464 | index 8205b45..c81a23f 100755 | |||
465 | --- a/src/launchpadlib/bin/launchpad-request-token | |||
466 | +++ b/src/launchpadlib/bin/launchpad-request-token | |||
467 | @@ -1,4 +1,4 @@ | |||
469 | 1 | #!/usr/bin/python | 1 | #!/usr/bin/python3 |
470 | 2 | 2 | ||
471 | 3 | # Copyright 2009 Canonical Ltd. | 3 | # Copyright 2009 Canonical Ltd. |
472 | 4 | 4 | ||
473 | @@ -22,10 +22,6 @@ This script will create a Launchpad request token and print to STDOUT | |||
474 | 22 | some JSON data about the token and the available access levels. | 22 | some JSON data about the token and the available access levels. |
475 | 23 | """ | 23 | """ |
476 | 24 | 24 | ||
477 | 25 | from __future__ import print_function | ||
478 | 26 | |||
479 | 27 | __metaclass__ = type | ||
480 | 28 | |||
481 | 29 | from optparse import OptionParser | 25 | from optparse import OptionParser |
482 | 30 | from launchpadlib.apps import RequestTokenApp | 26 | from launchpadlib.apps import RequestTokenApp |
483 | 31 | 27 | ||
484 | diff --git a/src/launchpadlib/credentials.py b/src/launchpadlib/credentials.py | |||
485 | index 5789fc0..abf55a2 100644 | |||
486 | --- a/src/launchpadlib/credentials.py | |||
487 | +++ b/src/launchpadlib/credentials.py | |||
488 | @@ -14,11 +14,8 @@ | |||
489 | 14 | # You should have received a copy of the GNU Lesser General Public License | 14 | # You should have received a copy of the GNU Lesser General Public License |
490 | 15 | # along with launchpadlib. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with launchpadlib. If not, see <http://www.gnu.org/licenses/>. |
491 | 16 | 16 | ||
492 | 17 | from __future__ import print_function | ||
493 | 18 | |||
494 | 19 | """launchpadlib credentials and authentication support.""" | 17 | """launchpadlib credentials and authentication support.""" |
495 | 20 | 18 | ||
496 | 21 | __metaclass__ = type | ||
497 | 22 | __all__ = [ | 19 | __all__ = [ |
498 | 23 | "AccessToken", | 20 | "AccessToken", |
499 | 24 | "AnonymousAccessToken", | 21 | "AnonymousAccessToken", |
500 | @@ -29,40 +26,20 @@ __all__ = [ | |||
501 | 29 | "Credentials", | 26 | "Credentials", |
502 | 30 | ] | 27 | ] |
503 | 31 | 28 | ||
509 | 32 | try: | 29 | from base64 import ( |
510 | 33 | from cStringIO import StringIO | 30 | b64decode, |
511 | 34 | except ImportError: | 31 | b64encode, |
512 | 35 | from io import StringIO | 32 | ) |
508 | 36 | |||
513 | 37 | import httplib2 | 33 | import httplib2 |
514 | 34 | from io import StringIO | ||
515 | 38 | import json | 35 | import json |
516 | 39 | import os | 36 | import os |
517 | 40 | from select import select | 37 | from select import select |
518 | 41 | import stat | 38 | import stat |
519 | 42 | from sys import stdin | 39 | from sys import stdin |
520 | 43 | import time | 40 | import time |
530 | 44 | 41 | from urllib.parse import urlencode, urljoin, parse_qs | |
522 | 45 | try: | ||
523 | 46 | from urllib.parse import urlencode | ||
524 | 47 | except ImportError: | ||
525 | 48 | from urllib import urlencode | ||
526 | 49 | try: | ||
527 | 50 | from urllib.parse import urljoin | ||
528 | 51 | except ImportError: | ||
529 | 52 | from urlparse import urljoin | ||
531 | 53 | import webbrowser | 42 | import webbrowser |
532 | 54 | from base64 import ( | ||
533 | 55 | b64decode, | ||
534 | 56 | b64encode, | ||
535 | 57 | ) | ||
536 | 58 | |||
537 | 59 | from six.moves.urllib.parse import parse_qs | ||
538 | 60 | |||
539 | 61 | if bytes is str: | ||
540 | 62 | # Python 2 | ||
541 | 63 | unicode_type = unicode # noqa: F821 | ||
542 | 64 | else: | ||
543 | 65 | unicode_type = str | ||
544 | 66 | 43 | ||
545 | 67 | from lazr.restfulclient.errors import HTTPError | 44 | from lazr.restfulclient.errors import HTTPError |
546 | 68 | from lazr.restfulclient.authorize.oauth import ( | 45 | from lazr.restfulclient.authorize.oauth import ( |
547 | @@ -135,7 +112,7 @@ class Credentials(OAuthAuthorizer): | |||
548 | 135 | sio = StringIO() | 112 | sio = StringIO() |
549 | 136 | self.save(sio) | 113 | self.save(sio) |
550 | 137 | serialized = sio.getvalue() | 114 | serialized = sio.getvalue() |
552 | 138 | if isinstance(serialized, unicode_type): | 115 | if isinstance(serialized, str): |
553 | 139 | serialized = serialized.encode("utf-8") | 116 | serialized = serialized.encode("utf-8") |
554 | 140 | return serialized | 117 | return serialized |
555 | 141 | 118 | ||
556 | @@ -146,7 +123,7 @@ class Credentials(OAuthAuthorizer): | |||
557 | 146 | This should probably be moved into OAuthAuthorizer. | 123 | This should probably be moved into OAuthAuthorizer. |
558 | 147 | """ | 124 | """ |
559 | 148 | credentials = cls() | 125 | credentials = cls() |
561 | 149 | if not isinstance(value, unicode_type): | 126 | if not isinstance(value, str): |
562 | 150 | value = value.decode("utf-8") | 127 | value = value.decode("utf-8") |
563 | 151 | credentials.load(StringIO(value)) | 128 | credentials.load(StringIO(value)) |
564 | 152 | return credentials | 129 | return credentials |
565 | @@ -255,7 +232,7 @@ class AccessToken(_AccessToken): | |||
566 | 255 | @classmethod | 232 | @classmethod |
567 | 256 | def from_string(cls, query_string): | 233 | def from_string(cls, query_string): |
568 | 257 | """Create and return a new `AccessToken` from the given string.""" | 234 | """Create and return a new `AccessToken` from the given string.""" |
570 | 258 | if not isinstance(query_string, unicode_type): | 235 | if not isinstance(query_string, str): |
571 | 259 | query_string = query_string.decode("utf-8") | 236 | query_string = query_string.decode("utf-8") |
572 | 260 | params = parse_qs(query_string, keep_blank_values=False) | 237 | params = parse_qs(query_string, keep_blank_values=False) |
573 | 261 | key = params["oauth_token"] | 238 | key = params["oauth_token"] |
574 | @@ -280,10 +257,10 @@ class AnonymousAccessToken(_AccessToken): | |||
575 | 280 | """ | 257 | """ |
576 | 281 | 258 | ||
577 | 282 | def __init__(self): | 259 | def __init__(self): |
579 | 283 | super(AnonymousAccessToken, self).__init__("", "") | 260 | super().__init__("", "") |
580 | 284 | 261 | ||
581 | 285 | 262 | ||
583 | 286 | class CredentialStore(object): | 263 | class CredentialStore: |
584 | 287 | """Store OAuth credentials locally. | 264 | """Store OAuth credentials locally. |
585 | 288 | 265 | ||
586 | 289 | This is a generic superclass. To implement a specific way of | 266 | This is a generic superclass. To implement a specific way of |
587 | @@ -369,7 +346,7 @@ class KeyringCredentialStore(CredentialStore): | |||
588 | 369 | B64MARKER = b"<B64>" | 346 | B64MARKER = b"<B64>" |
589 | 370 | 347 | ||
590 | 371 | def __init__(self, credential_save_failed=None, fallback=False): | 348 | def __init__(self, credential_save_failed=None, fallback=False): |
592 | 372 | super(KeyringCredentialStore, self).__init__(credential_save_failed) | 349 | super().__init__(credential_save_failed) |
593 | 373 | self._fallback = None | 350 | self._fallback = None |
594 | 374 | if fallback: | 351 | if fallback: |
595 | 375 | self._fallback = MemoryCredentialStore(credential_save_failed) | 352 | self._fallback = MemoryCredentialStore(credential_save_failed) |
596 | @@ -438,7 +415,7 @@ class KeyringCredentialStore(CredentialStore): | |||
597 | 438 | else: | 415 | else: |
598 | 439 | raise | 416 | raise |
599 | 440 | if credential_string is not None: | 417 | if credential_string is not None: |
601 | 441 | if isinstance(credential_string, unicode_type): | 418 | if isinstance(credential_string, str): |
602 | 442 | credential_string = credential_string.encode("utf8") | 419 | credential_string = credential_string.encode("utf8") |
603 | 443 | if credential_string.startswith(self.B64MARKER): | 420 | if credential_string.startswith(self.B64MARKER): |
604 | 444 | try: | 421 | try: |
605 | @@ -468,9 +445,7 @@ class UnencryptedFileCredentialStore(CredentialStore): | |||
606 | 468 | """ | 445 | """ |
607 | 469 | 446 | ||
608 | 470 | def __init__(self, filename, credential_save_failed=None): | 447 | def __init__(self, filename, credential_save_failed=None): |
612 | 471 | super(UnencryptedFileCredentialStore, self).__init__( | 448 | super().__init__(credential_save_failed) |
610 | 472 | credential_save_failed | ||
611 | 473 | ) | ||
613 | 474 | self.filename = filename | 449 | self.filename = filename |
614 | 475 | 450 | ||
615 | 476 | def do_save(self, credentials, unique_key): | 451 | def do_save(self, credentials, unique_key): |
616 | @@ -495,7 +470,7 @@ class MemoryCredentialStore(CredentialStore): | |||
617 | 495 | """ | 470 | """ |
618 | 496 | 471 | ||
619 | 497 | def __init__(self, credential_save_failed=None): | 472 | def __init__(self, credential_save_failed=None): |
621 | 498 | super(MemoryCredentialStore, self).__init__(credential_save_failed) | 473 | super().__init__(credential_save_failed) |
622 | 499 | self._credentials = {} | 474 | self._credentials = {} |
623 | 500 | 475 | ||
624 | 501 | def do_save(self, credentials, unique_key): | 476 | def do_save(self, credentials, unique_key): |
625 | @@ -507,7 +482,7 @@ class MemoryCredentialStore(CredentialStore): | |||
626 | 507 | return self._credentials.get(unique_key) | 482 | return self._credentials.get(unique_key) |
627 | 508 | 483 | ||
628 | 509 | 484 | ||
630 | 510 | class RequestTokenAuthorizationEngine(object): | 485 | class RequestTokenAuthorizationEngine: |
631 | 511 | """The superclass of all request token authorizers. | 486 | """The superclass of all request token authorizers. |
632 | 512 | 487 | ||
633 | 513 | This base class does not implement request token authorization, | 488 | This base class does not implement request token authorization, |
634 | @@ -774,15 +749,13 @@ class AuthorizeRequestTokenWithBrowser(AuthorizeRequestTokenWithURL): | |||
635 | 774 | # It doesn't look like we're doing anything here, but we | 749 | # It doesn't look like we're doing anything here, but we |
636 | 775 | # are discarding the passed-in values for consumer_name and | 750 | # are discarding the passed-in values for consumer_name and |
637 | 776 | # allow_access_levels. | 751 | # allow_access_levels. |
639 | 777 | super(AuthorizeRequestTokenWithBrowser, self).__init__( | 752 | super().__init__( |
640 | 778 | service_root, application_name, None, credential_save_failed | 753 | service_root, application_name, None, credential_save_failed |
641 | 779 | ) | 754 | ) |
642 | 780 | 755 | ||
643 | 781 | def notify_end_user_authorization_url(self, authorization_url): | 756 | def notify_end_user_authorization_url(self, authorization_url): |
644 | 782 | """Notify the end-user of the URL.""" | 757 | """Notify the end-user of the URL.""" |
648 | 783 | super( | 758 | super().notify_end_user_authorization_url(authorization_url) |
646 | 784 | AuthorizeRequestTokenWithBrowser, self | ||
647 | 785 | ).notify_end_user_authorization_url(authorization_url) | ||
649 | 786 | 759 | ||
650 | 787 | try: | 760 | try: |
651 | 788 | browser_obj = webbrowser.get() | 761 | browser_obj = webbrowser.get() |
652 | diff --git a/src/launchpadlib/docs/conf.py b/src/launchpadlib/docs/conf.py | |||
653 | index 268a8ce..235e7a9 100644 | |||
654 | --- a/src/launchpadlib/docs/conf.py | |||
655 | +++ b/src/launchpadlib/docs/conf.py | |||
656 | @@ -1,5 +1,3 @@ | |||
657 | 1 | # -*- coding: utf-8 -*- | ||
658 | 2 | # | ||
659 | 3 | # launchpadlib documentation build configuration file, created by | 1 | # launchpadlib documentation build configuration file, created by |
660 | 4 | # sphinx-quickstart on Tue Nov 5 23:48:15 2019. | 2 | # sphinx-quickstart on Tue Nov 5 23:48:15 2019. |
661 | 5 | # | 3 | # |
662 | @@ -47,9 +45,9 @@ source_suffix = ".rst" | |||
663 | 47 | master_doc = "index" | 45 | master_doc = "index" |
664 | 48 | 46 | ||
665 | 49 | # General information about the project. | 47 | # General information about the project. |
669 | 50 | project = u"launchpadlib" | 48 | project = "launchpadlib" |
670 | 51 | copyright = u"2008-2019, Canonical Ltd." | 49 | copyright = "2008-2019, Canonical Ltd." |
671 | 52 | author = u"LAZR Developers <lazr-developers@lists.launchpad.net>" | 50 | author = "LAZR Developers <lazr-developers@lists.launchpad.net>" |
672 | 53 | 51 | ||
673 | 54 | # The version info for the project you're documenting, acts as replacement for | 52 | # The version info for the project you're documenting, acts as replacement for |
674 | 55 | # |version| and |release|, also used in various other places throughout the | 53 | # |version| and |release|, also used in various other places throughout the |
675 | @@ -140,8 +138,8 @@ latex_documents = [ | |||
676 | 140 | ( | 138 | ( |
677 | 141 | master_doc, | 139 | master_doc, |
678 | 142 | "launchpadlib.tex", | 140 | "launchpadlib.tex", |
681 | 143 | u"launchpadlib Documentation", | 141 | "launchpadlib Documentation", |
682 | 144 | u"LAZR Developers \\textless{}lazr-developers@lists.launchpad.net\\textgreater{}", # noqa: E501 | 142 | "LAZR Developers \\textless{}lazr-developers@lists.launchpad.net\\textgreater{}", # noqa: E501 |
683 | 145 | "manual", | 143 | "manual", |
684 | 146 | ), | 144 | ), |
685 | 147 | ] | 145 | ] |
686 | @@ -152,7 +150,7 @@ latex_documents = [ | |||
687 | 152 | # One entry per manual page. List of tuples | 150 | # One entry per manual page. List of tuples |
688 | 153 | # (source start file, name, description, authors, manual section). | 151 | # (source start file, name, description, authors, manual section). |
689 | 154 | man_pages = [ | 152 | man_pages = [ |
691 | 155 | (master_doc, "launchpadlib", u"launchpadlib Documentation", [author], 1) | 153 | (master_doc, "launchpadlib", "launchpadlib Documentation", [author], 1) |
692 | 156 | ] | 154 | ] |
693 | 157 | 155 | ||
694 | 158 | 156 | ||
695 | @@ -165,7 +163,7 @@ texinfo_documents = [ | |||
696 | 165 | ( | 163 | ( |
697 | 166 | master_doc, | 164 | master_doc, |
698 | 167 | "launchpadlib", | 165 | "launchpadlib", |
700 | 168 | u"launchpadlib Documentation", | 166 | "launchpadlib Documentation", |
701 | 169 | author, | 167 | author, |
702 | 170 | "launchpadlib", | 168 | "launchpadlib", |
703 | 171 | "One line description of project.", | 169 | "One line description of project.", |
704 | diff --git a/src/launchpadlib/launchpad.py b/src/launchpadlib/launchpad.py | |||
705 | index d8c6ba6..6b8cea6 100644 | |||
706 | --- a/src/launchpadlib/launchpad.py | |||
707 | +++ b/src/launchpadlib/launchpad.py | |||
708 | @@ -16,18 +16,13 @@ | |||
709 | 16 | 16 | ||
710 | 17 | """Root Launchpad API class.""" | 17 | """Root Launchpad API class.""" |
711 | 18 | 18 | ||
712 | 19 | __metaclass__ = type | ||
713 | 20 | __all__ = [ | 19 | __all__ = [ |
714 | 21 | "Launchpad", | 20 | "Launchpad", |
715 | 22 | ] | 21 | ] |
716 | 23 | 22 | ||
717 | 24 | import errno | 23 | import errno |
718 | 25 | import os | 24 | import os |
724 | 26 | 25 | from urllib.parse import urlsplit | |
720 | 27 | try: | ||
721 | 28 | from urllib.parse import urlsplit | ||
722 | 29 | except ImportError: | ||
723 | 30 | from urlparse import urlsplit | ||
725 | 31 | import warnings | 26 | import warnings |
726 | 32 | 27 | ||
727 | 33 | try: | 28 | try: |
728 | @@ -130,7 +125,7 @@ class LaunchpadOAuthAwareHttp(RestfulHttp): | |||
729 | 130 | def __init__(self, launchpad, authorization_engine, *args): | 125 | def __init__(self, launchpad, authorization_engine, *args): |
730 | 131 | self.launchpad = launchpad | 126 | self.launchpad = launchpad |
731 | 132 | self.authorization_engine = authorization_engine | 127 | self.authorization_engine = authorization_engine |
733 | 133 | super(LaunchpadOAuthAwareHttp, self).__init__(*args) | 128 | super().__init__(*args) |
734 | 134 | 129 | ||
735 | 135 | def _bad_oauth_token(self, response, content): | 130 | def _bad_oauth_token(self, response, content): |
736 | 136 | """Helper method to detect an error caused by a bad OAuth token.""" | 131 | """Helper method to detect an error caused by a bad OAuth token.""" |
737 | @@ -141,9 +136,7 @@ class LaunchpadOAuthAwareHttp(RestfulHttp): | |||
738 | 141 | ) | 136 | ) |
739 | 142 | 137 | ||
740 | 143 | def _request(self, *args): | 138 | def _request(self, *args): |
744 | 144 | response, content = super(LaunchpadOAuthAwareHttp, self)._request( | 139 | response, content = super()._request(*args) |
742 | 145 | *args | ||
743 | 146 | ) | ||
745 | 147 | return self.retry_on_bad_token(response, content, *args) | 140 | return self.retry_on_bad_token(response, content, *args) |
746 | 148 | 141 | ||
747 | 149 | def retry_on_bad_token(self, response, content, *args): | 142 | def retry_on_bad_token(self, response, content, *args): |
748 | @@ -227,7 +220,7 @@ class Launchpad(ServiceRoot): | |||
749 | 227 | # case we need to authorize a new token during use. | 220 | # case we need to authorize a new token during use. |
750 | 228 | self.authorization_engine = authorization_engine | 221 | self.authorization_engine = authorization_engine |
751 | 229 | 222 | ||
753 | 230 | super(Launchpad, self).__init__( | 223 | super().__init__( |
754 | 231 | credentials, service_root, cache, timeout, proxy_info, version | 224 | credentials, service_root, cache, timeout, proxy_info, version |
755 | 232 | ) | 225 | ) |
756 | 233 | 226 | ||
757 | diff --git a/src/launchpadlib/testing/helpers.py b/src/launchpadlib/testing/helpers.py | |||
758 | index e625f5e..c9a7cec 100644 | |||
759 | --- a/src/launchpadlib/testing/helpers.py | |||
760 | +++ b/src/launchpadlib/testing/helpers.py | |||
761 | @@ -18,8 +18,6 @@ | |||
762 | 18 | 18 | ||
763 | 19 | """launchpadlib testing helpers.""" | 19 | """launchpadlib testing helpers.""" |
764 | 20 | 20 | ||
765 | 21 | |||
766 | 22 | __metaclass__ = type | ||
767 | 23 | __all__ = [ | 21 | __all__ = [ |
768 | 24 | "BadSaveKeyring", | 22 | "BadSaveKeyring", |
769 | 25 | "fake_keyring", | 23 | "fake_keyring", |
770 | @@ -64,7 +62,7 @@ class NoNetworkAuthorizationEngine(RequestTokenAuthorizationEngine): | |||
771 | 64 | ACCESS_TOKEN_KEY = "access_key:84" | 62 | ACCESS_TOKEN_KEY = "access_key:84" |
772 | 65 | 63 | ||
773 | 66 | def __init__(self, *args, **kwargs): | 64 | def __init__(self, *args, **kwargs): |
775 | 67 | super(NoNetworkAuthorizationEngine, self).__init__(*args, **kwargs) | 65 | super().__init__(*args, **kwargs) |
776 | 68 | # Set up some instrumentation. | 66 | # Set up some instrumentation. |
777 | 69 | self.request_tokens_obtained = 0 | 67 | self.request_tokens_obtained = 0 |
778 | 70 | self.access_tokens_obtained = 0 | 68 | self.access_tokens_obtained = 0 |
779 | @@ -144,7 +142,7 @@ class TestableLaunchpad(Launchpad): | |||
780 | 144 | generally pass in fully-formed Credentials objects. | 142 | generally pass in fully-formed Credentials objects. |
781 | 145 | :param service_root: Defaults to 'test_dev'. | 143 | :param service_root: Defaults to 'test_dev'. |
782 | 146 | """ | 144 | """ |
784 | 147 | super(TestableLaunchpad, self).__init__( | 145 | super().__init__( |
785 | 148 | credentials, | 146 | credentials, |
786 | 149 | authorization_engine, | 147 | authorization_engine, |
787 | 150 | credential_store, | 148 | credential_store, |
788 | diff --git a/src/launchpadlib/testing/launchpad.py b/src/launchpadlib/testing/launchpad.py | |||
789 | index aa2ee6d..3edaad4 100644 | |||
790 | --- a/src/launchpadlib/testing/launchpad.py | |||
791 | +++ b/src/launchpadlib/testing/launchpad.py | |||
792 | @@ -65,23 +65,15 @@ Where 'https://api.launchpad.net/devel/' is the URL for the WADL file, found | |||
793 | 65 | also in the WADL file itelf. | 65 | also in the WADL file itelf. |
794 | 66 | """ | 66 | """ |
795 | 67 | 67 | ||
796 | 68 | from collections.abc import Callable | ||
797 | 68 | from datetime import datetime | 69 | from datetime import datetime |
798 | 69 | 70 | ||
799 | 70 | try: | ||
800 | 71 | from collections.abc import Callable | ||
801 | 72 | except ImportError: | ||
802 | 73 | from collections import Callable | ||
803 | 74 | import sys | ||
804 | 75 | |||
805 | 76 | if sys.version_info[0] >= 3: | ||
806 | 77 | basestring = str | ||
807 | 78 | |||
808 | 79 | 71 | ||
809 | 80 | class IntegrityError(Exception): | 72 | class IntegrityError(Exception): |
810 | 81 | """Raised when bad sample data is used with a L{FakeLaunchpad} instance.""" | 73 | """Raised when bad sample data is used with a L{FakeLaunchpad} instance.""" |
811 | 82 | 74 | ||
812 | 83 | 75 | ||
814 | 84 | class FakeLaunchpad(object): | 76 | class FakeLaunchpad: |
815 | 85 | """A fake Launchpad API class for unit tests that depend on L{Launchpad}. | 77 | """A fake Launchpad API class for unit tests that depend on L{Launchpad}. |
816 | 86 | 78 | ||
817 | 87 | @param application: A C{wadllib.application.Application} instance for a | 79 | @param application: A C{wadllib.application.Application} instance for a |
818 | @@ -188,7 +180,7 @@ def wadl_tag(tag_name): | |||
819 | 188 | return "{http://research.sun.com/wadl/2006/10}" + tag_name | 180 | return "{http://research.sun.com/wadl/2006/10}" + tag_name |
820 | 189 | 181 | ||
821 | 190 | 182 | ||
823 | 191 | class FakeResource(object): | 183 | class FakeResource: |
824 | 192 | """ | 184 | """ |
825 | 193 | Represents valid sample data on L{FakeLaunchpad} instances. | 185 | Represents valid sample data on L{FakeLaunchpad} instances. |
826 | 194 | 186 | ||
827 | @@ -434,7 +426,7 @@ class FakeResource(object): | |||
828 | 434 | if param is None: | 426 | if param is None: |
829 | 435 | raise IntegrityError("%s not found" % name) | 427 | raise IntegrityError("%s not found" % name) |
830 | 436 | if param.type is None: | 428 | if param.type is None: |
832 | 437 | if not isinstance(value, basestring): | 429 | if not isinstance(value, str): |
833 | 438 | raise IntegrityError( | 430 | raise IntegrityError( |
834 | 439 | "%s is not a str or unicode for %s" % (value, name) | 431 | "%s is not a str or unicode for %s" % (value, name) |
835 | 440 | ) | 432 | ) |
836 | @@ -594,7 +586,7 @@ class FakeRoot(FakeResource): | |||
837 | 594 | resource_type = application.get_resource_type( | 586 | resource_type = application.get_resource_type( |
838 | 595 | application.markup_url + "#service-root" | 587 | application.markup_url + "#service-root" |
839 | 596 | ) | 588 | ) |
841 | 597 | super(FakeRoot, self).__init__(application, resource_type) | 589 | super().__init__(application, resource_type) |
842 | 598 | 590 | ||
843 | 599 | 591 | ||
844 | 600 | class FakeEntry(FakeResource): | 592 | class FakeEntry(FakeResource): |
845 | @@ -612,9 +604,7 @@ class FakeCollection(FakeResource): | |||
846 | 612 | name=None, | 604 | name=None, |
847 | 613 | child_resource_type=None, | 605 | child_resource_type=None, |
848 | 614 | ): | 606 | ): |
852 | 615 | super(FakeCollection, self).__init__( | 607 | super().__init__(application, resource_type, values) |
850 | 616 | application, resource_type, values | ||
851 | 617 | ) | ||
853 | 618 | self.__dict__.update( | 608 | self.__dict__.update( |
854 | 619 | {"_name": name, "_child_resource_type": child_resource_type} | 609 | {"_name": name, "_child_resource_type": child_resource_type} |
855 | 620 | ) | 610 | ) |
856 | diff --git a/src/launchpadlib/testing/tests/test_launchpad.py b/src/launchpadlib/testing/tests/test_launchpad.py | |||
857 | index c988d2b..cff4dd2 100644 | |||
858 | --- a/src/launchpadlib/testing/tests/test_launchpad.py | |||
859 | +++ b/src/launchpadlib/testing/tests/test_launchpad.py | |||
860 | @@ -160,8 +160,8 @@ class FakeLaunchpadTest(ResourcedTestCase): | |||
861 | 160 | dicts that represent objects. Plain string values can be represented | 160 | dicts that represent objects. Plain string values can be represented |
862 | 161 | as C{unicode} strings. | 161 | as C{unicode} strings. |
863 | 162 | """ | 162 | """ |
866 | 163 | self.launchpad.me = dict(name=u"foo") | 163 | self.launchpad.me = dict(name="foo") |
867 | 164 | self.assertEqual(u"foo", self.launchpad.me.name) | 164 | self.assertEqual("foo", self.launchpad.me.name) |
868 | 165 | 165 | ||
869 | 166 | def test_datetime_property(self): | 166 | def test_datetime_property(self): |
870 | 167 | """ | 167 | """ |
871 | diff --git a/src/launchpadlib/tests/test_credential_store.py b/src/launchpadlib/tests/test_credential_store.py | |||
872 | index 3049fbe..b6fe597 100644 | |||
873 | --- a/src/launchpadlib/tests/test_credential_store.py | |||
874 | +++ b/src/launchpadlib/tests/test_credential_store.py | |||
875 | @@ -169,9 +169,7 @@ class TestKeyringCredentialStore(CredentialStoreTestCase): | |||
876 | 169 | # handled correctly. (See bug lp:877374) | 169 | # handled correctly. (See bug lp:877374) |
877 | 170 | class UnicodeInMemoryKeyring(InMemoryKeyring): | 170 | class UnicodeInMemoryKeyring(InMemoryKeyring): |
878 | 171 | def get_password(self, service, username): | 171 | def get_password(self, service, username): |
882 | 172 | password = super(UnicodeInMemoryKeyring, self).get_password( | 172 | password = super().get_password(service, username) |
880 | 173 | service, username | ||
881 | 174 | ) | ||
883 | 175 | if isinstance(password, unicode_type): | 173 | if isinstance(password, unicode_type): |
884 | 176 | password = password.encode("utf-8") | 174 | password = password.encode("utf-8") |
885 | 177 | return password | 175 | return password |
886 | @@ -194,9 +192,7 @@ class TestKeyringCredentialStore(CredentialStoreTestCase): | |||
887 | 194 | 192 | ||
888 | 195 | class UnencodedInMemoryKeyring(InMemoryKeyring): | 193 | class UnencodedInMemoryKeyring(InMemoryKeyring): |
889 | 196 | def get_password(self, service, username): | 194 | def get_password(self, service, username): |
893 | 197 | pw = super(UnencodedInMemoryKeyring, self).get_password( | 195 | pw = super().get_password(service, username) |
891 | 198 | service, username | ||
892 | 199 | ) | ||
894 | 200 | return b64decode(pw[5:]) | 196 | return b64decode(pw[5:]) |
895 | 201 | 197 | ||
896 | 202 | self.keyring = UnencodedInMemoryKeyring() | 198 | self.keyring = UnencodedInMemoryKeyring() |
897 | diff --git a/src/launchpadlib/tests/test_http.py b/src/launchpadlib/tests/test_http.py | |||
898 | index 6924e4f..e2287f6 100644 | |||
899 | --- a/src/launchpadlib/tests/test_http.py | |||
900 | +++ b/src/launchpadlib/tests/test_http.py | |||
901 | @@ -17,15 +17,10 @@ | |||
902 | 17 | """Tests for the LaunchpadOAuthAwareHTTP class.""" | 17 | """Tests for the LaunchpadOAuthAwareHTTP class.""" |
903 | 18 | 18 | ||
904 | 19 | from collections import deque | 19 | from collections import deque |
906 | 20 | from json import dumps | 20 | from json import dumps, JSONDecodeError |
907 | 21 | import tempfile | 21 | import tempfile |
908 | 22 | import unittest | 22 | import unittest |
909 | 23 | 23 | ||
910 | 24 | try: | ||
911 | 25 | from json import JSONDecodeError | ||
912 | 26 | except ImportError: | ||
913 | 27 | JSONDecodeError = ValueError | ||
914 | 28 | |||
915 | 29 | from launchpadlib.errors import Unauthorized | 24 | from launchpadlib.errors import Unauthorized |
916 | 30 | from launchpadlib.credentials import UnencryptedFileCredentialStore | 25 | from launchpadlib.credentials import UnencryptedFileCredentialStore |
917 | 31 | from launchpadlib.launchpad import ( | 26 | from launchpadlib.launchpad import ( |
918 | @@ -75,7 +70,7 @@ class SimulatedResponsesHttp(LaunchpadOAuthAwareHttp): | |||
919 | 75 | :param responses: A list of HttpResponse objects to use | 70 | :param responses: A list of HttpResponse objects to use |
920 | 76 | in response to requests. | 71 | in response to requests. |
921 | 77 | """ | 72 | """ |
923 | 78 | super(SimulatedResponsesHttp, self).__init__(*args) | 73 | super().__init__(*args) |
924 | 79 | self.sent_responses = [] | 74 | self.sent_responses = [] |
925 | 80 | self.unsent_responses = responses | 75 | self.unsent_responses = responses |
926 | 81 | self.cache = None | 76 | self.cache = None |
927 | diff --git a/src/launchpadlib/tests/test_launchpad.py b/src/launchpadlib/tests/test_launchpad.py | |||
928 | index 66462c5..47410d5 100644 | |||
929 | --- a/src/launchpadlib/tests/test_launchpad.py | |||
930 | +++ b/src/launchpadlib/tests/test_launchpad.py | |||
931 | @@ -16,8 +16,6 @@ | |||
932 | 16 | 16 | ||
933 | 17 | """Tests for the Launchpad class.""" | 17 | """Tests for the Launchpad class.""" |
934 | 18 | 18 | ||
935 | 19 | __metaclass__ = type | ||
936 | 20 | |||
937 | 21 | from contextlib import contextmanager | 19 | from contextlib import contextmanager |
938 | 22 | import os | 20 | import os |
939 | 23 | import shutil | 21 | import shutil |
940 | @@ -25,11 +23,7 @@ import socket | |||
941 | 25 | import stat | 23 | import stat |
942 | 26 | import tempfile | 24 | import tempfile |
943 | 27 | import unittest | 25 | import unittest |
949 | 28 | 26 | from unittest.mock import patch | |
945 | 29 | try: | ||
946 | 30 | from unittest.mock import patch | ||
947 | 31 | except ImportError: | ||
948 | 32 | from mock import patch | ||
950 | 33 | import warnings | 27 | import warnings |
951 | 34 | 28 | ||
952 | 35 | from lazr.restfulclient.resource import ServiceRoot | 29 | from lazr.restfulclient.resource import ServiceRoot |
953 | @@ -351,11 +345,11 @@ class TestLaunchpadLoginWith(KeyringTest): | |||
954 | 351 | """Tests for Launchpad.login_with().""" | 345 | """Tests for Launchpad.login_with().""" |
955 | 352 | 346 | ||
956 | 353 | def setUp(self): | 347 | def setUp(self): |
958 | 354 | super(TestLaunchpadLoginWith, self).setUp() | 348 | super().setUp() |
959 | 355 | self.temp_dir = tempfile.mkdtemp() | 349 | self.temp_dir = tempfile.mkdtemp() |
960 | 356 | 350 | ||
961 | 357 | def tearDown(self): | 351 | def tearDown(self): |
963 | 358 | super(TestLaunchpadLoginWith, self).tearDown() | 352 | super().tearDown() |
964 | 359 | shutil.rmtree(self.temp_dir) | 353 | shutil.rmtree(self.temp_dir) |
965 | 360 | 354 | ||
966 | 361 | def test_dirs_created(self): | 355 | def test_dirs_created(self): |
967 | diff --git a/src/launchpadlib/uris.py b/src/launchpadlib/uris.py | |||
968 | index dda802e..1bcc8f6 100644 | |||
969 | --- a/src/launchpadlib/uris.py | |||
970 | +++ b/src/launchpadlib/uris.py | |||
971 | @@ -20,18 +20,15 @@ The code in this module lets users say "staging" when they mean | |||
972 | 20 | "https://api.staging.launchpad.net/". | 20 | "https://api.staging.launchpad.net/". |
973 | 21 | """ | 21 | """ |
974 | 22 | 22 | ||
975 | 23 | __metaclass__ = type | ||
976 | 24 | __all__ = [ | 23 | __all__ = [ |
977 | 25 | "lookup_service_root", | 24 | "lookup_service_root", |
978 | 26 | "lookup_web_root", | 25 | "lookup_web_root", |
979 | 27 | "web_root_for_service_root", | 26 | "web_root_for_service_root", |
980 | 28 | ] | 27 | ] |
981 | 29 | try: | ||
982 | 30 | from urllib.parse import urlparse | ||
983 | 31 | except ImportError: | ||
984 | 32 | from urlparse import urlparse | ||
985 | 33 | 28 | ||
986 | 29 | from urllib.parse import urlparse | ||
987 | 34 | import warnings | 30 | import warnings |
988 | 31 | |||
989 | 35 | from lazr.uri import URI | 32 | from lazr.uri import URI |
990 | 36 | 33 | ||
991 | 37 | LPNET_SERVICE_ROOT = "https://api.launchpad.net/" | 34 | LPNET_SERVICE_ROOT = "https://api.launchpad.net/" |
992 | diff --git a/tox.ini b/tox.ini | |||
993 | index aaa62c9..ecde674 100644 | |||
994 | --- a/tox.ini | |||
995 | +++ b/tox.ini | |||
996 | @@ -1,13 +1,13 @@ | |||
997 | 1 | [tox] | 1 | [tox] |
998 | 2 | envlist = | 2 | envlist = |
1000 | 3 | py27,py35,py36,py37,py38,py39,py310,py311,lint,docs | 3 | py35,py36,py37,py38,py39,py310,py311,lint,docs |
1001 | 4 | requires = virtualenv<20.22 | 4 | requires = virtualenv<20.22 |
1002 | 5 | 5 | ||
1003 | 6 | [testenv] | 6 | [testenv] |
1004 | 7 | deps = | 7 | deps = |
1005 | 8 | .[test,testing] | 8 | .[test,testing] |
1006 | 9 | commands = | 9 | commands = |
1008 | 10 | coverage run -m pytest src {posargs} | 10 | pytest src {posargs} |
1009 | 11 | 11 | ||
1010 | 12 | [testenv:lint] | 12 | [testenv:lint] |
1011 | 13 | # necessary to build the woke linter | 13 | # necessary to build the woke linter |
1012 | @@ -34,17 +34,16 @@ deps = | |||
1013 | 34 | .[docs] | 34 | .[docs] |
1014 | 35 | 35 | ||
1015 | 36 | [testenv:coverage] | 36 | [testenv:coverage] |
1016 | 37 | description = usage: tox -e py27,py35,py310,coverage | ||
1017 | 38 | basepython = | 37 | basepython = |
1018 | 39 | python3 | 38 | python3 |
1019 | 40 | deps = | 39 | deps = |
1020 | 41 | .[testing,test] | 40 | .[testing,test] |
1021 | 42 | commands = | 41 | commands = |
1022 | 42 | coverage erase | ||
1023 | 43 | coverage run -m pytest src {posargs} | ||
1024 | 43 | coverage combine | 44 | coverage combine |
1025 | 44 | coverage html | 45 | coverage html |
1029 | 45 | coverage report -m --fail-under=89 | 46 | coverage report -m --fail-under=91 |
1027 | 46 | depends = | ||
1028 | 47 | py27,py35,py310 | ||
1030 | 48 | 47 | ||
1031 | 49 | [coverage:run] | 48 | [coverage:run] |
1032 | 50 | parallel=True | 49 | parallel=True |
Hi Colin, thank you for your contribution!
The changes look good to me with a couple of comments. 👍