Skip to content

⬆️ UPGRADE: Drop support for EOL Python 3.6 #194

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

Merged
merged 5 commits into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['pypy-3.6', '3.6', '3.7', '3.8', '3.9', '3.10']
python-version: ['pypy-3.7', '3.7', '3.8', '3.9', '3.10']

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -126,3 +126,11 @@ jobs:
with:
user: __token__
password: ${{ secrets.PYPI_KEY }}

allgood:
runs-on: ubuntu-latest
needs:
- pre-commit
- tests
steps:
- run: echo "Great success!"
8 changes: 5 additions & 3 deletions markdown_it/cli/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

Parse one or more markdown files, convert each to HTML, and print to stdout.
"""
from __future__ import annotations

import argparse
from collections.abc import Iterable, Sequence
import sys
from typing import Iterable, Optional, Sequence

from markdown_it import __version__
from markdown_it.main import MarkdownIt
Expand All @@ -15,7 +17,7 @@
version_str = "markdown-it-py [version {}]".format(__version__)


def main(args: Optional[Sequence[str]] = None) -> int:
def main(args: Sequence[str] | None = None) -> int:
Copy link
Member

Choose a reason for hiding this comment

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

Oh I didn't think type unions were supported yet: https://www.python.org/dev/peps/pep-0604/
But I guess if it works it works? Or is there any issue with type evaluation pre python 3.10?

Copy link
Member

Choose a reason for hiding this comment

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

dammit, made this comment ages ago, but didn't realise it was pending

Copy link
Contributor Author

@hukkin hukkin Feb 10, 2022

Choose a reason for hiding this comment

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

This works perfectly!

It would be a syntax error (pre Python 3.10) but from __future__ import annotations (first available in Python 3.7) makes it not error.

And mypy doesn't care what our minimum supported Python version is.

Copy link
Member

@chrisjsewell chrisjsewell Feb 10, 2022

Choose a reason for hiding this comment

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

Not sure how mypy does that 🤷 (are you sure it actually picks them up, and not silently fails)

On python 3.10 you can do:

In [1]: def bar() -> int | str:
   ...:     pass
In [2]: from typing import get_type_hints
In [3]: get_type_hints(bar)
Out[3]: {'return': int | str}

but on python 3.8, you get:

In [1]: from __future__ import annotations
In [2]: from typing import get_type_hints
In [3]: def bar() -> int | str:
   ...:     pass
   ...: 
In [4]: get_type_hints(bar)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-348bd5169782> in <module>
----> 1 get_type_hints(bar)

~/Documents/GitHub/aiida_core_develop/.tox/py38-pre-commit/lib/python3.8/typing.py in get_type_hints(obj, globalns, localns)
   1262         if isinstance(value, str):
   1263             value = ForwardRef(value)
-> 1264         value = _eval_type(value, globalns, localns)
   1265         if name in defaults and defaults[name] is None:
   1266             value = Optional[value]

~/Documents/GitHub/aiida_core_develop/.tox/py38-pre-commit/lib/python3.8/typing.py in _eval_type(t, globalns, localns)
    268     """
    269     if isinstance(t, ForwardRef):
--> 270         return t._evaluate(globalns, localns)
    271     if isinstance(t, _GenericAlias):
    272         ev_args = tuple(_eval_type(a, globalns, localns) for a in t.__args__)

~/Documents/GitHub/aiida_core_develop/.tox/py38-pre-commit/lib/python3.8/typing.py in _evaluate(self, globalns, localns)
    516                 localns = globalns
    517             self.__forward_value__ = _type_check(
--> 518                 eval(self.__forward_code__, globalns, localns),
    519                 "Forward references must evaluate to types.",
    520                 is_argument=self.__forward_is_argument__)

<string> in <module>

TypeError: unsupported operand type(s) for |: 'type' and 'type'

Copy link
Contributor Author

@hukkin hukkin Feb 10, 2022

Choose a reason for hiding this comment

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

I'm sure it works. Mypy is a static type checker. It doesn't run the code it type checks.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

It doesn't run the code it type checks

Not sure about sphinx autodoc though

Copy link
Member

Choose a reason for hiding this comment

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

Ah think it just uses:

In [13]: import inspect
In [16]: def bar() -> int | str:
    ...:     pass
    ...: 
In [17]: inspect.signature(bar).return_annotation
Out[17]: 'int | str'

and never evaluates, so should be fine

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 thought sphinx-autodoc doesn't do anything with type annotations unless sphinx-autodoc-typehints is in use (it isn't)?

And the plugin certainly supports what we do in this PR: tox-dev/sphinx-autodoc-typehints#184

Copy link
Member

@chrisjsewell chrisjsewell Feb 11, 2022

Choose a reason for hiding this comment

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

Yeh exactly, looks all good 👍; I just wanted to make sure I understood the implications of using future annotations, before rolling them out everywhere

namespace = parse_args(args)
if namespace.filenames:
convert(namespace.filenames)
Expand Down Expand Up @@ -63,7 +65,7 @@ def interactive() -> None:
break


def parse_args(args: Optional[Sequence[str]]) -> argparse.Namespace:
def parse_args(args: Sequence[str] | None) -> argparse.Namespace:
"""Parse input CLI arguments."""
parser = argparse.ArgumentParser(
description="Parse one or more markdown files, "
Expand Down
6 changes: 4 additions & 2 deletions markdown_it/common/normalize_url.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from collections.abc import Callable
import re
from typing import Callable, Optional
from urllib.parse import urlparse, urlunparse, quote, unquote # noqa: F401

import mdurl
Expand Down Expand Up @@ -67,7 +69,7 @@ def normalizeLinkText(url: str) -> str:
GOOD_DATA_RE = re.compile(r"^data:image\/(gif|png|jpeg|webp);")


def validateLink(url: str, validator: Optional[Callable] = None) -> bool:
def validateLink(url: str, validator: Callable | None = None) -> bool:
"""Validate URL link is allowed in output.

This validator can prohibit more than really needed to prevent XSS.
Expand Down
50 changes: 20 additions & 30 deletions markdown_it/main.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
from __future__ import annotations

from contextlib import contextmanager
from typing import (
Any,
Callable,
Dict,
Generator,
Iterable,
List,
Mapping,
MutableMapping,
Optional,
Union,
)
from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping
from typing import Any

from . import helpers, presets # noqa F401
from .common import normalize_url, utils # noqa F401
Expand Down Expand Up @@ -40,10 +32,10 @@
class MarkdownIt:
def __init__(
self,
config: Union[str, Mapping] = "commonmark",
options_update: Optional[Mapping] = None,
config: str | Mapping = "commonmark",
options_update: Mapping | None = None,
*,
renderer_cls: Callable[["MarkdownIt"], RendererProtocol] = RendererHTML,
renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML,
):
"""Main parser class

Expand Down Expand Up @@ -94,8 +86,8 @@ def set(self, options: MutableMapping) -> None:
self.options = OptionsDict(options)

def configure(
self, presets: Union[str, Mapping], options_update: Optional[Mapping] = None
) -> "MarkdownIt":
self, presets: str | Mapping, options_update: Mapping | None = None
) -> MarkdownIt:
"""Batch load of all options and component settings.
This is an internal method, and you probably will not need it.
But if you will - see available presets and data structure
Expand Down Expand Up @@ -131,7 +123,7 @@ def configure(

return self

def get_all_rules(self) -> Dict[str, List[str]]:
def get_all_rules(self) -> dict[str, list[str]]:
"""Return the names of all active rules."""
rules = {
chain: self[chain].ruler.get_all_rules()
Expand All @@ -140,7 +132,7 @@ def get_all_rules(self) -> Dict[str, List[str]]:
rules["inline2"] = self.inline.ruler2.get_all_rules()
return rules

def get_active_rules(self) -> Dict[str, List[str]]:
def get_active_rules(self) -> dict[str, list[str]]:
"""Return the names of all active rules."""
rules = {
chain: self[chain].ruler.get_active_rules()
Expand All @@ -150,8 +142,8 @@ def get_active_rules(self) -> Dict[str, List[str]]:
return rules

def enable(
self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False
) -> "MarkdownIt":
self, names: str | Iterable[str], ignoreInvalid: bool = False
) -> MarkdownIt:
"""Enable list or rules. (chainable)

:param names: rule name or list of rule names to enable.
Expand Down Expand Up @@ -182,8 +174,8 @@ def enable(
return self

def disable(
self, names: Union[str, Iterable[str]], ignoreInvalid: bool = False
) -> "MarkdownIt":
self, names: str | Iterable[str], ignoreInvalid: bool = False
) -> MarkdownIt:
"""The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable)

:param names: rule name or list of rule names to disable.
Expand Down Expand Up @@ -222,7 +214,7 @@ def add_render_rule(self, name: str, function: Callable, fmt: str = "html") -> N
if self.renderer.__output__ == fmt:
self.renderer.rules[name] = function.__get__(self.renderer) # type: ignore

def use(self, plugin: Callable, *params, **options) -> "MarkdownIt":
def use(self, plugin: Callable, *params, **options) -> MarkdownIt:
"""Load specified plugin with given params into current parser instance. (chainable)

It's just a sugar to call `plugin(md, params)` with curring.
Expand All @@ -237,7 +229,7 @@ def func(tokens, idx):
plugin(self, *params, **options)
return self

def parse(self, src: str, env: Optional[MutableMapping] = None) -> List[Token]:
def parse(self, src: str, env: MutableMapping | None = None) -> list[Token]:
"""Parse the source string to a token stream

:param src: source string
Expand All @@ -260,7 +252,7 @@ def parse(self, src: str, env: Optional[MutableMapping] = None) -> List[Token]:
self.core.process(state)
return state.tokens

def render(self, src: str, env: Optional[MutableMapping] = None) -> Any:
def render(self, src: str, env: MutableMapping | None = None) -> Any:
"""Render markdown string into html. It does all magic for you :).

:param src: source string
Expand All @@ -274,9 +266,7 @@ def render(self, src: str, env: Optional[MutableMapping] = None) -> Any:
env = {} if env is None else env
return self.renderer.render(self.parse(src, env), self.options, env)

def parseInline(
self, src: str, env: Optional[MutableMapping] = None
) -> List[Token]:
def parseInline(self, src: str, env: MutableMapping | None = None) -> list[Token]:
"""The same as [[MarkdownIt.parse]] but skip all block rules.

:param src: source string
Expand All @@ -296,7 +286,7 @@ def parseInline(
self.core.process(state)
return state.tokens

def renderInline(self, src: str, env: Optional[MutableMapping] = None) -> Any:
def renderInline(self, src: str, env: MutableMapping | None = None) -> Any:
"""Similar to [[MarkdownIt.render]] but for single paragraph content.

:param src: source string
Expand Down
11 changes: 6 additions & 5 deletions markdown_it/parser_block.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Block-level tokenizer."""
from __future__ import annotations

import logging
from typing import List, Optional, Tuple

from .ruler import Ruler
from .token import Token
Expand All @@ -10,7 +11,7 @@
LOGGER = logging.getLogger(__name__)


_rules: List[Tuple] = [
_rules: list[tuple] = [
# First 2 params - rule name & source. Secondary array - list of rules,
# which can be terminated by this one.
("table", rules_block.table, ["paragraph", "reference"]),
Expand Down Expand Up @@ -97,9 +98,9 @@ def parse(
src: str,
md,
env,
outTokens: List[Token],
ords: Optional[Tuple[int, ...]] = None,
) -> Optional[List[Token]]:
outTokens: list[Token],
ords: tuple[int, ...] | None = None,
) -> list[Token] | None:
"""Process input string and push block tokens into `outTokens`."""
if not src:
return None
Expand Down
4 changes: 2 additions & 2 deletions markdown_it/parser_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
* Top-level rules executor. Glues block/inline parsers and does intermediate
* transformations.
"""
from typing import List, Tuple
from __future__ import annotations

from .ruler import Ruler, RuleFunc
from .rules_core.state_core import StateCore
from .rules_core import normalize, block, inline, replace, smartquotes, linkify


_rules: List[Tuple[str, RuleFunc]] = [
_rules: list[tuple[str, RuleFunc]] = [
("normalize", normalize),
("block", block),
("inline", inline),
Expand Down
8 changes: 4 additions & 4 deletions markdown_it/parser_inline.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Tokenizes paragraph content.
"""
from typing import List, Tuple
from __future__ import annotations

from .ruler import Ruler, RuleFunc
from .token import Token
from .rules_inline.state_inline import StateInline
from . import rules_inline

# Parser rules
_rules: List[Tuple[str, RuleFunc]] = [
_rules: list[tuple[str, RuleFunc]] = [
("text", rules_inline.text),
("newline", rules_inline.newline),
("escape", rules_inline.escape),
Expand All @@ -22,7 +22,7 @@
("entity", rules_inline.entity),
]

_rules2: List[Tuple[str, RuleFunc]] = [
_rules2: list[tuple[str, RuleFunc]] = [
("balance_pairs", rules_inline.link_pairs),
("strikethrough", rules_inline.strikethrough.postProcess),
("emphasis", rules_inline.emphasis.postProcess),
Expand Down Expand Up @@ -114,7 +114,7 @@ def tokenize(self, state: StateInline) -> None:
if state.pending:
state.pushPending()

def parse(self, src: str, md, env, tokens: List[Token]) -> List[Token]:
def parse(self, src: str, md, env, tokens: list[Token]) -> list[Token]:
"""Process input string and push inline tokens into `tokens`"""
state = StateInline(src, md, env, tokens)
self.tokenize(state)
Expand Down
13 changes: 5 additions & 8 deletions markdown_it/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ class Renderer
copy of rules. Those can be rewritten with ease. Also, you can add new
rules if you create plugin and adds new token types.
"""
from __future__ import annotations

from collections.abc import MutableMapping, Sequence
import inspect
from typing import (
Any,
ClassVar,
MutableMapping,
Optional,
Sequence,
)
from typing import Any, ClassVar

from .common.utils import unescapeAll, escapeHtml
from .token import Token
Expand Down Expand Up @@ -191,7 +188,7 @@ def renderAttrs(token: Token) -> str:

def renderInlineAsText(
self,
tokens: Optional[Sequence[Token]],
tokens: Sequence[Token] | None,
options: OptionsDict,
env: MutableMapping,
) -> str:
Expand Down
Loading