Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 797ee78

Browse files
David Robertsonclokep
andauthored
Relax ignore-missing-imports for modules that have stubs now and update mypy (#11006)
Updating mypy past version 0.9 means that third-party stubs are no-longer distributed with typeshed. See http://mypy-lang.blogspot.com/2021/06/mypy-0900-released.html for details. We therefore pull in stub packages in setup.py Additionally, some modules that we were previously ignoring import failures for now have stubs. So let's use them. The rest of this change consists of fixups to make the newer mypy + stubs pass CI. Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com>
1 parent 670a8d9 commit 797ee78

File tree

12 files changed

+100
-80
lines changed

12 files changed

+100
-80
lines changed

changelog.d/11006.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Bump mypy version for CI to 0.910, and pull in new type stubs for dependencies.

mypy.ini

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -198,98 +198,97 @@ disallow_untyped_defs = True
198198
[mypy-tests.storage.test_user_directory]
199199
disallow_untyped_defs = True
200200

201-
[mypy-pymacaroons.*]
202-
ignore_missing_imports = True
201+
;; Dependencies without annotations
202+
;; Before ignoring a module, check to see if type stubs are available.
203+
;; The `typeshed` project maintains stubs here:
204+
;; https://github.com/python/typeshed/tree/master/stubs
205+
;; and for each package `foo` there's a corresponding `types-foo` package on PyPI,
206+
;; which we can pull in as a dev dependency by adding to `setup.py`'s
207+
;; `CONDITIONAL_REQUIREMENTS["mypy"]` list.
203208

204-
[mypy-zope]
209+
[mypy-authlib.*]
205210
ignore_missing_imports = True
206211

207212
[mypy-bcrypt]
208213
ignore_missing_imports = True
209214

210-
[mypy-constantly]
211-
ignore_missing_imports = True
212-
213-
[mypy-twisted.*]
215+
[mypy-canonicaljson]
214216
ignore_missing_imports = True
215217

216-
[mypy-treq.*]
218+
[mypy-constantly]
217219
ignore_missing_imports = True
218220

219-
[mypy-hyperlink]
221+
[mypy-daemonize]
220222
ignore_missing_imports = True
221223

222224
[mypy-h11]
223225
ignore_missing_imports = True
224226

225-
[mypy-msgpack]
226-
ignore_missing_imports = True
227-
228-
[mypy-opentracing]
227+
[mypy-hiredis]
229228
ignore_missing_imports = True
230229

231-
[mypy-OpenSSL.*]
230+
[mypy-hyperlink]
232231
ignore_missing_imports = True
233232

234-
[mypy-netaddr]
233+
[mypy-ijson.*]
235234
ignore_missing_imports = True
236235

237-
[mypy-saml2.*]
236+
[mypy-jaeger_client.*]
238237
ignore_missing_imports = True
239238

240-
[mypy-canonicaljson]
239+
[mypy-josepy.*]
241240
ignore_missing_imports = True
242241

243-
[mypy-jaeger_client.*]
242+
[mypy-jwt.*]
244243
ignore_missing_imports = True
245244

246-
[mypy-jsonschema]
245+
[mypy-lxml]
247246
ignore_missing_imports = True
248247

249-
[mypy-signedjson.*]
248+
[mypy-msgpack]
250249
ignore_missing_imports = True
251250

252-
[mypy-prometheus_client.*]
251+
[mypy-nacl.*]
253252
ignore_missing_imports = True
254253

255-
[mypy-service_identity.*]
254+
[mypy-netaddr]
256255
ignore_missing_imports = True
257256

258-
[mypy-daemonize]
257+
[mypy-opentracing]
259258
ignore_missing_imports = True
260259

261-
[mypy-sentry_sdk]
260+
[mypy-phonenumbers.*]
262261
ignore_missing_imports = True
263262

264-
[mypy-PIL.*]
263+
[mypy-prometheus_client.*]
265264
ignore_missing_imports = True
266265

267-
[mypy-lxml]
266+
[mypy-pymacaroons.*]
268267
ignore_missing_imports = True
269268

270-
[mypy-jwt.*]
269+
[mypy-pympler.*]
271270
ignore_missing_imports = True
272271

273-
[mypy-authlib.*]
272+
[mypy-rust_python_jaeger_reporter.*]
274273
ignore_missing_imports = True
275274

276-
[mypy-rust_python_jaeger_reporter.*]
275+
[mypy-saml2.*]
277276
ignore_missing_imports = True
278277

279-
[mypy-nacl.*]
278+
[mypy-sentry_sdk]
280279
ignore_missing_imports = True
281280

282-
[mypy-hiredis]
281+
[mypy-service_identity.*]
283282
ignore_missing_imports = True
284283

285-
[mypy-josepy.*]
284+
[mypy-signedjson.*]
286285
ignore_missing_imports = True
287286

288-
[mypy-pympler.*]
287+
[mypy-treq.*]
289288
ignore_missing_imports = True
290289

291-
[mypy-phonenumbers.*]
290+
[mypy-twisted.*]
292291
ignore_missing_imports = True
293292

294-
[mypy-ijson.*]
293+
[mypy-zope]
295294
ignore_missing_imports = True

setup.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,16 @@ def exec_file(path_segments):
112112
"pygithub==1.55",
113113
]
114114

115-
CONDITIONAL_REQUIREMENTS["mypy"] = ["mypy==0.812", "mypy-zope==0.2.13"]
115+
CONDITIONAL_REQUIREMENTS["mypy"] = [
116+
"mypy==0.910",
117+
"mypy-zope==0.3.2",
118+
"types-bleach>=4.1.0",
119+
"types-jsonschema>=3.2.0",
120+
"types-Pillow>=8.3.4",
121+
"types-pyOpenSSL>=20.0.7",
122+
"types-PyYAML>=5.4.10",
123+
"types-setuptools>=57.4.0",
124+
]
116125

117126
# Dependencies which are exclusively required by unit test code. This is
118127
# NOT a list of all modules that are necessary to run the unit tests.

synapse/config/tls.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,12 @@ def is_disk_cert_valid(self, allow_self_signed=True):
172172
)
173173

174174
# YYYYMMDDhhmmssZ -- in UTC
175-
expires_on = datetime.strptime(
176-
tls_certificate.get_notAfter().decode("ascii"), "%Y%m%d%H%M%SZ"
177-
)
175+
expiry_data = tls_certificate.get_notAfter()
176+
if expiry_data is None:
177+
raise ValueError(
178+
"TLS Certificate has no expiry date, and this is not permitted"
179+
)
180+
expires_on = datetime.strptime(expiry_data.decode("ascii"), "%Y%m%d%H%M%SZ")
178181
now = datetime.utcnow()
179182
days_remaining = (expires_on - now).days
180183
return days_remaining

synapse/http/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ class InsecureInterceptableContextFactory(ssl.ContextFactory):
912912

913913
def __init__(self):
914914
self._context = SSL.Context(SSL.SSLv23_METHOD)
915-
self._context.set_verify(VERIFY_NONE, lambda *_: None)
915+
self._context.set_verify(VERIFY_NONE, lambda *_: False)
916916

917917
def getContext(self, hostname=None, port=None):
918918
return self._context

synapse/logging/context.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252

5353
is_thread_resource_usage_supported = True
5454

55-
def get_thread_resource_usage() -> "Optional[resource._RUsage]":
55+
def get_thread_resource_usage() -> "Optional[resource.struct_rusage]":
5656
return resource.getrusage(RUSAGE_THREAD)
5757

5858

@@ -61,7 +61,7 @@ def get_thread_resource_usage() -> "Optional[resource._RUsage]":
6161
# won't track resource usage.
6262
is_thread_resource_usage_supported = False
6363

64-
def get_thread_resource_usage() -> "Optional[resource._RUsage]":
64+
def get_thread_resource_usage() -> "Optional[resource.struct_rusage]":
6565
return None
6666

6767

@@ -226,10 +226,10 @@ def __str__(self):
226226
def copy_to(self, record):
227227
pass
228228

229-
def start(self, rusage: "Optional[resource._RUsage]"):
229+
def start(self, rusage: "Optional[resource.struct_rusage]"):
230230
pass
231231

232-
def stop(self, rusage: "Optional[resource._RUsage]"):
232+
def stop(self, rusage: "Optional[resource.struct_rusage]"):
233233
pass
234234

235235
def add_database_transaction(self, duration_sec):
@@ -289,7 +289,7 @@ def __init__(
289289

290290
# The thread resource usage when the logcontext became active. None
291291
# if the context is not currently active.
292-
self.usage_start: Optional[resource._RUsage] = None
292+
self.usage_start: Optional[resource.struct_rusage] = None
293293

294294
self.main_thread = get_thread_id()
295295
self.request = None
@@ -410,7 +410,7 @@ def copy_to(self, record) -> None:
410410
# we also track the current scope:
411411
record.scope = self.scope
412412

413-
def start(self, rusage: "Optional[resource._RUsage]") -> None:
413+
def start(self, rusage: "Optional[resource.struct_rusage]") -> None:
414414
"""
415415
Record that this logcontext is currently running.
416416
@@ -435,7 +435,7 @@ def start(self, rusage: "Optional[resource._RUsage]") -> None:
435435
else:
436436
self.usage_start = rusage
437437

438-
def stop(self, rusage: "Optional[resource._RUsage]") -> None:
438+
def stop(self, rusage: "Optional[resource.struct_rusage]") -> None:
439439
"""
440440
Record that this logcontext is no longer running.
441441
@@ -490,7 +490,7 @@ def get_resource_usage(self) -> ContextResourceUsage:
490490

491491
return res
492492

493-
def _get_cputime(self, current: "resource._RUsage") -> Tuple[float, float]:
493+
def _get_cputime(self, current: "resource.struct_rusage") -> Tuple[float, float]:
494494
"""Get the cpu usage time between start() and the given rusage
495495
496496
Args:

synapse/metrics/background_process_metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ def __init__(self, name: str, instance_id: Optional[Union[int, str]] = None):
265265
super().__init__("%s-%s" % (name, instance_id))
266266
self._proc = _BackgroundProcess(name, self)
267267

268-
def start(self, rusage: "Optional[resource._RUsage]"):
268+
def start(self, rusage: "Optional[resource.struct_rusage]"):
269269
"""Log context has started running (again)."""
270270

271271
super().start(rusage)

synapse/push/mailer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ def safe_text(raw_text: str) -> jinja2.Markup:
892892
A Markup object ready to safely use in a Jinja template.
893893
"""
894894
return jinja2.Markup(
895-
bleach.linkify(bleach.clean(raw_text, tags=[], attributes={}, strip=False))
895+
bleach.linkify(bleach.clean(raw_text, tags=[], attributes=[], strip=False))
896896
)
897897

898898

synapse/rest/media/v1/__init__.py

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,21 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import PIL.Image
15+
from PIL.features import check_codec
1616

1717
# check for JPEG support.
18-
try:
19-
PIL.Image._getdecoder("rgb", "jpeg", None)
20-
except OSError as e:
21-
if str(e).startswith("decoder jpeg not available"):
22-
raise Exception(
23-
"FATAL: jpeg codec not supported. Install pillow correctly! "
24-
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
25-
" pip install pillow --user'"
26-
)
27-
except Exception:
28-
# any other exception is fine
29-
pass
18+
if not check_codec("jpg"):
19+
raise Exception(
20+
"FATAL: jpeg codec not supported. Install pillow correctly! "
21+
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
22+
" pip install pillow --user'"
23+
)
3024

3125

3226
# check for PNG support.
33-
try:
34-
PIL.Image._getdecoder("rgb", "zip", None)
35-
except OSError as e:
36-
if str(e).startswith("decoder zip not available"):
37-
raise Exception(
38-
"FATAL: zip codec not supported. Install pillow correctly! "
39-
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
40-
" pip install pillow --user'"
41-
)
42-
except Exception:
43-
# any other exception is fine
44-
pass
27+
if not check_codec("zlib"):
28+
raise Exception(
29+
"FATAL: zip codec not supported. Install pillow correctly! "
30+
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
31+
" pip install pillow --user'"
32+
)

synapse/rest/media/v1/thumbnailer.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,19 @@ def __init__(self, input_path: str):
6161
self.transpose_method = None
6262
try:
6363
# We don't use ImageOps.exif_transpose since it crashes with big EXIF
64-
image_exif = self.image._getexif()
64+
#
65+
# Ignore safety: Pillow seems to acknowledge that this method is
66+
# "private, experimental, but generally widely used". Pillow 6
67+
# includes a public getexif() method (no underscore) that we might
68+
# consider using instead when we can bump that dependency.
69+
#
70+
# At the time of writing, Debian buster (currently oldstable)
71+
# provides version 5.4.1. It's expected to EOL in mid-2022, see
72+
# https://wiki.debian.org/DebianReleases#Production_Releases
73+
image_exif = self.image._getexif() # type: ignore
6574
if image_exif is not None:
6675
image_orientation = image_exif.get(EXIF_ORIENTATION_TAG)
76+
assert isinstance(image_orientation, int)
6777
self.transpose_method = EXIF_TRANSPOSE_MAPPINGS.get(image_orientation)
6878
except Exception as e:
6979
# A lot of parsing errors can happen when parsing EXIF
@@ -76,7 +86,10 @@ def transpose(self) -> Tuple[int, int]:
7686
A tuple containing the new image size in pixels as (width, height).
7787
"""
7888
if self.transpose_method is not None:
79-
self.image = self.image.transpose(self.transpose_method)
89+
# Safety: `transpose` takes an int rather than e.g. an IntEnum.
90+
# self.transpose_method is set above to be a value in
91+
# EXIF_TRANSPOSE_MAPPINGS, and that only contains correct values.
92+
self.image = self.image.transpose(self.transpose_method) # type: ignore[arg-type]
8093
self.width, self.height = self.image.size
8194
self.transpose_method = None
8295
# We don't need EXIF any more
@@ -101,7 +114,7 @@ def aspect(self, max_width: int, max_height: int) -> Tuple[int, int]:
101114
else:
102115
return (max_height * self.width) // self.height, max_height
103116

104-
def _resize(self, width: int, height: int) -> Image:
117+
def _resize(self, width: int, height: int) -> Image.Image:
105118
# 1-bit or 8-bit color palette images need converting to RGB
106119
# otherwise they will be scaled using nearest neighbour which
107120
# looks awful.
@@ -151,7 +164,7 @@ def crop(self, width: int, height: int, output_type: str) -> BytesIO:
151164
cropped = scaled_image.crop((crop_left, 0, crop_right, height))
152165
return self._encode_image(cropped, output_type)
153166

154-
def _encode_image(self, output_image: Image, output_type: str) -> BytesIO:
167+
def _encode_image(self, output_image: Image.Image, output_type: str) -> BytesIO:
155168
output_bytes_io = BytesIO()
156169
fmt = self.FORMATS[output_type]
157170
if fmt == "JPEG":

0 commit comments

Comments
 (0)