Skip to content

Commit 00fffdc

Browse files
authored
Run mypy on src (#115)
Many fixes to the types
1 parent 6bb1ce8 commit 00fffdc

35 files changed

+709
-330
lines changed

docs/utility_functions.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ assert BooleanParsers.boolean.parse('false') == Success(False)
2424

2525
## `splat(function)`: convert a function of many arguments to take only one list argument
2626

27-
The function `splat(function: Callable[Tuple[*B], A]) -> Callable[Tuple[Tuple[*B]], A]` has a complicated type signature, but does a simple thing. It takes a single function that takes multiple arguments and converts it to a function that takes only one argument, which is a list of all original arguments. It is particularly useful for passing a list of results from a sequential parser `&` to a function that takes each element as an separate argument. By applying `splat` to the function, it now takes the single list that is returned by the sequential parser.
27+
The function `splat(function: Callable[tuple[*B], A]) -> Callable[tuple[tuple[*B]], A]` has a complicated type signature, but does a simple thing. It takes a single function that takes multiple arguments and converts it to a function that takes only one argument, which is a list of all original arguments. It is particularly useful for passing a list of results from a sequential parser `&` to a function that takes each element as an separate argument. By applying `splat` to the function, it now takes the single list that is returned by the sequential parser.
2828

2929
```python
3030
from collections import namedtuple
@@ -44,7 +44,7 @@ assert UrlParsers.url.parse('https://drhagen.com:443/blog/') == \
4444

4545
## `unsplat(function)`: convert a function of one list argument to take many arguments
4646

47-
The function `unsplat(function: Callable[Tuple[Tuple[*B]], A]) -> Callable[Tuple[*B], A]` does the opposite of `splat`. It takes a single function that takes a single argument that is a list and converts it to a function that takes multiple arguments, each of which was an element of the original list. It is not very useful for writing parsers because the conversion parser always calls its converter function with a single argument, but is included here to complement `splat`.
47+
The function `unsplat(function: Callable[tuple[tuple[*B]], A]) -> Callable[tuple[*B], A]` does the opposite of `splat`. It takes a single function that takes a single argument that is a list and converts it to a function that takes multiple arguments, each of which was an element of the original list. It is not very useful for writing parsers because the conversion parser always calls its converter function with a single argument, but is included here to complement `splat`.
4848

4949
```python
5050
from parsita.util import splat, unsplat

examples/expressions.py

+24-20
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
1+
from typing import Literal, Sequence
2+
13
from parsita import ParserContext, lit, opt, reg, rep
24

35

6+
def make_term(args: tuple[float, Sequence[tuple[Literal["*", "/"], float]]]) -> float:
7+
factor1, factors = args
8+
result = factor1
9+
for op, factor in factors:
10+
if op == "*":
11+
result = result * factor
12+
else:
13+
result = result / factor
14+
return result
15+
16+
17+
def make_expr(args: tuple[float, Sequence[tuple[Literal["+", "-"], float]]]) -> float:
18+
term1, terms = args
19+
result = term1
20+
for op, term2 in terms:
21+
if op == "+":
22+
result = result + term2
23+
else:
24+
result = result - term2
25+
return result
26+
27+
428
class ExpressionParsers(ParserContext, whitespace=r"[ ]*"):
529
number = reg(r"[+-]?\d+(\.\d+)?(e[+-]?\d+)?") > float
630

731
base = "(" >> expr << ")" | number
832

933
factor = base & opt("^" >> base) > (lambda x: x[0] ** x[1][0] if x[1] else x[0])
1034

11-
def make_term(args):
12-
factor1, factors = args
13-
result = factor1
14-
for op, factor in factors:
15-
if op == "*":
16-
result = result * factor
17-
else:
18-
result = result / factor
19-
return result
20-
2135
term = factor & rep(lit("*", "/") & factor) > make_term
2236

23-
def make_expr(args):
24-
term1, terms = args
25-
result = term1
26-
for op, term2 in terms:
27-
if op == "+":
28-
result = result + term2
29-
else:
30-
result = result - term2
31-
return result
32-
3337
expr = term & rep(lit("+", "-") & term) > make_expr
3438

3539

examples/json.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class JsonStringParsers(ParserContext):
1313
line_feed = lit(r"\n") > constant("\n")
1414
carriage_return = lit(r"\r") > constant("\r")
1515
tab = lit(r"\t") > constant("\t")
16-
uni = reg(r"\\u([0-9a-fA-F]{4})") > (lambda x: chr(int(x.group(1), 16)))
16+
uni = reg(r"\\u[0-9a-fA-F]{4}") > (lambda x: chr(int(x[2:], 16)))
1717

1818
escaped = (
1919
quote
@@ -61,6 +61,7 @@ class JsonParsers(ParserContext, whitespace=r"[ \t\n\r]*"):
6161
"width" : 4.0
6262
}""",
6363
'{"text" : ""}',
64+
r'"\u2260"',
6465
]
6566

6667
for string in strings:

examples/positioned.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from abc import abstractmethod
1010
from dataclasses import dataclass
11-
from typing import Generic
11+
from typing import Generic, Optional
1212

1313
from parsita import Parser, ParserContext, Reader, reg
1414
from parsita.state import Continue, Input, Output, State
@@ -45,7 +45,7 @@ def __init__(self, parser: Parser[Input, PositionAware[Output]]):
4545
super().__init__()
4646
self.parser = parser
4747

48-
def _consume(self, state: State, reader: Reader[Input]):
48+
def _consume(self, state: State, reader: Reader[Input]) -> Optional[Continue[Input, Output]]:
4949
start = reader.position
5050
status = self.parser.consume(state, reader)
5151

@@ -55,11 +55,11 @@ def _consume(self, state: State, reader: Reader[Input]):
5555
else:
5656
return status
5757

58-
def __repr__(self):
58+
def __repr__(self) -> str:
5959
return self.name_or_nothing() + f"positioned({self.parser.name_or_repr()})"
6060

6161

62-
def positioned(parser: Parser[Input, PositionAware[Output]]):
62+
def positioned(parser: Parser[Input, PositionAware[Output]]) -> PositionedParser[Input, Output]:
6363
"""Set the position on a PositionAware value.
6464
6565
This parser matches ``parser`` and, if successful, calls ``set_position``
@@ -75,18 +75,18 @@ def positioned(parser: Parser[Input, PositionAware[Output]]):
7575

7676
# Everything below here is an example use case
7777
@dataclass
78-
class UnfinishedVariable(PositionAware):
78+
class Variable:
7979
name: str
80-
81-
def set_position(self, start: int, length: int):
82-
return Variable(self.name, start, length)
80+
start: int
81+
length: int
8382

8483

8584
@dataclass
86-
class Variable:
85+
class UnfinishedVariable(PositionAware[Variable]):
8786
name: str
88-
start: int
89-
length: int
87+
88+
def set_position(self, start: int, length: int) -> Variable:
89+
return Variable(self.name, start, length)
9090

9191

9292
@dataclass

noxfile.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from nox import options, parametrize
44
from nox_poetry import Session, session
55

6-
options.sessions = ["test", "coverage", "lint"]
6+
options.sessions = ["test", "coverage", "lint", "type_check"]
77

88

99
@session(python=["3.9", "3.10", "3.11", "3.12", "3.13"])
@@ -27,6 +27,11 @@ def lint(s: Session, command: list[str]):
2727

2828

2929
@session(venv_backend="none")
30-
def format(s: Session) -> None:
30+
def type_check(s: Session):
31+
s.run("mypy", "src")
32+
33+
34+
@session(venv_backend="none")
35+
def format(s: Session):
3136
s.run("ruff", "check", ".", "--select", "I", "--fix")
3237
s.run("ruff", "format", ".")

poetry.lock

+59-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ pytest-timeout = "*"
3131
# Lint
3232
ruff = "^0.6"
3333

34+
# Type checking
35+
mypy = "^1"
36+
3437
# Docs
3538
mkdocs-material = "^9"
3639

@@ -49,6 +52,7 @@ exclude_lines = [
4952
"pragma: no cover",
5053
"raise NotImplementedError",
5154
"if TYPE_CHECKING",
55+
"@overload",
5256
]
5357

5458
[tool.coverage.paths]
@@ -85,6 +89,11 @@ extend-ignore = ["F821", "N805"]
8589
"__init__.py" = ["F401"]
8690

8791

92+
[tool.mypy]
93+
strict = true
94+
implicit_reexport = true
95+
96+
8897
[build-system]
8998
requires = ["poetry-core>=1.0.0"]
9099
build-backend = "poetry.core.masonry.api"

0 commit comments

Comments
 (0)