diff --git a/lark/lark.py b/lark/lark.py index afebdd1..40b74be 100644 --- a/lark/lark.py +++ b/lark/lark.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod import sys, os, pickle, hashlib import tempfile +import types from typing import ( TypeVar, Type, List, Dict, Iterator, Callable, Union, Optional, Sequence, Tuple, Iterable, IO, Any, TYPE_CHECKING, Collection @@ -27,9 +28,10 @@ from .grammar import Rule import re try: - import regex # type: ignore + import regex + _has_regex = True except ImportError: - regex = None + _has_regex = False ###{standalone @@ -253,11 +255,12 @@ class Lark(Serialize): def __init__(self, grammar: 'Union[Grammar, str, IO[str]]', **options) -> None: self.options = LarkOptions(options) + re_module: types.ModuleType # Set regex or re module use_regex = self.options.regex if use_regex: - if regex: + if _has_regex: re_module = regex else: raise ImportError('`regex` module must be installed if calling `Lark(regex=True)`.') @@ -267,7 +270,7 @@ class Lark(Serialize): # Some, but not all file-like objects have a 'name' attribute if self.options.source_path is None: try: - self.source_path = grammar.name + self.source_path = grammar.name # type: ignore[union-attr] except AttributeError: self.source_path = '' else: @@ -275,7 +278,7 @@ class Lark(Serialize): # Drain file-like objects to get their contents try: - read = grammar.read + read = grammar.read # type: ignore[union-attr] except AttributeError: pass else: diff --git a/lark/tree.py b/lark/tree.py index 9e223a8..7ad620f 100644 --- a/lark/tree.py +++ b/lark/tree.py @@ -5,6 +5,7 @@ from typing import List, Callable, Iterator, Union, Optional, Generic, TypeVar, if TYPE_CHECKING: from .lexer import TerminalDef, Token + import rich if sys.version_info >= (3, 8): from typing import Literal else: diff --git a/lark/utils.py b/lark/utils.py index 1ba72f4..f9c0fd0 100644 --- a/lark/utils.py +++ b/lark/utils.py @@ -109,9 +109,10 @@ class SerializeMemoizer(Serialize): try: - import regex # type: ignore + import regex + _has_regex = True except ImportError: - regex = None + _has_regex = False if sys.version_info >= (3, 11): import re._parser as sre_parse @@ -123,7 +124,7 @@ else: categ_pattern = re.compile(r'\\p{[A-Za-z_]+}') def get_regexp_width(expr): - if regex: + if _has_regex: # Since `sre_parse` cannot deal with Unicode categories of the form `\p{Mn}`, we replace these with # a simple letter, which makes no difference as we are only trying to get the possible lengths of the regex # match here below. @@ -135,7 +136,7 @@ def get_regexp_width(expr): try: return [int(x) for x in sre_parse.parse(regexp_final).getwidth()] except sre_constants.error: - if not regex: + if not _has_regex: raise ValueError(expr) else: # sre_parse does not support the new features in regex. To not completely fail in that case, @@ -223,15 +224,16 @@ def combine_alternatives(lists): try: import atomicwrites + _has_atomicwrites = True except ImportError: - atomicwrites = None # type: ignore[assigment] + _has_atomicwrites = False class FS: exists = staticmethod(os.path.exists) @staticmethod def open(name, mode="r", **kwargs): - if atomicwrites and "w" in mode: + if _has_atomicwrites and "w" in mode: return atomicwrites.atomic_write(name, mode=mode, overwrite=True, **kwargs) else: return open(name, mode, **kwargs) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e73c651 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" + + +[tool.mypy] +files = "lark" +python_version = "3.6" +show_error_codes = true +enable_error_code = ["ignore-without-code"] +exclude = [ + "^lark/__pyinstaller", +] + +# You can disable imports or control per-module/file settings here +[[tool.mypy.overrides]] +module = [ "js2py" ] +ignore_missing_imports = true diff --git a/tox.ini b/tox.ini index 04840eb..ef89565 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,30 @@ [tox] -envlist = py36, py37, py38, py39, pypy, pypy3 -skip_missing_interpreters=true +envlist = py36, py37, py38, py39, py310, pypy3, type +skip_missing_interpreters = true [testenv] whitelist_externals = git deps = -rtest-requirements.txt +passenv = + TERM # to always force recreation and avoid unexpected side effects -recreate=True +recreate = True -commands= +commands = git submodule sync -q git submodule update --init python -m tests {posargs} + +[testenv:type] +description = run type check on code base +skip_install = true +recreate = false +deps = + mypy==0.950 + types-atomicwrites + types-regex + rich +commands = + mypy