Skip to content

Commit fec6a82

Browse files
committed
typing: strict mypy & py.typed
1 parent e5194e1 commit fec6a82

File tree

8 files changed

+58
-38
lines changed

8 files changed

+58
-38
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ repos:
1818
hooks:
1919
- id: black
2020
language_version: python3
21-
# - repo: https://github.com/pre-commit/mirrors-mypy
22-
# rev: v0.812
23-
# hooks:
24-
# - id: mypy
25-
# args: ["--strict", "--show-error-codes"]
21+
- repo: https://github.com/pre-commit/mirrors-mypy
22+
rev: v1.0.1
23+
hooks:
24+
- id: mypy

obonet/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
from __future__ import annotations
2+
13
from .read import read_obo
24

35
__all__ = [
46
"read_obo",
57
]
68

79

8-
def _get_version():
10+
def _get_version() -> str | None:
911
# https://github.com/pypa/setuptools_scm#retrieving-package-version-at-runtime
1012
from pkg_resources import DistributionNotFound, get_distribution
1113

1214
try:
13-
return get_distribution("obonet").version
15+
return str(get_distribution("obonet").version)
1416
except DistributionNotFound:
1517
return None
1618

obonet/io.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
import io
55
import logging
66
import mimetypes
7+
import os
78
import re
9+
from typing import Callable, TextIO, Union
810
from urllib.request import urlopen
911

12+
PathType = Union[str, os.PathLike, TextIO]
1013

11-
def open_read_file(path, encoding: str | None = None):
14+
15+
def open_read_file(path: PathType, encoding: str | None = None) -> TextIO:
1216
"""
1317
Return a file object from the path. Automatically detects and supports
1418
URLs and compression. If path is pathlike, it's converted to a string.
@@ -18,7 +22,7 @@ def open_read_file(path, encoding: str | None = None):
1822
"""
1923
# Convert pathlike objects to string paths
2024
if hasattr(path, "__fspath__"):
21-
path = path.__fspath__()
25+
path = os.fspath(path)
2226

2327
if not isinstance(path, str):
2428
# Passthrough open file buffers without modification
@@ -52,7 +56,7 @@ def open_read_file(path, encoding: str | None = None):
5256
}
5357

5458

55-
def get_opener(filename):
59+
def get_opener(filename: str) -> Callable[..., TextIO]:
5660
"""
5761
Automatically detect compression and return the file opening function.
5862
"""

obonet/py.typed

Whitespace-only changes.

obonet/read.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
import itertools
44
import logging
55
import re
6-
from typing import Any
6+
from typing import Any, Iterator
77

88
import networkx
99

10-
from .io import open_read_file
10+
from .io import PathType, open_read_file
1111

1212
logger = logging.getLogger(__name__)
1313

1414

1515
def read_obo(
16-
path_or_file, ignore_obsolete: bool = True, encoding: str | None = "utf-8"
16+
path_or_file: PathType, ignore_obsolete: bool = True, encoding: str | None = "utf-8"
1717
) -> networkx.MultiDiGraph[str]:
1818
"""
1919
Return a networkx.MultiDiGraph of the ontology serialized by the
@@ -34,9 +34,8 @@ def read_obo(
3434
The character set encoding to use for path_or_file when path_or_file
3535
is a path/URL. Set to None for platform-dependent locale default.
3636
"""
37-
obo_file = open_read_file(path_or_file, encoding=encoding)
38-
typedefs, terms, instances, header = get_sections(obo_file)
39-
obo_file.close()
37+
with open_read_file(path_or_file, encoding=encoding) as obo_file:
38+
typedefs, terms, instances, header = get_sections(obo_file)
4039

4140
if "ontology" in header:
4241
header["name"] = header.get("ontology")
@@ -69,7 +68,7 @@ def read_obo(
6968

7069

7170
def get_sections(
72-
lines,
71+
lines: Iterator[str],
7372
) -> tuple[
7473
list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]], dict[str, Any]
7574
]:
@@ -82,11 +81,10 @@ def get_sections(
8281
typedefs, terms, instances = [], [], []
8382
header = None
8483
groups = itertools.groupby(lines, lambda line: line.strip() == "")
85-
for is_blank, stanza_lines in groups:
84+
for is_blank, stanza_lines_iter in groups:
8685
if is_blank:
8786
continue
88-
stanza_type_line = next(stanza_lines)
89-
stanza_lines = list(stanza_lines)
87+
stanza_type_line, *stanza_lines = stanza_lines_iter
9088
if stanza_type_line.startswith("[Typedef]"):
9189
typedef = parse_stanza(stanza_lines, typedef_tag_singularity)
9290
typedefs.append(typedef)
@@ -108,7 +106,7 @@ def get_sections(
108106
# regular expression to parse key-value pair lines.
109107
tag_line_pattern = re.compile(
110108
r"^(?P<tag>.+?): *(?P<value>.+?) ?(?P<trailing_modifier>(?<!\\)\{.*?(?<!\\)\})? ?(?P<comment>(?<!\\)!.*?)?$"
111-
) # noqa: E501
109+
)
112110

113111

114112
def parse_tag_line(line: str) -> tuple[str, str | None, str | None, str | None]:
@@ -131,11 +129,11 @@ def parse_tag_line(line: str) -> tuple[str, str | None, str | None, str | None]:
131129
return tag, value, trailing_modifier, comment
132130

133131

134-
def parse_stanza(lines, tag_singularity) -> dict[str, Any]:
132+
def parse_stanza(lines: list[str], tag_singularity: dict[str, bool]) -> dict[str, Any]:
135133
"""
136134
Returns a dictionary representation of a stanza.
137135
"""
138-
stanza = {}
136+
stanza: dict[str, Any] = {}
139137
for line in lines:
140138
if line.startswith("!"):
141139
continue

pyproject.toml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@ select = [
7070
"W", # pycode warnings
7171
]
7272

73+
[tool.mypy]
74+
python_version = "3.7"
75+
strict = true
76+
77+
7378
[[tool.mypy.overrides]]
74-
module = ["networkx.*", "setuptools.*", "pytest.*", "_pytest.*"]
79+
module = [
80+
"networkx.*",
81+
"setuptools.*",
82+
"pkg_resources.*",
83+
"pytest.*",
84+
"_pytest.*",
85+
]
7586
ignore_missing_imports = true
87+
88+
[[tool.mypy.overrides]]
89+
module = [
90+
"tests.*",
91+
]
92+
disallow_untyped_decorators = false

tests/__init__.py

Whitespace-only changes.

tests/test_obo_reading.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
directory = os.path.dirname(os.path.abspath(__file__))
1010

1111

12-
def test_read_taxrank_file():
12+
def test_read_taxrank_file() -> None:
1313
"""
1414
Test reading the taxrank ontology OBO file.
1515
"""
@@ -24,20 +24,20 @@ def test_read_taxrank_file():
2424

2525
@pytest.mark.parametrize("extension", ["", ".gz", ".bz2", ".xz"])
2626
@pytest.mark.parametrize("pathlike", [False, True])
27-
def test_read_taxrank_path(extension, pathlike):
27+
def test_read_taxrank_path(extension: str, pathlike: bool) -> None:
2828
"""
2929
Test reading the taxrank ontology OBO file from paths. Includes reading
3030
compressed paths.
3131
"""
3232
path = os.path.join(directory, "data", "taxrank.obo" + extension)
3333
if pathlike:
34-
path = pathlib.Path(path)
34+
path = pathlib.Path(path) # type: ignore [assignment]
3535
taxrank = obonet.read_obo(path)
3636
assert len(taxrank) == 61
3737

3838

3939
@pytest.mark.parametrize("extension", ["", ".gz", ".bz2", ".xz"])
40-
def test_read_taxrank_url(extension):
40+
def test_read_taxrank_url(extension: str) -> None:
4141
"""
4242
Test reading the taxrank ontology OBO file from paths. Includes reading
4343
compressed paths.
@@ -48,7 +48,7 @@ def test_read_taxrank_url(extension):
4848
assert len(taxrank) == 61
4949

5050

51-
def test_read_brenda_subset():
51+
def test_read_brenda_subset() -> None:
5252
"""
5353
Test reading a subset of the BrendaTissue.obo file. This file does not set
5454
the ontology tag. See <https://github.com/dhimmel/obonet/issues/10>.
@@ -64,7 +64,7 @@ def test_read_brenda_subset():
6464

6565

6666
@pytest.mark.parametrize("ontology", ["doid", "go", "pato"])
67-
def test_read_obo(ontology):
67+
def test_read_obo(ontology: str) -> None:
6868
"""
6969
Test that reading ontology does not error.
7070
"""
@@ -73,7 +73,7 @@ def test_read_obo(ontology):
7373
assert graph
7474

7575

76-
def test_parse_tag_line_newline_agnostic():
76+
def test_parse_tag_line_newline_agnostic() -> None:
7777
for line in ["saved-by: vw", "saved-by: vw\n"]:
7878
tag, value, trailing_modifier, comment = parse_tag_line(line)
7979
assert tag == "saved-by"
@@ -82,7 +82,7 @@ def test_parse_tag_line_newline_agnostic():
8282
assert comment is None
8383

8484

85-
def test_parse_tag_line_with_tag_and_value():
85+
def test_parse_tag_line_with_tag_and_value() -> None:
8686
line = 'synonym: "ovarian ring canal" NARROW []\n'
8787
tag, value, trailing_modifier, comment = parse_tag_line(line)
8888
assert tag == "synonym"
@@ -91,7 +91,7 @@ def test_parse_tag_line_with_tag_and_value():
9191
assert comment is None
9292

9393

94-
def test_parse_tag_line_with_tag_value_and_comment():
94+
def test_parse_tag_line_with_tag_value_and_comment() -> None:
9595
line = "is_a: GO:0005102 ! receptor binding\n"
9696
tag, value, trailing_modifier, comment = parse_tag_line(line)
9797
assert tag == "is_a"
@@ -100,7 +100,7 @@ def test_parse_tag_line_with_tag_value_and_comment():
100100
assert comment == "receptor binding"
101101

102102

103-
def test_parse_tag_line_with_tag_value_and_trailing_modifier():
103+
def test_parse_tag_line_with_tag_value_and_trailing_modifier() -> None:
104104
line = 'xref: UMLS:C0226369 {source="ncithesaurus:Obturator_Artery"}\n'
105105
tag, value, trailing_modifier, comment = parse_tag_line(line)
106106
assert tag == "xref"
@@ -109,7 +109,7 @@ def test_parse_tag_line_with_tag_value_and_trailing_modifier():
109109
assert comment is None
110110

111111

112-
def test_parse_tag_line_with_tag_value_trailing_modifier_and_comment():
112+
def test_parse_tag_line_with_tag_value_trailing_modifier_and_comment() -> None:
113113
line = 'xref: UMLS:C0022131 {source="ncithesaurus:Islet_of_Langerhans"} ! Islets of Langerhans\n' # noqa: E501
114114
tag, value, trailing_modifier, comment = parse_tag_line(line)
115115
assert tag == "xref"
@@ -118,22 +118,22 @@ def test_parse_tag_line_with_tag_value_trailing_modifier_and_comment():
118118
assert comment == "Islets of Langerhans"
119119

120120

121-
def test_parse_tag_line_backslashed_exclamation():
121+
def test_parse_tag_line_backslashed_exclamation() -> None:
122122
line = "synonym: not a real example \\!\n"
123123
tag, value, trailing_modifier, comment = parse_tag_line(line)
124124
assert tag == "synonym"
125125
assert value == r"not a real example \!"
126126

127127

128-
def test_ignore_obsolete_nodes():
128+
def test_ignore_obsolete_nodes() -> None:
129129
"""Quick verification that the change doesn't break anything"""
130130
path = os.path.join(directory, "data", "brenda-subset.obo")
131131
brenda = obonet.read_obo(path)
132132
nodes = brenda.nodes(data=True)
133133
assert "BTO:0000311" not in nodes
134134

135135

136-
def test_presence_of_obsolete_nodes():
136+
def test_presence_of_obsolete_nodes() -> None:
137137
"""Test that we did, indeed, capture those obsolete entries"""
138138
pytest.importorskip("networkx", minversion="2.0")
139139
path = os.path.join(directory, "data", "brenda-subset.obo")

0 commit comments

Comments
 (0)