-
Notifications
You must be signed in to change notification settings - Fork 871
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix all mypy typechecking errors, add a lot of type annotations #1399
Changes from 3 commits
b79d59d
e743883
5db9b6f
905b46f
de64ef2
c9121d7
70676a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,7 @@ | |
import sys | ||
import logging | ||
import importlib | ||
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Mapping, Sequence, TextIO | ||
from typing import TYPE_CHECKING, Any, BinaryIO, Callable, ClassVar, Mapping, Sequence | ||
from . import util | ||
from .preprocessors import build_preprocessors | ||
from .blockprocessors import build_block_parser | ||
|
@@ -36,6 +36,7 @@ | |
|
||
if TYPE_CHECKING: # pragma: no cover | ||
from xml.etree.ElementTree import Element | ||
from markdown.extensions.toc import TocToken | ||
|
||
__all__ = ['Markdown', 'markdown', 'markdownFromFile'] | ||
|
||
|
@@ -85,7 +86,11 @@ class Markdown: | |
callable which accepts an [`Element`][xml.etree.ElementTree.Element] and returns a `str`. | ||
""" | ||
|
||
def __init__(self, **kwargs): | ||
toc_tokens: list[TocToken] | ||
toc: str | ||
Meta: dict[str, Any] | ||
oprypin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def __init__(self, **kwargs: Any): | ||
""" | ||
Creates a new Markdown instance. | ||
|
||
|
@@ -159,7 +164,7 @@ def build_parser(self) -> Markdown: | |
def registerExtensions( | ||
self, | ||
extensions: Sequence[Extension | str], | ||
configs: Mapping[str, Mapping[str, Any]] | ||
configs: Mapping[str, dict[str, Any]] | ||
) -> Markdown: | ||
""" | ||
Load a list of extensions into an instance of the `Markdown` class. | ||
|
@@ -183,7 +188,7 @@ def registerExtensions( | |
'Successfully loaded extension "%s.%s".' | ||
% (ext.__class__.__module__, ext.__class__.__name__) | ||
) | ||
elif ext is not None: | ||
elif ext is not None: # type: ignore[unreachable] | ||
raise TypeError( | ||
'Extension "{}.{}" must be of type: "{}.{}"'.format( | ||
ext.__class__.__module__, ext.__class__.__name__, | ||
|
@@ -387,8 +392,8 @@ def convert(self, source: str) -> str: | |
|
||
def convertFile( | ||
self, | ||
input: str | TextIO | None = None, | ||
output: str | TextIO | None = None, | ||
input: str | BinaryIO | None = None, | ||
output: str | BinaryIO | None = None, | ||
encoding: str | None = None, | ||
) -> Markdown: | ||
""" | ||
|
@@ -417,15 +422,13 @@ def convertFile( | |
# Read the source | ||
if input: | ||
if isinstance(input, str): | ||
input_file = codecs.open(input, mode="r", encoding=encoding) | ||
with codecs.open(input, mode="r", encoding=encoding) as input_file: | ||
text = input_file.read() | ||
else: | ||
input_file = codecs.getreader(encoding)(input) | ||
text = input_file.read() | ||
input_file.close() | ||
with codecs.getreader(encoding)(input) as input_file: | ||
text = input_file.read() | ||
else: | ||
text = sys.stdin.read() | ||
if not isinstance(text, str): # pragma: no cover | ||
text = text.decode(encoding) | ||
|
||
text = text.lstrip('\ufeff') # remove the byte-order mark | ||
|
||
|
@@ -442,18 +445,14 @@ def convertFile( | |
output_file.close() | ||
else: | ||
writer = codecs.getwriter(encoding) | ||
output_file = writer(output, errors="xmlcharrefreplace") | ||
output_file.write(html) | ||
output_writer = writer(output, errors="xmlcharrefreplace") | ||
output_writer.write(html) | ||
Comment on lines
-443
to
+444
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why this change was made at all. Note that the Contributing Guide states:
I realize that in this instance the variable name is not exposed outside of this method so there is no concern over a backward incompatible change, but the general principle remains. Please, let's refrain from making unnecessary changes just for personal preference. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 100% of code body changes in this pull request are in response to error messages produced by mypy. Don't need to tell me this regarding unrelated changes because I'm always the first one to say this as well. This particular change is because mypy doesn't like that these two differently-typed values share the same variable name. With lines 425-429 reverted: $ mypy markdown
markdown/core.py:427: error: Incompatible types in assignment (expression has type "StreamReader", variable has type "StreamReaderWriter") [assignment]
Found 1 error in 1 file (checked 33 source files) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I embrace Python not being strongly typed. If that means making changes like this, then no thank you. |
||
# Don't close here. User may want to write more. | ||
else: | ||
# Encode manually and write bytes to stdout. | ||
html = html.encode(encoding, "xmlcharrefreplace") | ||
try: | ||
# Write bytes directly to buffer (Python 3). | ||
sys.stdout.buffer.write(html) | ||
except AttributeError: # pragma: no cover | ||
# Probably Python 2, which works with bytes by default. | ||
sys.stdout.write(html) | ||
html_bytes = html.encode(encoding, "xmlcharrefreplace") | ||
# Write bytes directly to buffer (Python 3). | ||
sys.stdout.buffer.write(html_bytes) | ||
waylan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return self | ||
|
||
|
@@ -489,7 +488,13 @@ def markdown(text: str, **kwargs: Any) -> str: | |
return md.convert(text) | ||
|
||
|
||
def markdownFromFile(**kwargs: Any): | ||
def markdownFromFile( | ||
*, | ||
input: str | BinaryIO | None = None, | ||
output: str | BinaryIO | None = None, | ||
encoding: str | None = None, | ||
**kwargs: Any | ||
) -> None: | ||
Comment on lines
-485
to
+491
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't recall why the specific keyword parameters were removed some years ago, but why did you feel the need to add them back in? I'm not opposed to it if there is a good reason related to this scope of this PR, but I would like to here the reason. However, I am more concerned about why you added position arguments (*)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function's signature doesn't change, I'm just formalizing the de-facto signature. The |
||
""" | ||
Read Markdown text from a file and write output to a file or a stream. | ||
|
||
|
@@ -498,13 +503,11 @@ def markdownFromFile(**kwargs: Any): | |
[`convert`][markdown.Markdown.convert]. | ||
|
||
Keyword arguments: | ||
input (str | TextIO): A file name or readable object. | ||
output (str | TextIO): A file name or writable object. | ||
encoding (str): Encoding of input and output. | ||
input: A file name or readable object. | ||
output: A file name or writable object. | ||
encoding: Encoding of input and output. | ||
**kwargs: Any arguments accepted by the `Markdown` class. | ||
|
||
""" | ||
md = Markdown(**kwargs) | ||
md.convertFile(kwargs.get('input', None), | ||
kwargs.get('output', None), | ||
kwargs.get('encoding', None)) | ||
md.convertFile(input, output, encoding) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what you are trying to accomplish here. Does this make it possible for Markdown content to cause an error? If so, that would be unacceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With lines 429-431 reverted:
The code before:
where
RE.match(string)
can beNone
, andNone.group()
is an errorThe code after:
The old code doesn't care to check for the
None
case where it would violently error withAttributeError
. mypy doesn't let it slide and exposes a potential bug.The new code directly checks for it and errors with a clearer message.
There is no change regarding which situations an error does or does not happen.
TBD: It is worth checking whether this bug case can actually happen (in current code already). In any case we should be happy that mypy flagged this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, now we know about a bug which we didn't know about before. That is good. But this is not the way to address it. Under no circumstances should source text cause Markdown to raise an error. In fact, that is one of the primary goals of the project as documented on the home page. Therefore, this is not the proper way to fix the bug. The error needs to be silenced and some reasonable text needs to be included in the output (depending on the conditions under which the issue arises).