Skip to content

Commit a952cb2

Browse files
jandomlilyminium
andauthored
refactor: python doc script classes (#301)
* feat: adding snapshot tests * allow snapshot tests to fail * more tests on ugly classes * refactor away the document classes migrate gen_topology_groupmethods.py migrate gen_topologyattr_defaults.py migrate FormatOverview migrate CoordinateReaders migrate gen_format_overview_classes migrate TopologyParsers migrate TopologyAttrs migrate ConnectivityAttrs remove dead code, never executed remove all class attributes all non-strict mypy checks pass, mypy in pre-commit first nice simplification another nice simplification more simplifications further cleanups further trimmings continue to refactor update base.py harmonize to use private functions simpler code architecture more refactoring mypy strict mode on some files finish mypy changes flake8 on everything Update doc/source/scripts/base.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> Update doc/source/scripts/gen_format_overview_classes.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> Update doc/source/scripts/gen_format_overview_classes.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> Update doc/source/scripts/gen_format_overview_classes.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> Update doc/source/scripts/gen_format_overview_classes.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> review comments from Lily fixing mypy * Update doc/source/scripts/base.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> * Update doc/source/scripts/gen_format_overview_classes.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> * Update doc/source/scripts/gen_topologyparser_attrs.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> * Update doc/source/scripts/gen_topologyparser_attrs.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> * Update doc/source/scripts/gen_topologyparser_attrs.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> * Update doc/source/scripts/gen_topologyparser_attrs.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> * Update doc/source/scripts/gen_topologyparser_attrs.py Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com> * review: comments from Lily * fix tests * more flake8 goodies * run linter --------- Co-authored-by: Lily Wang <31115101+lilyminium@users.noreply.github.com>
1 parent 5e660d0 commit a952cb2

21 files changed

+682
-422
lines changed

.pre-commit-config.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
33
rev: v4.2.0
44
hooks:
5+
- id: check-added-large-files
6+
- id: check-case-conflict
7+
- id: check-executables-have-shebangs
8+
- id: check-merge-conflict
9+
- id: check-shebang-scripts-are-executable
10+
- id: check-symlinks
511
- id: check-yaml
612
- id: end-of-file-fixer
713
- id: trailing-whitespace
@@ -17,3 +23,21 @@ repos:
1723
hooks:
1824
- id: black
1925
args: [--line-length=79]
26+
- repo: https://github.com/pre-commit/mirrors-mypy
27+
rev: v1.5.1
28+
hooks:
29+
- id: mypy
30+
args: [--ignore-missing-imports, --install-types, --non-interactive, --strict]
31+
exclude: "/tests/.*\\.py|clean_example_notebooks.py|update_json_stubs_sitemap.py"
32+
- repo: https://github.com/PyCQA/flake8
33+
rev: 6.1.0
34+
hooks:
35+
- id: flake8
36+
additional_dependencies: [
37+
'flake8-simplify',
38+
'flake8-comprehensions',
39+
'flake8-bugbear',
40+
'darglint',
41+
'flake8-pep585',
42+
'Flake8-pyproject',
43+
]

doc/source/conf.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
project = "MDAnalysis User Guide"
2424

2525

26-
def sort_authors(filename):
26+
def sort_authors(filename: str) -> list[str]:
2727
"""Generate sorted list of authors from AUTHORS"""
2828
authors = []
2929
with open(filename, "r") as f:
@@ -144,7 +144,7 @@ def sort_authors(filename):
144144
# relative to this directory. They are copied after the builtin static files,
145145
# so a file named "default.css" will overwrite the builtin "default.css".
146146
html_static_path = ["_static"]
147-
html_css_files = []
147+
html_css_files: list[str] = []
148148

149149
# Custom sidebar templates, maps document names to template names.
150150
# alabaster sidebars
@@ -170,8 +170,9 @@ def sort_authors(filename):
170170
}
171171

172172
# nbsphinx
173-
html_js_files = [
174-
# "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js",
173+
html_js_files: list[str] = [
174+
# 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js',
175+
# DEFAULT_EMBED_REQUIREJS_URL,
175176
]
176177

177178
ipython_warning_is_error = False

doc/source/scripts/.coverage

52 KB
Binary file not shown.

doc/source/scripts/base.py

Lines changed: 118 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,65 @@
1-
from __future__ import print_function
2-
31
import os
42
import pathlib
53
import sys
64
import textwrap
7-
from collections import defaultdict
5+
from collections.abc import Callable, Iterable
6+
from typing import Any, Optional, Type
87

8+
import pandas as pd
99
import tabulate
1010

1111

12-
class TableWriter(object):
12+
def _run_method(method: Callable[..., str], *args: Any) -> str:
13+
val = method(*args)
14+
return val
15+
16+
17+
def _generate_row(
18+
*, column_spec: list[tuple[str, Callable[..., str]]], args: Iterable[Any]
19+
) -> dict[str, str]:
20+
row = {}
21+
for heading, method in column_spec:
22+
val = _run_method(method, *args)
23+
row[heading] = val
24+
return row
25+
26+
27+
def _generate_table(
28+
*,
29+
input_items: Iterable[Any],
30+
column_spec: list[tuple[str, Callable[..., str]]],
31+
) -> pd.DataFrame:
32+
rows = []
33+
for args in input_items:
34+
if not isinstance(args, Iterable):
35+
args = [args]
36+
line = _generate_row(column_spec=column_spec, args=args)
37+
rows.append(line)
38+
df = pd.DataFrame(rows)
39+
return df
40+
41+
42+
def write_table(
43+
*,
44+
path: str,
45+
headers: list[str],
46+
lines: list[list[str]],
47+
include_table: Optional[str] = None,
48+
) -> None:
49+
parent_directory = pathlib.Path(path).parent
50+
parent_directory.mkdir(exist_ok=True, parents=True)
51+
with open(path, "w") as f:
52+
f.write(f'..\n Generated by {sys.argv[0].split("/")[-1]}\n\n')
53+
if include_table:
54+
f.write(f".. table:: {include_table}\n\n")
55+
tabled = tabulate.tabulate(lines, headers=headers, tablefmt="rst")
56+
if include_table:
57+
tabled = textwrap.indent(tabled, " ")
58+
f.write(tabled)
59+
print("Wrote ", path)
60+
61+
62+
class TableWriter:
1363
"""
1464
For writing tables with easy column switching.
1565
@@ -18,91 +68,77 @@ class TableWriter(object):
1868
Filename relative to source.
1969
"""
2070

21-
filename = ""
22-
include_table = False
23-
headings = []
24-
preprocess = []
25-
postprocess = []
26-
sort = True
71+
def __init__(
72+
self,
73+
column_spec: list[tuple[str, Callable[..., str]]],
74+
lines: list[list[str]],
75+
filename: str = "",
76+
include_table: Optional[str] = None,
77+
sort: bool = True,
78+
input_items: Optional[Iterable[Any]] = None,
79+
):
80+
if column_spec:
81+
assert input_items
82+
assert (column_spec and not lines) or (lines and not column_spec)
83+
stem = os.getcwd().split("source")[0]
84+
self.path = os.path.join(stem, "source", filename)
85+
self.filename = filename
86+
self.include_table = include_table
87+
self.sort = sort
88+
self.input_items = input_items
89+
self.column_spec = column_spec
90+
self.lines = lines
91+
self._df = pd.DataFrame()
2792

28-
def __getattr__(self, key: str) -> list:
29-
return self.fields[key]
93+
@property
94+
def headers(self) -> list[str]:
95+
return [column_name for column_name, _ in self.column_spec]
3096

31-
def __init__(self, *args, **kwargs):
32-
stem = os.getcwd().split("source")[0]
33-
self.path = os.path.join(stem, "source", self.filename)
34-
self.fields = defaultdict(list)
97+
@property
98+
def fields(self) -> pd.DataFrame:
99+
return self._df
35100

36-
parent_directory = pathlib.Path(self.path).parent
37-
parent_directory.mkdir(exist_ok=True, parents=True)
38-
self.get_lines(*args, **kwargs)
39-
self.write_table()
101+
def generate_lines_and_write_table(self) -> None:
102+
df = _generate_table(
103+
input_items=self.input_items or [],
104+
column_spec=self.column_spec,
105+
)
40106

41-
def _run_method(self, method, *args, **kwargs):
42-
sanitized = self.sanitize_name(method)
43-
meth = getattr(self, sanitized)
44-
val = meth(*args, **kwargs)
45-
self.fields[method].append(val)
46-
return val
47-
48-
@staticmethod
49-
def sanitize_name(name):
50-
return "_" + name.replace(" ", "_").replace("/", "_").lower()
51-
52-
def get_lines(self, *args, **kwargs):
53-
lines = []
54-
for items in self._set_up_input():
55-
try:
56-
lines.append(self.get_line(*items))
57-
except TypeError: # one argument
58-
lines.append(self.get_line(items))
59-
if self.sort:
60-
lines = sorted(lines)
107+
lines = df.values.tolist()
108+
lines = sorted(lines) if self.sort else lines
61109
self.lines = lines
110+
self._df = df
111+
self.write_table()
62112

63-
def get_line(self, *args):
64-
line = []
65-
for p in self.preprocess:
66-
self._run_method(p, *args)
67-
for h in self.headings:
68-
line.append(self._run_method(h, *args))
69-
for p in self.postprocess:
70-
self._run_method(p, *args)
71-
return line
72-
73-
def write_table(self):
74-
with open(self.path, "w") as f:
75-
f.write(f'..\n Generated by {sys.argv[0].split("/")[-1]}\n\n')
76-
if self.include_table:
77-
f.write(f".. table:: {self.include_table}\n\n")
78-
tabled = tabulate.tabulate(
79-
self.lines, headers=self.headings, tablefmt="rst"
80-
)
81-
if self.include_table:
82-
tabled = textwrap.indent(tabled, " ")
83-
f.write(tabled)
84-
print("Wrote ", self.filename)
85-
86-
# ==== HELPER FUNCTIONS ==== #
87-
88-
@staticmethod
89-
def sphinx_class(klass, tilde=True):
90-
prefix = "~" if tilde else ""
91-
return ":class:`{}{}.{}`".format(
92-
prefix, klass.__module__, klass.__name__
113+
def write_table(self) -> None:
114+
write_table(
115+
path=self.path,
116+
headers=self.headers,
117+
lines=self.lines,
118+
include_table=self.include_table,
93119
)
94120

95-
@staticmethod
96-
def sphinx_meth(meth, tilde=True):
97-
prefix = "~" if tilde else ""
98-
return ":meth:`{}{}.{}`".format(
99-
prefix, meth.__module__, meth.__qualname__
100-
)
101121

102-
@staticmethod
103-
def sphinx_ref(txt: str, label: str = None, suffix: str = "") -> str:
104-
return f":ref:`{txt} <{label}{suffix}>`"
122+
# ==== HELPER FUNCTIONS ==== #
123+
124+
125+
def sphinx_class(*, klass: Type[Any], tilde: bool = True) -> str:
126+
prefix = "~" if tilde else ""
127+
return f":class:`{prefix}{klass.__module__}.{klass.__name__}`"
128+
129+
130+
def sphinx_method(*, method: Callable[..., Any], tilde: bool = True) -> str:
131+
prefix = "~" if tilde else ""
132+
return ":meth:`{}{}.{}`".format(
133+
prefix, method.__module__, method.__qualname__
134+
)
135+
136+
137+
def sphinx_ref(
138+
*, txt: str, label: Optional[str] = None, suffix: str = ""
139+
) -> str:
140+
return f":ref:`{txt} <{label}{suffix}>`"
141+
105142

106-
@staticmethod
107-
def sphinx_link(txt):
108-
return "`{}`_".format(txt)
143+
def sphinx_link(*, txt: str) -> str:
144+
return f"`{txt}`_"

doc/source/scripts/core.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
from __future__ import print_function
2-
3-
import os
4-
5-
import tabulate
61
from MDAnalysis import _TOPOLOGY_ATTRS
72

83
# ====== TOPOLOGY ====== #
@@ -75,11 +70,9 @@
7570
for c in _TOPOLOGY_ATTRS.values()
7671
}
7772

78-
base_attrnames = set(
79-
["atomattrs", "residueattrs", "segmentattrs", "topologyattrs"]
80-
)
73+
base_attrnames = {"atomattrs", "residueattrs", "segmentattrs", "topologyattrs"}
8174

82-
core_attrnames = set(["indices", "resindices", "segindices"])
75+
core_attrnames = {"indices", "resindices", "segindices"}
8376

8477
BASE_ATTRS = {k: v for k, v in ATTRS.items() if k in base_attrnames}
8578

@@ -90,12 +83,6 @@
9083
}
9184

9285
TOPOLOGY_CLS = sorted(
93-
set(
94-
[
95-
x
96-
for x in _TOPOLOGY_ATTRS.values()
97-
if x.attrname in NON_CORE_ATTRS.keys()
98-
]
99-
),
86+
{x for x in _TOPOLOGY_ATTRS.values() if x.attrname in NON_CORE_ATTRS},
10087
key=lambda x: x.attrname,
10188
)

0 commit comments

Comments
 (0)