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

Relax ignore-missing-imports for modules that have stubs now and update mypy #11006

Merged
merged 15 commits into from
Oct 8, 2021
1 change: 1 addition & 0 deletions changelog.d/11006.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use type stubs for jsonschema, pyOpenSSL and Pillow when running mypy in CI.
69 changes: 34 additions & 35 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -192,98 +192,97 @@ disallow_untyped_defs = True
[mypy-tests.storage.test_user_directory]
disallow_untyped_defs = True

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

[mypy-zope]
[mypy-authlib.*]
ignore_missing_imports = True

[mypy-bcrypt]
ignore_missing_imports = True

[mypy-constantly]
ignore_missing_imports = True

[mypy-twisted.*]
[mypy-canonicaljson]
ignore_missing_imports = True

[mypy-treq.*]
[mypy-constantly]
ignore_missing_imports = True

[mypy-hyperlink]
[mypy-daemonize]
ignore_missing_imports = True

[mypy-h11]
ignore_missing_imports = True

[mypy-msgpack]
ignore_missing_imports = True

[mypy-opentracing]
[mypy-hiredis]
ignore_missing_imports = True

[mypy-OpenSSL.*]
[mypy-hyperlink]
ignore_missing_imports = True

[mypy-netaddr]
[mypy-ijson.*]
ignore_missing_imports = True

[mypy-saml2.*]
[mypy-jaeger_client.*]
ignore_missing_imports = True

[mypy-canonicaljson]
[mypy-josepy.*]
ignore_missing_imports = True

[mypy-jaeger_client.*]
[mypy-jwt.*]
ignore_missing_imports = True

[mypy-jsonschema]
[mypy-lxml]
ignore_missing_imports = True

[mypy-signedjson.*]
[mypy-msgpack]
ignore_missing_imports = True

[mypy-prometheus_client.*]
[mypy-nacl.*]
ignore_missing_imports = True

[mypy-service_identity.*]
[mypy-netaddr]
ignore_missing_imports = True

[mypy-daemonize]
[mypy-opentracing]
ignore_missing_imports = True

[mypy-sentry_sdk]
[mypy-phonenumbers.*]
ignore_missing_imports = True

[mypy-PIL.*]
[mypy-prometheus_client.*]
ignore_missing_imports = True

[mypy-lxml]
[mypy-pymacaroons.*]
ignore_missing_imports = True

[mypy-jwt.*]
[mypy-pympler.*]
ignore_missing_imports = True

[mypy-authlib.*]
[mypy-rust_python_jaeger_reporter.*]
ignore_missing_imports = True

[mypy-rust_python_jaeger_reporter.*]
[mypy-saml2.*]
ignore_missing_imports = True

[mypy-nacl.*]
[mypy-sentry_sdk]
ignore_missing_imports = True

[mypy-hiredis]
[mypy-service_identity.*]
ignore_missing_imports = True

[mypy-josepy.*]
[mypy-signedjson.*]
ignore_missing_imports = True

[mypy-pympler.*]
[mypy-treq.*]
ignore_missing_imports = True

[mypy-phonenumbers.*]
[mypy-twisted.*]
ignore_missing_imports = True

[mypy-ijson.*]
[mypy-zope]
ignore_missing_imports = True
11 changes: 10 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,16 @@ def exec_file(path_segments):
"pygithub==1.55",
]

CONDITIONAL_REQUIREMENTS["mypy"] = ["mypy==0.812", "mypy-zope==0.2.13"]
CONDITIONAL_REQUIREMENTS["mypy"] = [
"mypy>=0.812",
"mypy-zope>=0.2.13",
DMRobertson marked this conversation as resolved.
Show resolved Hide resolved
"types-bleach>=4.1.0",
"types-jsonschema>=3.2.0",
"types-Pillow>=8.3.4",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how closely these should match the versions that we have installed (or are they versioned separately)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed the versions match the underlying project's versions, but from http://mypy-lang.blogspot.com/2021/05/the-upcoming-switch-to-modular-typeshed.html I read that

Currently stub packages are mostly versioned independently of the implementation package. However, it’s possible to update stub metadata in typeshed to have the versioning better reflect the target implementation package version.

That was back in May though.

There's some discussion on PEP 561, but I'm not sure what to make of it to be honest.

I'm somewhat tempted to leave this as it is and see how things go.

"types-pyOpenSSL>=20.0.7",
"types-PyYAML>=5.4.10",
"types-setuptools>=57.4.0",
]

# Dependencies which are exclusively required by unit test code. This is
# NOT a list of all modules that are necessary to run the unit tests.
Expand Down
9 changes: 6 additions & 3 deletions synapse/config/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,12 @@ def is_disk_cert_valid(self, allow_self_signed=True):
)

# YYYYMMDDhhmmssZ -- in UTC
expires_on = datetime.strptime(
tls_certificate.get_notAfter().decode("ascii"), "%Y%m%d%H%M%SZ"
)
expiry_data = tls_certificate.get_notAfter()
if expiry_data is None:
raise ValueError(
"TLS Certificate has no expiry date, and this is not permitted"
)
expires_on = datetime.strptime(expiry_data.decode("ascii"), "%Y%m%d%H%M%SZ")
now = datetime.utcnow()
days_remaining = (expires_on - now).days
return days_remaining
Expand Down
2 changes: 1 addition & 1 deletion synapse/http/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,7 @@ class InsecureInterceptableContextFactory(ssl.ContextFactory):

def __init__(self):
self._context = SSL.Context(SSL.SSLv23_METHOD)
self._context.set_verify(VERIFY_NONE, lambda *_: None)
self._context.set_verify(VERIFY_NONE, lambda *_: False)

def getContext(self, hostname=None, port=None):
return self._context
Expand Down
16 changes: 8 additions & 8 deletions synapse/logging/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@

is_thread_resource_usage_supported = True

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


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

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


Expand Down Expand Up @@ -226,10 +226,10 @@ def __str__(self):
def copy_to(self, record):
pass

def start(self, rusage: "Optional[resource._RUsage]"):
def start(self, rusage: "Optional[resource.struct_rusage]"):
pass

def stop(self, rusage: "Optional[resource._RUsage]"):
def stop(self, rusage: "Optional[resource.struct_rusage]"):
pass

def add_database_transaction(self, duration_sec):
Expand Down Expand Up @@ -289,7 +289,7 @@ def __init__(

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

self.main_thread = get_thread_id()
self.request = None
Expand Down Expand Up @@ -410,7 +410,7 @@ def copy_to(self, record) -> None:
# we also track the current scope:
record.scope = self.scope

def start(self, rusage: "Optional[resource._RUsage]") -> None:
def start(self, rusage: "Optional[resource.struct_rusage]") -> None:
"""
Record that this logcontext is currently running.

Expand All @@ -435,7 +435,7 @@ def start(self, rusage: "Optional[resource._RUsage]") -> None:
else:
self.usage_start = rusage

def stop(self, rusage: "Optional[resource._RUsage]") -> None:
def stop(self, rusage: "Optional[resource.struct_rusage]") -> None:
"""
Record that this logcontext is no longer running.

Expand Down Expand Up @@ -490,7 +490,7 @@ def get_resource_usage(self) -> ContextResourceUsage:

return res

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

Args:
Expand Down
2 changes: 1 addition & 1 deletion synapse/metrics/background_process_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def __init__(self, name: str, instance_id: Optional[Union[int, str]] = None):
super().__init__("%s-%s" % (name, instance_id))
self._proc = _BackgroundProcess(name, self)

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

super().start(rusage)
Expand Down
2 changes: 1 addition & 1 deletion synapse/push/mailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ def safe_text(raw_text: str) -> jinja2.Markup:
A Markup object ready to safely use in a Jinja template.
"""
return jinja2.Markup(
bleach.linkify(bleach.clean(raw_text, tags=[], attributes={}, strip=False))
bleach.linkify(bleach.clean(raw_text, tags=[], attributes=[], strip=False))
clokep marked this conversation as resolved.
Show resolved Hide resolved
)


Expand Down
38 changes: 13 additions & 25 deletions synapse/rest/media/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import PIL.Image
from PIL.features import check_codec

# check for JPEG support.
try:
PIL.Image._getdecoder("rgb", "jpeg", None)
except OSError as e:
if str(e).startswith("decoder jpeg not available"):
raise Exception(
"FATAL: jpeg codec not supported. Install pillow correctly! "
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
" pip install pillow --user'"
)
except Exception:
# any other exception is fine
pass
if not check_codec("jpg"):
raise Exception(
"FATAL: jpeg codec not supported. Install pillow correctly! "
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
" pip install pillow --user'"
)


# check for PNG support.
try:
PIL.Image._getdecoder("rgb", "zip", None)
except OSError as e:
if str(e).startswith("decoder zip not available"):
raise Exception(
"FATAL: zip codec not supported. Install pillow correctly! "
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
" pip install pillow --user'"
)
except Exception:
# any other exception is fine
pass
if not check_codec("zlib"):
raise Exception(
"FATAL: zip codec not supported. Install pillow correctly! "
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
" pip install pillow --user'"
)
16 changes: 12 additions & 4 deletions synapse/rest/media/v1/thumbnailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ def __init__(self, input_path: str):
self.transpose_method = None
try:
# We don't use ImageOps.exif_transpose since it crashes with big EXIF
image_exif = self.image._getexif()
# Safety: Pillow seems to acknowledge that this method is
# "private, experimental, but generally widely used". Pillow 6
# includes a public getexif() method (no underscore) that we might
# consider using?
clokep marked this conversation as resolved.
Show resolved Hide resolved
image_exif = self.image._getexif() # type: ignore
if image_exif is not None:
image_orientation = image_exif.get(EXIF_ORIENTATION_TAG)
assert isinstance(image_orientation, int)
self.transpose_method = EXIF_TRANSPOSE_MAPPINGS.get(image_orientation)
except Exception as e:
# A lot of parsing errors can happen when parsing EXIF
Expand All @@ -76,7 +81,10 @@ def transpose(self) -> Tuple[int, int]:
A tuple containing the new image size in pixels as (width, height).
"""
if self.transpose_method is not None:
self.image = self.image.transpose(self.transpose_method)
# Safety: `transpose` takes an int rather than e.g. an IntEnum.
# self.transpose_method is set above to be a value in
# EXIF_TRANSPOSE_MAPPINGS, and that only contains correct values.
self.image = self.image.transpose(self.transpose_method) # type: ignore[arg-type]
self.width, self.height = self.image.size
self.transpose_method = None
# We don't need EXIF any more
Expand All @@ -101,7 +109,7 @@ def aspect(self, max_width: int, max_height: int) -> Tuple[int, int]:
else:
return (max_height * self.width) // self.height, max_height

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

def _encode_image(self, output_image: Image, output_type: str) -> BytesIO:
def _encode_image(self, output_image: Image.Image, output_type: str) -> BytesIO:
output_bytes_io = BytesIO()
fmt = self.FORMATS[output_type]
if fmt == "JPEG":
Expand Down
1 change: 1 addition & 0 deletions synapse/storage/prepare_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ def _upgrade_existing_database(
spec = importlib.util.spec_from_file_location(
module_name, absolute_path
)
assert spec is not None
clokep marked this conversation as resolved.
Show resolved Hide resolved
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore

Expand Down
5 changes: 4 additions & 1 deletion synapse/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ def _handle_frozendict(obj: Any) -> Dict[Any, Any]:
# fishing the protected dict out of the object is a bit nasty,
# but we don't really want the overhead of copying the dict.
try:
return obj._dict
# Safety: we catch the AttributeError immediately below.
# See https://github.com/matrix-org/python-canonicaljson/issues/36#issuecomment-927816293
# for discussion on how frozendict's internals have changed over time.
return obj._dict # type: ignore[attr-defined]
except AttributeError:
# When the C implementation of frozendict is used,
# there isn't a `_dict` attribute with a dict
Expand Down