From a952cb2456bba8acea11099fa103c8b1dc54d159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Doma=C5=84ski?= Date: Mon, 9 Oct 2023 20:48:55 +0100 Subject: [PATCH] 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> --- .pre-commit-config.yaml | 24 ++ doc/source/conf.py | 9 +- doc/source/scripts/.coverage | Bin 0 -> 53248 bytes doc/source/scripts/base.py | 200 +++++++++------ doc/source/scripts/core.py | 19 +- .../scripts/gen_format_overview_classes.py | 236 +++++++++++------- doc/source/scripts/gen_selection_exporters.py | 61 +++-- doc/source/scripts/gen_standard_selections.py | 70 ++++-- .../scripts/gen_topology_groupmethods.py | 66 +++-- .../scripts/gen_topologyattr_defaults.py | 81 +++--- .../scripts/gen_topologyparser_attrs.py | 232 ++++++++++------- doc/source/scripts/gen_unit_tables.py | 11 +- .../test_gen_format_overview_classes.py | 6 +- .../snapshot/test_gen_selection_exporters.py | 3 +- .../snapshot/test_gen_standard_selections.py | 42 +++- .../test_gen_topology_groupmethods.py | 3 +- .../test_gen_topologyattr_defaults.py | 3 +- .../snapshot/test_gen_topologyparser_attrs.py | 17 +- maintainer/update_json_stubs_sitemap.py | 6 +- mypy.ini | 4 + pyproject.toml | 11 + 21 files changed, 682 insertions(+), 422 deletions(-) create mode 100644 doc/source/scripts/.coverage create mode 100644 mypy.ini create mode 100644 pyproject.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 279473110..6c10d68b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,6 +2,12 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.2.0 hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-shebang-scripts-are-executable + - id: check-symlinks - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace @@ -17,3 +23,21 @@ repos: hooks: - id: black args: [--line-length=79] +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 + hooks: + - id: mypy + args: [--ignore-missing-imports, --install-types, --non-interactive, --strict] + exclude: "/tests/.*\\.py|clean_example_notebooks.py|update_json_stubs_sitemap.py" +- repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + additional_dependencies: [ + 'flake8-simplify', + 'flake8-comprehensions', + 'flake8-bugbear', + 'darglint', + 'flake8-pep585', + 'Flake8-pyproject', + ] diff --git a/doc/source/conf.py b/doc/source/conf.py index 3b30a2e54..c5ffa9b01 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -23,7 +23,7 @@ project = "MDAnalysis User Guide" -def sort_authors(filename): +def sort_authors(filename: str) -> list[str]: """Generate sorted list of authors from AUTHORS""" authors = [] with open(filename, "r") as f: @@ -144,7 +144,7 @@ def sort_authors(filename): # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] -html_css_files = [] +html_css_files: list[str] = [] # Custom sidebar templates, maps document names to template names. # alabaster sidebars @@ -170,8 +170,9 @@ def sort_authors(filename): } # nbsphinx -html_js_files = [ - # "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js", +html_js_files: list[str] = [ + # 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js', + # DEFAULT_EMBED_REQUIREJS_URL, ] ipython_warning_is_error = False diff --git a/doc/source/scripts/.coverage b/doc/source/scripts/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..94227d53309382f9cb9c0bb877ff5080f447ad67 GIT binary patch literal 53248 zcmeI4Z)_Y#6~K3I*SEKKdpBp2a7j(^R!VV9{0BD@B_NbKsZ~Wu34yezf_lAoH@4Tl zyFKsj`H!Fq*H%ghLP#x;HbT&EAR!_6Kr2CMkU&U)1XO&WLbMS9)%gH~5(v;FHSg^o zpU?5NmCkNc=dJE$W_M@aoA;YHGds7p`^lq+EsyKtwo^7eeUFe8L{Yd)*9AdP;hTnU zv?-8~qaBFF73o$&EzTI{lly~*B3jwhAemM!85?%QNK z1^A?ztbqd!Qx1-Upn~lbvG#e#tQ01>+ozk$78Y!%=e!qK>)7Q%&(=#;#W%C!TApQB zbUx1uHIEn9G_Vh$MH|3dX${C4LxaYGlA*2@bQ*=%C>drC3y(wPDt3gmQ4m!_VZY9m zS~(BMr)uRYHV%{s6nl27lC8>mc4+LIZksjQvSDvib4JLqMT1hciZxT?%Z=ZsH%j*T zW>oAx9gP4FxIGom?cE{@;FWydohiZfmSNUB+ut{!@r@w}v`0G=O0K(GJTc=t3gR*H zmgg=f)F!7{KHS1}@O*2(!CunrB-o2eCEXtPy9}1rgT?`GUfE{`BN(QG9Zk2xj8N0J z)!VSLPzOC*$RSMWuT(qHJ&vM6{-~-{=U|4v&9SK}#NR8)4 zx?8!ut#xNS4Ds$7VInhA!dV^$CqA^NF zeIz^s=JYG^+;CSbbHaSaP?$*66=^3vqSg(xb3 z>OGUDi=&X+qvvhA#LY@DH$aJlF6gve-LJ#-v(mnX40i!#Ow9K9b&4hz9OjQfr;GX* zB+6y8QVi!SoQ4CbF!p2TL_NVE0tNLR=#(yW(N!`dq2XB^u_E~cRbYZr%Vsg??${_R zB+gCp>%;}K(cPxUJ*&)h%;HzZcdR+n)e8=XlQ5U|_DHYi2H0B05Qex)PT-|ny<1dr zyLXHATG$zbamH|AQZJiPwZq+5las7E+3=SmFyO+~&PqQ)b}Mk;ZlD1Nx1a^{0fi`6 zc&(G{#Ra~|t_bj>>oT z5dk7V1c(3;AOb{y2oM1xKm>>Y5g-EZOafX=x|KzL@%R7}rLJ7~CxAN#?ijc;30B3} z1%X{)|9)p0NVO&cM1Tko0U|&IhyW2F0z`la5CI}U1R?^ObgLNs3J^<5U26Cv0N(%a zR6iuJlWb?^_nA|fcc-6D@7K<2B`ulyO6rc}kCUHK|DiqsS!g2yM1Tko0U|&Ih`>!v zV6;;f>cfNgx$unY;1oR53NK=qvz=+T3h%5Ky!YHwI=kBC2yf2eV89`1J%r^=7lq|xAnahfHA4@AH>tpD_6&U8$gIWZ zj6%tDU5>k0h69FPX1c(3;AOb`{5Dd9KEVg^S)_VQl`6;Sz1+5j zH!^Fr*3`P&wR$CsB0vO)01+SpM1Tko z0U|&IhyW2F0-J+CDl3cX_UvC?zWCa2RE5D#mVIq^cLox7W#m78d1>j|($dk{*JkH_ zw6x{LBQHIDt;fD{B&Mbz!`Aef#s7USqe1e$n*5)W30*k9^th5jA*nNxmW0GDN%^Du z_FUShDk>gU&nT$`B(^8y$)DSo-<4Ki=RL}q)2C0LefDZcI*z;XNB@4$=TH83X{XYG zWjfACoiZeL%2QXDU|U>T`q+KSRtXNhUwZXd+J%e9&%Jc*(sO5(#lyG6;OGZq^5s`Y z-QGQCpE(7N3!fklH^CRm<+!G`1k*X?2TeO z)q@BS0U|&IhyW2F0z`la5CI}U1c(3;*a!l6|DV?X8$p(25&>Y5x8*(;Pd~T>^u1X|I6$Z_E)yZ&a)Tc8GzrhU$bAp8v)L;AHs70--9f)5dk7V z1c(3;AOb{y2oM1xKm>>Y5g-B^PeAH~_w`Fz3>XF(4AK~A7^E;rVxVG>z(BzujzI?o OG6oU`F%0ni|NjSpLenGw literal 0 HcmV?d00001 diff --git a/doc/source/scripts/base.py b/doc/source/scripts/base.py index d1da2a533..18b68e6c5 100644 --- a/doc/source/scripts/base.py +++ b/doc/source/scripts/base.py @@ -1,15 +1,65 @@ -from __future__ import print_function - import os import pathlib import sys import textwrap -from collections import defaultdict +from collections.abc import Callable, Iterable +from typing import Any, Optional, Type +import pandas as pd import tabulate -class TableWriter(object): +def _run_method(method: Callable[..., str], *args: Any) -> str: + val = method(*args) + return val + + +def _generate_row( + *, column_spec: list[tuple[str, Callable[..., str]]], args: Iterable[Any] +) -> dict[str, str]: + row = {} + for heading, method in column_spec: + val = _run_method(method, *args) + row[heading] = val + return row + + +def _generate_table( + *, + input_items: Iterable[Any], + column_spec: list[tuple[str, Callable[..., str]]], +) -> pd.DataFrame: + rows = [] + for args in input_items: + if not isinstance(args, Iterable): + args = [args] + line = _generate_row(column_spec=column_spec, args=args) + rows.append(line) + df = pd.DataFrame(rows) + return df + + +def write_table( + *, + path: str, + headers: list[str], + lines: list[list[str]], + include_table: Optional[str] = None, +) -> None: + parent_directory = pathlib.Path(path).parent + parent_directory.mkdir(exist_ok=True, parents=True) + with open(path, "w") as f: + f.write(f'..\n Generated by {sys.argv[0].split("/")[-1]}\n\n') + if include_table: + f.write(f".. table:: {include_table}\n\n") + tabled = tabulate.tabulate(lines, headers=headers, tablefmt="rst") + if include_table: + tabled = textwrap.indent(tabled, " ") + f.write(tabled) + print("Wrote ", path) + + +class TableWriter: """ For writing tables with easy column switching. @@ -18,91 +68,77 @@ class TableWriter(object): Filename relative to source. """ - filename = "" - include_table = False - headings = [] - preprocess = [] - postprocess = [] - sort = True + def __init__( + self, + column_spec: list[tuple[str, Callable[..., str]]], + lines: list[list[str]], + filename: str = "", + include_table: Optional[str] = None, + sort: bool = True, + input_items: Optional[Iterable[Any]] = None, + ): + if column_spec: + assert input_items + assert (column_spec and not lines) or (lines and not column_spec) + stem = os.getcwd().split("source")[0] + self.path = os.path.join(stem, "source", filename) + self.filename = filename + self.include_table = include_table + self.sort = sort + self.input_items = input_items + self.column_spec = column_spec + self.lines = lines + self._df = pd.DataFrame() - def __getattr__(self, key: str) -> list: - return self.fields[key] + @property + def headers(self) -> list[str]: + return [column_name for column_name, _ in self.column_spec] - def __init__(self, *args, **kwargs): - stem = os.getcwd().split("source")[0] - self.path = os.path.join(stem, "source", self.filename) - self.fields = defaultdict(list) + @property + def fields(self) -> pd.DataFrame: + return self._df - parent_directory = pathlib.Path(self.path).parent - parent_directory.mkdir(exist_ok=True, parents=True) - self.get_lines(*args, **kwargs) - self.write_table() + def generate_lines_and_write_table(self) -> None: + df = _generate_table( + input_items=self.input_items or [], + column_spec=self.column_spec, + ) - def _run_method(self, method, *args, **kwargs): - sanitized = self.sanitize_name(method) - meth = getattr(self, sanitized) - val = meth(*args, **kwargs) - self.fields[method].append(val) - return val - - @staticmethod - def sanitize_name(name): - return "_" + name.replace(" ", "_").replace("/", "_").lower() - - def get_lines(self, *args, **kwargs): - lines = [] - for items in self._set_up_input(): - try: - lines.append(self.get_line(*items)) - except TypeError: # one argument - lines.append(self.get_line(items)) - if self.sort: - lines = sorted(lines) + lines = df.values.tolist() + lines = sorted(lines) if self.sort else lines self.lines = lines + self._df = df + self.write_table() - def get_line(self, *args): - line = [] - for p in self.preprocess: - self._run_method(p, *args) - for h in self.headings: - line.append(self._run_method(h, *args)) - for p in self.postprocess: - self._run_method(p, *args) - return line - - def write_table(self): - with open(self.path, "w") as f: - f.write(f'..\n Generated by {sys.argv[0].split("/")[-1]}\n\n') - if self.include_table: - f.write(f".. table:: {self.include_table}\n\n") - tabled = tabulate.tabulate( - self.lines, headers=self.headings, tablefmt="rst" - ) - if self.include_table: - tabled = textwrap.indent(tabled, " ") - f.write(tabled) - print("Wrote ", self.filename) - - # ==== HELPER FUNCTIONS ==== # - - @staticmethod - def sphinx_class(klass, tilde=True): - prefix = "~" if tilde else "" - return ":class:`{}{}.{}`".format( - prefix, klass.__module__, klass.__name__ + def write_table(self) -> None: + write_table( + path=self.path, + headers=self.headers, + lines=self.lines, + include_table=self.include_table, ) - @staticmethod - def sphinx_meth(meth, tilde=True): - prefix = "~" if tilde else "" - return ":meth:`{}{}.{}`".format( - prefix, meth.__module__, meth.__qualname__ - ) - @staticmethod - def sphinx_ref(txt: str, label: str = None, suffix: str = "") -> str: - return f":ref:`{txt} <{label}{suffix}>`" +# ==== HELPER FUNCTIONS ==== # + + +def sphinx_class(*, klass: Type[Any], tilde: bool = True) -> str: + prefix = "~" if tilde else "" + return f":class:`{prefix}{klass.__module__}.{klass.__name__}`" + + +def sphinx_method(*, method: Callable[..., Any], tilde: bool = True) -> str: + prefix = "~" if tilde else "" + return ":meth:`{}{}.{}`".format( + prefix, method.__module__, method.__qualname__ + ) + + +def sphinx_ref( + *, txt: str, label: Optional[str] = None, suffix: str = "" +) -> str: + return f":ref:`{txt} <{label}{suffix}>`" + - @staticmethod - def sphinx_link(txt): - return "`{}`_".format(txt) +def sphinx_link(*, txt: str) -> str: + return f"`{txt}`_" diff --git a/doc/source/scripts/core.py b/doc/source/scripts/core.py index 59341d88e..0867b6c31 100644 --- a/doc/source/scripts/core.py +++ b/doc/source/scripts/core.py @@ -1,8 +1,3 @@ -from __future__ import print_function - -import os - -import tabulate from MDAnalysis import _TOPOLOGY_ATTRS # ====== TOPOLOGY ====== # @@ -75,11 +70,9 @@ for c in _TOPOLOGY_ATTRS.values() } -base_attrnames = set( - ["atomattrs", "residueattrs", "segmentattrs", "topologyattrs"] -) +base_attrnames = {"atomattrs", "residueattrs", "segmentattrs", "topologyattrs"} -core_attrnames = set(["indices", "resindices", "segindices"]) +core_attrnames = {"indices", "resindices", "segindices"} BASE_ATTRS = {k: v for k, v in ATTRS.items() if k in base_attrnames} @@ -90,12 +83,6 @@ } TOPOLOGY_CLS = sorted( - set( - [ - x - for x in _TOPOLOGY_ATTRS.values() - if x.attrname in NON_CORE_ATTRS.keys() - ] - ), + {x for x in _TOPOLOGY_ATTRS.values() if x.attrname in NON_CORE_ATTRS}, key=lambda x: x.attrname, ) diff --git a/doc/source/scripts/gen_format_overview_classes.py b/doc/source/scripts/gen_format_overview_classes.py index 9c03d44e3..e733f0a9b 100644 --- a/doc/source/scripts/gen_format_overview_classes.py +++ b/doc/source/scripts/gen_format_overview_classes.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """ Generates: - ../formats/format_overview.txt : table of format overview @@ -7,124 +6,181 @@ """ from collections import defaultdict +from collections.abc import Iterable +from typing import Any, Final, Literal, Type +import base from base import TableWriter from core import DESCRIPTIONS from MDAnalysis import _CONVERTERS, _PARSERS, _READERS, _SINGLEFRAME_WRITERS -FILE_TYPES = defaultdict(dict) +HANDLER_T = Literal[ + "Coordinate reader", "Coordinate writer", "Topology parser", "Converter" +] -for clstype, dct in ( +PAIR_T = tuple[HANDLER_T, dict[str, Any]] + +handlers: Iterable[PAIR_T] = [ ("Coordinate reader", _READERS), ("Coordinate writer", _SINGLEFRAME_WRITERS), ("Topology parser", _PARSERS), ("Converter", _CONVERTERS), -): - for fmt, klass in dct.items(): - if fmt in ("CHAIN", "MEMORY", "MINIMAL", "NULL"): +] + +FILE_TYPES: dict[str, dict[HANDLER_T, Any]] = defaultdict(dict) + +for handler, dct in handlers: + for format, klass in dct.items(): + if format in {"CHAIN", "MEMORY", "MINIMAL", "NULL"}: continue # get their own pages - FILE_TYPES[fmt][clstype] = klass + FILE_TYPES[format][handler] = klass -sorted_types = sorted(FILE_TYPES.items()) + +SORTED_FILE_TYPES: Final = sorted(FILE_TYPES.items()) SUCCESS = "\u2713" # checkmark FAIL = "" -class FormatOverview(TableWriter): - filename = "formats/format_overview.txt" - include_table = "Table of all supported formats in MDAnalysis" - preprocess = ["keys"] - headings = [ - "File type", - "Description", - "Topology", - "Coordinates", - "Read", - "Write", - ] - - def _set_up_input(self): - return sorted_types - - def _file_type(self, fmt, handlers): - return self.sphinx_ref(fmt, self.keys[-1], suffix="-format") +def _create_key(fmt: str, handlers: dict[HANDLER_T, Type[Any]]) -> str: + if fmt in DESCRIPTIONS: + key = fmt + else: + key = list(handlers.values())[0].format[0] - def _keys(self, fmt, handlers): - if fmt in DESCRIPTIONS: + # raise an informative error + if key not in DESCRIPTIONS: key = fmt - else: - key = list(handlers.values())[0].format[0] + return key - # raise an informative error - if key not in DESCRIPTIONS: - key = fmt - return key - def _description(self, fmt, handlers): - return DESCRIPTIONS[self.keys[-1]] +def _file_type( + fmt: str, handlers: dict[HANDLER_T, Type[Any]], key: str +) -> str: + return base.sphinx_ref(txt=fmt, label=key, suffix="-format") - def _topology(self, fmt, handlers): - if "Topology parser" in handlers: - return SUCCESS - return FAIL - - def _coordinates(self, fmt, handlers): - if "Coordinate reader" in handlers: - return SUCCESS - return FAIL - def _read(self, fmt, handlers): - return SUCCESS +def _description( + fmt: str, handlers: dict[HANDLER_T, Type[Any]], key: str +) -> str: + return DESCRIPTIONS[key] - def _write(self, fmt, handlers): - if "Coordinate writer" in handlers: - return SUCCESS - if "Converter" in handlers: - return SUCCESS - return FAIL +class FormatOverview: + def __init__(self) -> None: + def _topology( + fmt: str, handlers: dict[HANDLER_T, Type[Any]], key: str + ) -> str: + return SUCCESS if "Topology parser" in handlers else FAIL -class CoordinateReaders(FormatOverview): - filename = "formats/coordinate_readers.txt" - include_table = ( - "Table of supported coordinate readers and the information read" - ) - headings = ["File type", "Description", "Velocities", "Forces"] + def _coordinates( + fmt: str, handlers: dict[HANDLER_T, Type[Any]], key: str + ) -> str: + if "Coordinate reader" in handlers: + return SUCCESS + return FAIL - def _set_up_input(self): - return [(x, y) for x, y in sorted_types if "Coordinate reader" in y] - - def _velocities(self, fmt, handlers): - if handlers["Coordinate reader"].units.get("velocity", None): - return SUCCESS - return FAIL - - def _forces(self, fmt, handlers): - if handlers["Coordinate reader"].units.get("force", None): + def _read( + fmt: str, handlers: dict[HANDLER_T, Type[Any]], key: str + ) -> str: return SUCCESS - return FAIL - - -class SphinxClasses(TableWriter): - filename = "formats/reference/classes/{}.txt" - def __init__(self, fmt): - self.filename = self.filename.format(fmt) - self.fmt = fmt - super(SphinxClasses, self).__init__() - - def get_lines(self): - lines = [] - for label, klass in sorted(FILE_TYPES[self.fmt].items()): - lines.append( - ["**{}**".format(label), self.sphinx_class(klass, tilde=False)] - ) - self.lines = lines + def _write( + fmt: str, handlers: dict[HANDLER_T, Type[Any]], key: str + ) -> str: + if "Coordinate writer" in handlers: + return SUCCESS + if "Converter" in handlers: + return SUCCESS + return FAIL + + input_items = [ + (format, handlers, _create_key(format, handlers)) + for format, handlers in SORTED_FILE_TYPES + ] + + self.table_writer = TableWriter( + filename="formats/format_overview.txt", + include_table="Table of all supported formats in MDAnalysis", + column_spec=[ + ("File type", _file_type), + ("Description", _description), + ("Topology", _topology), + ("Coordinates", _coordinates), + ("Read", _read), + ("Write", _write), + ], + input_items=input_items, + lines=[], + ) + self.table_writer.generate_lines_and_write_table() + self.table_writer.fields["keys"] = list(zip(*input_items))[2] + + +class CoordinateReaders: + def __init__(self) -> None: + def _velocities( + fmt: str, handlers: dict[HANDLER_T, Type[Any]], key: str + ) -> str: + if handlers["Coordinate reader"].units.get("velocity"): + return SUCCESS + return FAIL + + def _forces( + fmt: str, handlers: dict[HANDLER_T, Type[Any]], key: str + ) -> str: + if handlers["Coordinate reader"].units.get("force"): + return SUCCESS + return FAIL + + input_items = [ + (format, handlers, _create_key(format, handlers)) + for format, handlers in SORTED_FILE_TYPES + if "Coordinate reader" in handlers + ] + self.table_writer = TableWriter( + filename="formats/coordinate_readers.txt", + include_table="Table of supported coordinate readers and the information read", + input_items=input_items, + column_spec=[ + ("File type", _file_type), + ("Description", _description), + ("Velocities", _velocities), + ("Forces", _forces), + ], + lines=[], + ) + self.table_writer.generate_lines_and_write_table() + + +class SphinxClasses: + def __init__(self, fmt: str): + def _custom_get_lines() -> list[list[str]]: + lines = [] + for label, klass in sorted(FILE_TYPES[fmt].items()): + lines.append( + [ + f"**{label}**", + base.sphinx_class(klass=klass, tilde=False), + ] + ) + return lines + + lines = _custom_get_lines() + self.table_writer = TableWriter( + filename=f"formats/reference/classes/{fmt}.txt", + lines=lines, + column_spec=[], + ) + self.table_writer.write_table() + + +def main() -> None: + overview = FormatOverview() + CoordinateReaders() + for key in set(overview.table_writer.fields["keys"]): + SphinxClasses(key) if __name__ == "__main__": - ov = FormatOverview() - CoordinateReaders() - for key in set(ov.fields["keys"]): - SphinxClasses(key) + main() diff --git a/doc/source/scripts/gen_selection_exporters.py b/doc/source/scripts/gen_selection_exporters.py index f4ffbab04..76acd5762 100644 --- a/doc/source/scripts/gen_selection_exporters.py +++ b/doc/source/scripts/gen_selection_exporters.py @@ -1,11 +1,12 @@ -#!/usr/bin/env python """ Generates: - ../formats/selection_exporters.txt """ +import base from base import TableWriter from MDAnalysis import _SELECTION_WRITERS +from MDAnalysis.selections.base import SelectionWriterBase SELECTION_DESCRIPTIONS = { "vmd": "VMD macros, available in Representations", @@ -16,31 +17,39 @@ } -class SelectionExporterWriter(TableWriter): - headings = ["Program", "Extension", "Description", "Class"] - filename = "formats/selection_exporter_formats.txt" - include_table = "Supported selection exporters" - sort = True - - def _set_up_input(self): - return set(_SELECTION_WRITERS.values()) - - def _program(self, klass): - # classes have multiple formats. - # First tends to be the program name, second is extension - p = klass.format - if isinstance(p, (list, tuple)): - p = p[0] - return self.sphinx_link(p) - - def _extension(self, klass): - return klass.ext - - def _description(self, klass): - return SELECTION_DESCRIPTIONS[klass.ext] - - def _class(self, klass): - return self.sphinx_class(klass, tilde=False) +class SelectionExporterWriter: + def __init__(self) -> None: + def _program(klass: SelectionWriterBase) -> str: + # classes have multiple formats. + # First tends to be the program name, second is extension + p = klass.format + if isinstance(p, (list, tuple)): + p = p[0] + return base.sphinx_link(txt=p) + + def _extension(klass: SelectionWriterBase) -> str: + return klass.ext # type: ignore + + def _description(klass: SelectionWriterBase) -> str: + return SELECTION_DESCRIPTIONS[klass.ext] + + def _class(klass: SelectionWriterBase) -> str: + return base.sphinx_class(klass=klass, tilde=False) + + self.table_writer = TableWriter( + filename="formats/selection_exporter_formats.txt", + include_table="Supported selection exporters", + sort=True, + input_items=set(_SELECTION_WRITERS.values()), + column_spec=[ + ("Program", _program), + ("Extension", _extension), + ("Description", _description), + ("Class", _class), + ], + lines=[], + ) + self.table_writer.generate_lines_and_write_table() if __name__ == "__main__": diff --git a/doc/source/scripts/gen_standard_selections.py b/doc/source/scripts/gen_standard_selections.py index a9304fbc0..c54b0c7f4 100755 --- a/doc/source/scripts/gen_standard_selections.py +++ b/doc/source/scripts/gen_standard_selections.py @@ -8,42 +8,70 @@ - nucleobase atoms - nucleic sugar atoms """ +from typing import Any + from base import TableWriter from MDAnalysis.core import selection as sel -def chunk_list(lst, n=8): - return [lst[i : i + n] for i in range(0, len(lst), n)] +def _chunk_list(lst: list[str], chunk_size: int = 8) -> list[list[str]]: + return [lst[i : i + chunk_size] for i in range(0, len(lst), chunk_size)] + +# override get_lines as there are no headings +def _custom_get_lines( + *, + klass: sel.Selection, + attribute_name: str, + sort: bool = False, + chunk_size: int = 8 +) -> list[list[str]]: + selected = getattr(klass, attribute_name) + if sort: + selected = sorted(selected) -class StandardSelectionTable(TableWriter): - sort = False - filename = "generated/selections/{}.txt" + table = _chunk_list(list(selected), chunk_size=chunk_size) + return table - def __init__(self, filename, *args, **kwargs): - self.filename = self.filename.format(filename) - super(StandardSelectionTable, self).__init__(*args, **kwargs) - # override get_lines as there are no headings - def get_lines(self, klass, attr, sort=False, n=8): - selected = getattr(klass, attr) - if sort: - selected = sorted(selected) +class StandardSelectionTable: + def __init__(self, filename: str, *args: Any, **kwargs: Any) -> None: - table = chunk_list(list(selected), n=n) - self.lines = table + lines = _custom_get_lines(*args, **kwargs) + self.table_writer = TableWriter( + sort=False, + filename="generated/selections/{}.txt".format(filename), + lines=lines, + column_spec=[], + ) + self.table_writer.write_table() if __name__ == "__main__": - StandardSelectionTable("protein", sel.ProteinSelection, "prot_res", True) StandardSelectionTable( - "protein_backbone", sel.BackboneSelection, "bb_atoms" + "protein", + klass=sel.ProteinSelection, + attribute_name="prot_res", + sort=True, + ) + StandardSelectionTable( + "protein_backbone", + klass=sel.BackboneSelection, + attribute_name="bb_atoms", + ) + StandardSelectionTable( + "nucleic", klass=sel.NucleicSelection, attribute_name="nucl_res" + ) + StandardSelectionTable( + "nucleic_backbone", + klass=sel.NucleicBackboneSelection, + attribute_name="bb_atoms", ) - StandardSelectionTable("nucleic", sel.NucleicSelection, "nucl_res") StandardSelectionTable( - "nucleic_backbone", sel.NucleicBackboneSelection, "bb_atoms" + "base", klass=sel.BaseSelection, attribute_name="base_atoms" ) - StandardSelectionTable("base", sel.BaseSelection, "base_atoms") StandardSelectionTable( - "nucleic_sugar", sel.NucleicSugarSelection, "sug_atoms" + "nucleic_sugar", + klass=sel.NucleicSugarSelection, + attribute_name="sug_atoms", ) diff --git a/doc/source/scripts/gen_topology_groupmethods.py b/doc/source/scripts/gen_topology_groupmethods.py index f6eb92ec9..b6b32cf9e 100644 --- a/doc/source/scripts/gen_topology_groupmethods.py +++ b/doc/source/scripts/gen_topology_groupmethods.py @@ -1,36 +1,56 @@ -#!/usr/bin/env python """ Generate groupmethods.txt: A table of transplanted methods. """ -from collections import defaultdict +from collections.abc import Callable +from typing import Any +import base from base import TableWriter from core import TOPOLOGY_CLS from MDAnalysis.core.groups import GroupBase - - -class TransplantedMethods(TableWriter): - headings = ["Method", "Description", "Requires"] - filename = "generated/topology/groupmethods.txt" - - def _set_up_input(self): - items = [] - for klass in TOPOLOGY_CLS: - for name, method in klass.transplants[GroupBase]: - items.append([name, klass, method]) - return [x[1:] for x in sorted(items)] - - def _method(self, klass, method): - return self.sphinx_meth(method) - - def _description(self, klass, method): - return " ".join(method.__doc__.split(".\n")[0].split()) - - def _requires(self, klass, method): - return klass.attrname +from MDAnalysis.core.topologyattrs import TopologyAttr + + +class TransplantedMethods: + def __init__(self) -> None: + def _generate_input_items() -> list[tuple[TopologyAttr, Any]]: + items = [] + for klass in TOPOLOGY_CLS: + for name, method in klass.transplants[GroupBase]: + items.append((name, klass, method)) + return [x[1:] for x in sorted(items)] + + def _method( + klass: TopologyAttr, method: Callable[[TopologyAttr], str] + ) -> str: + return base.sphinx_method(method=method) + + def _description( + klass: TopologyAttr, method: Callable[[TopologyAttr], str] + ) -> str: + assert method.__doc__ + return " ".join(method.__doc__.split(".\n")[0].split()) + + def _requires( + klass: TopologyAttr, method: Callable[[TopologyAttr], str] + ) -> str: + return klass.attrname # type: ignore + + input_items = _generate_input_items() + self.table_writer = TableWriter( + filename="generated/topology/groupmethods.txt", + input_items=input_items, + column_spec=[ + ("Method", _method), + ("Description", _description), + ("Requires", _requires), + ], + lines=[], + ) + self.table_writer.generate_lines_and_write_table() if __name__ == "__main__": diff --git a/doc/source/scripts/gen_topologyattr_defaults.py b/doc/source/scripts/gen_topologyattr_defaults.py index 1990ea6ce..4887cfa1d 100644 --- a/doc/source/scripts/gen_topologyattr_defaults.py +++ b/doc/source/scripts/gen_topologyattr_defaults.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """ Generate topology_defaults.txt: @@ -7,7 +6,12 @@ from base import TableWriter from core import TOPOLOGY_CLS -from MDAnalysis.core.topologyattrs import AtomAttr, ResidueAttr, SegmentAttr +from MDAnalysis.core.topologyattrs import ( + AtomAttr, + ResidueAttr, + SegmentAttr, + TopologyAttr, +) DEFAULTS = { "resids": "continuous sequence from 1 to n_residues", @@ -16,42 +20,51 @@ } -class TopologyDefaults(TableWriter): - filename = "generated/topology/defaults.txt" - headings = ("Atom", "AtomGroup", "default", "level", "type") - sort = True +class TopologyDefaults: + def __init__(self) -> None: + def _atom(klass: TopologyAttr) -> str: + return klass.attrname # type: ignore - def _set_up_input(self): - return TOPOLOGY_CLS + def _atomgroup(klass: TopologyAttr) -> str: + return klass.singular # type: ignore - def _atom(self, klass): - return klass.attrname + def _default(klass: TopologyAttr) -> str: + try: + return DEFAULTS[klass.attrname] + except KeyError: + try: + return repr(klass._gen_initial_values(1, 1, 1)[0]) + except NotImplementedError: + return "No default values" - def _atomgroup(self, klass): - return klass.singular + def _level(klass: TopologyAttr) -> str: + if issubclass(klass, AtomAttr): + level = "atom" + elif issubclass(klass, ResidueAttr): + level = "residue" + elif issubclass(klass, SegmentAttr): + level = "segment" + else: + raise ValueError + return level - def _default(self, klass): - try: - return DEFAULTS[klass.attrname] - except KeyError: - try: - return repr(klass._gen_initial_values(1, 1, 1)[0]) - except NotImplementedError: - return "No default values" - - def _level(self, klass): - if issubclass(klass, AtomAttr): - level = "atom" - elif issubclass(klass, ResidueAttr): - level = "residue" - elif issubclass(klass, SegmentAttr): - level = "segment" - else: - raise ValueError - return level - - def _type(self, klass): - return klass.dtype + def _type(klass: TopologyAttr) -> str: + return klass.dtype # type: ignore + + self.table_writer = TableWriter( + filename="generated/topology/defaults.txt", + sort=True, + input_items=TOPOLOGY_CLS, + column_spec=[ + ("Atom", _atom), + ("AtomGroup", _atomgroup), + ("default", _default), + ("level", _level), + ("type", _type), + ], + lines=[], + ) + self.table_writer.generate_lines_and_write_table() if __name__ == "__main__": diff --git a/doc/source/scripts/gen_topologyparser_attrs.py b/doc/source/scripts/gen_topologyparser_attrs.py index e648fb737..ebeefbe76 100755 --- a/doc/source/scripts/gen_topologyparser_attrs.py +++ b/doc/source/scripts/gen_topologyparser_attrs.py @@ -7,12 +7,13 @@ This script imports the testsuite, which tests these. """ -import os -import sys from collections import defaultdict +from typing import Any +import base from base import TableWriter from core import DESCRIPTIONS, NON_CORE_ATTRS +from MDAnalysis.topology.base import TopologyReaderBase from MDAnalysisTests.topology.base import mandatory_attrs from MDAnalysisTests.topology.test_crd import TestCRDParser from MDAnalysisTests.topology.test_dlpoly import ( @@ -64,110 +65,165 @@ ) -MANDATORY_ATTRS = set(mandatory_attrs) - - -parser_attrs = {} - - -for p in PARSER_TESTS: - e, g = set(p.expected_attrs) - MANDATORY_ATTRS, set(p.guessed_attrs) - # clunky hack for PDB - if p is TestPDBParser: - e.add("elements") - parser_attrs[p.parser] = (e, g) - - -class TopologyParsers(TableWriter): - headings = [ - "Format", - "Description", - "Attributes read", - "Attributes guessed", - ] - preprocess = ["keys"] - filename = "formats/topology_parsers.txt" - include_table = ( - "Table of supported topology parsers and the attributes read" - ) - sort = True - - def __init__(self): - self.attrs = defaultdict(set) - super(TopologyParsers, self).__init__() - - def _set_up_input(self): - return [[x, *y] for x, y in parser_attrs.items()] - - def get_line(self, parser, expected, guessed): - line = super(TopologyParsers, self).get_line(parser, expected, guessed) - for a in expected | guessed: - self.attrs[a].add(self.fields["Format"][-1]) - return line - - def _keys(self, parser, *args): - f = parser.format - if isinstance(f, (list, tuple)): - key = f[0] - label = ", ".join(f) - else: - key = label = f - return (key, label) +def create_parser_attributes() -> dict[Any, tuple[set[str], set[str]]]: + parser_attrs = {} + for test_parser_class in PARSER_TESTS: + expected = set(test_parser_class.expected_attrs) - set(mandatory_attrs) + guessed = set(test_parser_class.guessed_attrs) + # clunky hack for PDB + if test_parser_class is TestPDBParser: + expected.add("elements") + parser_attrs[test_parser_class.parser] = (expected, guessed) + return parser_attrs + + +class TopologyParsers: + def __init__(self) -> None: + def _keys(parser: TopologyReaderBase) -> tuple[str, str]: + f = parser.format + if isinstance(f, (list, tuple)): + key = f[0] + label = ", ".join(f) + else: + key = label = f + return (key, label) + + def _description( + parser: TopologyReaderBase, + expected: set[str], + guessed: set[str], + key_label: tuple[str, str], + ) -> str: + key, label = key_label + return DESCRIPTIONS[key] + + def _format( + parser: TopologyReaderBase, + expected: set[str], + guessed: set[str], + key_label: tuple[str, str], + ) -> str: + key, label = key_label + return base.sphinx_ref(txt=label, label=key, suffix="-format") + + def _attributes_read( + parser: TopologyReaderBase, + expected: set[str], + guessed: set[str], + key_label: tuple[str, str], + ) -> str: + vals = sorted(expected - guessed) + return ", ".join(vals) + + def _attributes_guessed( + parser: TopologyReaderBase, + expected: set[str], + guessed: set[str], + key_label: tuple[str, str], + ) -> str: + return ", ".join(sorted(guessed)) + + parser_attrs = create_parser_attributes() + input_items = [ + [parser, expected, guessed, _keys(parser=parser)] + for parser, (expected, guessed) in parser_attrs.items() + ] + self.table_writer = TableWriter( + filename="formats/topology_parsers.txt", + include_table="Table of supported topology parsers and the attributes read", + sort=True, + input_items=input_items, + lines=[], + column_spec=[ + ("Format", _format), + ("Description", _description), + ("Attributes read", _attributes_read), + ("Attributes guessed", _attributes_guessed), + ], + ) + self.table_writer.generate_lines_and_write_table() - def _description(self, *args): - key, label = self.keys[-1] - return DESCRIPTIONS[key] - def _format(self, *args): - key, label = self.keys[-1] - return self.sphinx_ref(label, key, suffix="-format") +def get_format_attrs(topology_parsers: TopologyParsers) -> dict[str, set[str]]: + attrs = defaultdict(set) + writer = topology_parsers.table_writer + assert writer.input_items + for format, (_, expected, guessed, _) in zip( + writer.fields["Format"], + writer.input_items, + ): + for attribute in expected | guessed: + attrs[attribute].add(format) + return attrs - def _attributes_read(self, parser, expected, guessed): - vals = sorted(expected - guessed) - return ", ".join(vals) - def _attributes_guessed(self, parser, expected, guessed): - return ", ".join(sorted(guessed)) +class TopologyAttrs: + def __init__(self, attrs: dict[str, set[str]]) -> None: + def _atom(name: str, singular: str, description: str) -> str: + return singular + def _atomgroup(name: str, singular: str, description: str) -> str: + return name -class TopologyAttrs(TableWriter): - headings = ("Atom", "AtomGroup", "Description", "Supported formats") - filename = "generated/topology/topologyattrs.txt" + def _description(name: str, singular: str, description: str) -> str: + return description - def __init__(self, attrs): - self.attrs = attrs - super(TopologyAttrs, self).__init__() + def _supported_formats( + name: str, singular: str, description: str + ) -> str: + return ", ".join(sorted(attrs[name])) - def _set_up_input(self): - return sorted( + input_items = sorted( [x, *y] for x, y in NON_CORE_ATTRS.items() - if x not in MANDATORY_ATTRS + if x not in set(mandatory_attrs) + ) + self.table_writer = TableWriter( + filename="generated/topology/topologyattrs.txt", + lines=[], + input_items=input_items, + column_spec=[ + ("Atom", _atom), + ("AtomGroup", _atomgroup), + ("Description", _description), + ("Supported formats", _supported_formats), + ], ) + self.table_writer.generate_lines_and_write_table() - def _atom(self, name, singular, *args): - return singular - def _atomgroup(self, name, *args): - return name +class ConnectivityAttrs: + def __init__(self, attrs: dict[str, set[str]]) -> None: + def _atom(name: str) -> str: + return name - def _description(self, name, singular, description): - return description + def _atomgroup(name: str) -> str: + return name - def _supported_formats(self, name, singular, description): - return ", ".join(sorted(self.attrs[name])) + def _supported_formats(name: str) -> str: + return ", ".join(sorted(attrs[name])) + input_items = [("bonds",), ("angles",), ("dihedrals",), ("impropers",)] + + self.table_writer = TableWriter( + filename="generated/topology/connectivityattrs.txt", + input_items=input_items, + lines=[], + column_spec=[ + ("Atom", _atom), + ("AtomGroup", _atomgroup), + ("Supported formats", _supported_formats), + ], + ) + self.table_writer.generate_lines_and_write_table() -class ConnectivityAttrs(TopologyAttrs): - headings = ("Atom", "AtomGroup", "Supported formats") - filename = "generated/topology/connectivityattrs.txt" - def _set_up_input(self): - inp = [[x] * 3 for x in "bonds angles dihedrals impropers".split()] - return inp +def main() -> None: + top = TopologyParsers() + topology_attrs = get_format_attrs(top) + TopologyAttrs(topology_attrs) + ConnectivityAttrs(topology_attrs) if __name__ == "__main__": - top = TopologyParsers() - TopologyAttrs(top.attrs) - ConnectivityAttrs(top.attrs) + main() diff --git a/doc/source/scripts/gen_unit_tables.py b/doc/source/scripts/gen_unit_tables.py index c18ab5f4e..8fffdf7c6 100755 --- a/doc/source/scripts/gen_unit_tables.py +++ b/doc/source/scripts/gen_unit_tables.py @@ -10,19 +10,20 @@ from MDAnalysis.units import conversion_factor -def write_unit_table(filename): +def write_unit_table( + filename: str, +) -> list[tuple[str, list[tuple[str, float]]]]: headings = ["Unit", "Conversion factor"] - table_heading = ".. table:: {}" tables = [] for data_type, items in conversion_factor.items(): - lines = sorted(list(items.items())) + lines = sorted(items.items()) tables.append((data_type, lines)) parent_directory = pathlib.Path(__file__).parent.parent parent_directory.mkdir(exist_ok=True, parents=True) - filename = parent_directory / filename + output = parent_directory / filename - with filename.open("w") as f: + with output.open("w") as f: f.write(".. Generated by {}\n".format(sys.argv[0])) for data_type, lines in tables: line = "\n" + "-" * len(data_type) + "\n" diff --git a/doc/source/scripts/tests/snapshot/test_gen_format_overview_classes.py b/doc/source/scripts/tests/snapshot/test_gen_format_overview_classes.py index 835e5d039..647f4f89b 100644 --- a/doc/source/scripts/tests/snapshot/test_gen_format_overview_classes.py +++ b/doc/source/scripts/tests/snapshot/test_gen_format_overview_classes.py @@ -15,16 +15,16 @@ def test_FILE_TYPES(): def test_FormatOverview(snapshot): with patch("builtins.open"): ov = FormatOverview() - assert ov.lines == snapshot + assert ov.table_writer.lines == snapshot def test_CoordinateReaders(snapshot): with patch("builtins.open"): cr = CoordinateReaders() - assert cr.lines == snapshot + assert cr.table_writer.lines == snapshot def test_SphinxClasses(snapshot): with patch("builtins.open"): sc = SphinxClasses("PDB") - assert sc.lines == snapshot + assert sc.table_writer.lines == snapshot diff --git a/doc/source/scripts/tests/snapshot/test_gen_selection_exporters.py b/doc/source/scripts/tests/snapshot/test_gen_selection_exporters.py index 34d8a5616..fae8d5d68 100644 --- a/doc/source/scripts/tests/snapshot/test_gen_selection_exporters.py +++ b/doc/source/scripts/tests/snapshot/test_gen_selection_exporters.py @@ -1,10 +1,9 @@ from unittest.mock import patch from gen_selection_exporters import SelectionExporterWriter -from MDAnalysis.core import selection as sel def test_SelectionExporterWriter(snapshot): with patch("builtins.open"): se = SelectionExporterWriter() - assert se.lines == snapshot + assert se.table_writer.lines == snapshot diff --git a/doc/source/scripts/tests/snapshot/test_gen_standard_selections.py b/doc/source/scripts/tests/snapshot/test_gen_standard_selections.py index dc4529de3..ff47ea158 100644 --- a/doc/source/scripts/tests/snapshot/test_gen_standard_selections.py +++ b/doc/source/scripts/tests/snapshot/test_gen_standard_selections.py @@ -7,46 +7,64 @@ def test_StandardSelectionTable_protein(snapshot): with patch("builtins.open"): ss = StandardSelectionTable( - "protein", sel.ProteinSelection, "prot_res", True + "protein", + klass=sel.ProteinSelection, + attribute_name="prot_res", + sort=True, ) - assert ss.lines == snapshot + assert ss.table_writer.lines == snapshot def test_StandardSelectionTable_protein_backbone(snapshot): with patch("builtins.open"): ss = StandardSelectionTable( - "protein_backbone", sel.BackboneSelection, "bb_atoms", True + "protein_backbone", + klass=sel.BackboneSelection, + attribute_name="bb_atoms", + sort=True, ) - assert ss.lines == snapshot + assert ss.table_writer.lines == snapshot def test_StandardSelectionTable_nucleic(snapshot): with patch("builtins.open"): ss = StandardSelectionTable( - "nucleic", sel.NucleicSelection, "nucl_res", True + "nucleic", + klass=sel.NucleicSelection, + attribute_name="nucl_res", + sort=True, ) - assert ss.lines == snapshot + assert ss.table_writer.lines == snapshot def test_StandardSelectionTable_nucleic_backbone(snapshot): with patch("builtins.open"): ss = StandardSelectionTable( - "nucleic_backbone", sel.NucleicBackboneSelection, "bb_atoms", True + "nucleic_backbone", + klass=sel.NucleicBackboneSelection, + attribute_name="bb_atoms", + sort=True, ) - assert ss.lines == snapshot + assert ss.table_writer.lines == snapshot def test_StandardSelectionTable_base(snapshot): with patch("builtins.open"): ss = StandardSelectionTable( - "base", sel.BaseSelection, "base_atoms", True + "base", + klass=sel.BaseSelection, + attribute_name="base_atoms", + sort=True, ) - assert ss.lines == snapshot + assert ss.table_writer.lines == snapshot def test_StandardSelectionTable_nucleic_sugar(snapshot): with patch("builtins.open"): ss = StandardSelectionTable( - "nucleic_sugar", sel.NucleicSugarSelection, "sug_atoms", True + "nucleic_sugar", + klass=sel.NucleicSugarSelection, + attribute_name="sug_atoms", + sort=True, ) - assert ss.lines == snapshot + assert ss.table_writer.lines == snapshot diff --git a/doc/source/scripts/tests/snapshot/test_gen_topology_groupmethods.py b/doc/source/scripts/tests/snapshot/test_gen_topology_groupmethods.py index f585f489d..741bd1263 100644 --- a/doc/source/scripts/tests/snapshot/test_gen_topology_groupmethods.py +++ b/doc/source/scripts/tests/snapshot/test_gen_topology_groupmethods.py @@ -1,10 +1,9 @@ from unittest.mock import patch from gen_topology_groupmethods import TransplantedMethods -from MDAnalysis.core import selection as sel def test_TransplantedMethods(snapshot): with patch("builtins.open"): tm = TransplantedMethods() - assert tm.lines == snapshot + assert tm.table_writer.lines == snapshot diff --git a/doc/source/scripts/tests/snapshot/test_gen_topologyattr_defaults.py b/doc/source/scripts/tests/snapshot/test_gen_topologyattr_defaults.py index bf461d275..47b817b6d 100644 --- a/doc/source/scripts/tests/snapshot/test_gen_topologyattr_defaults.py +++ b/doc/source/scripts/tests/snapshot/test_gen_topologyattr_defaults.py @@ -1,10 +1,9 @@ from unittest.mock import patch from gen_topologyattr_defaults import TopologyDefaults -from MDAnalysis.core import selection as sel def test_TopologyDefaults(snapshot): with patch("builtins.open"): td = TopologyDefaults() - assert td.lines == snapshot + assert td.table_writer.lines == snapshot diff --git a/doc/source/scripts/tests/snapshot/test_gen_topologyparser_attrs.py b/doc/source/scripts/tests/snapshot/test_gen_topologyparser_attrs.py index f8d95a152..bf94a3aed 100644 --- a/doc/source/scripts/tests/snapshot/test_gen_topologyparser_attrs.py +++ b/doc/source/scripts/tests/snapshot/test_gen_topologyparser_attrs.py @@ -4,31 +4,34 @@ ConnectivityAttrs, TopologyAttrs, TopologyParsers, + get_format_attrs, ) -from MDAnalysis.core import selection as sel def test_TopologyParsers_lines(snapshot): with patch("builtins.open"): top = TopologyParsers() - assert top.lines == snapshot + assert top.table_writer.lines == snapshot def test_TopologyParsers_attrs(snapshot): with patch("builtins.open"): top = TopologyParsers() - assert top.attrs == snapshot + attrs = get_format_attrs(top) + assert attrs == snapshot def test_TopologyAttrs(snapshot): with patch("builtins.open"): top = TopologyParsers() - ta = TopologyAttrs(top.attrs) - assert ta.lines == snapshot + attrs = get_format_attrs(top) + ta = TopologyAttrs(attrs) + assert ta.table_writer.lines == snapshot def test_ConnectivityAttrs(snapshot): with patch("builtins.open"): top = TopologyParsers() - ca = ConnectivityAttrs(top.attrs) - assert ca.lines == snapshot + attrs = get_format_attrs(top) + ca = ConnectivityAttrs(attrs) + assert ca.table_writer.lines == snapshot diff --git a/maintainer/update_json_stubs_sitemap.py b/maintainer/update_json_stubs_sitemap.py index 921dea637..b38549d3c 100644 --- a/maintainer/update_json_stubs_sitemap.py +++ b/maintainer/update_json_stubs_sitemap.py @@ -16,11 +16,7 @@ import shutil import textwrap import xml.etree.ElementTree as ET - -try: - from urllib.request import Request, urlopen -except ImportError: - from urllib2 import Request, urlopen +from urllib.request import Request, urlopen URL = os.environ["URL"] VERSION = os.environ["VERSION"] diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..0c95025b6 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,4 @@ +[mypy] +mypy_path = doc/source/scripts/ +exclude = + conf.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..3790c0f32 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[tool.isort] +profile = "black" + +[tool.flake8] +max-line-length = 130 +extend-ignore = ["E203", "PEA001"] +exclude = [ + "doc/source/conf.py", + "doc/source/scripts/clean_example_notebooks.py", + "maintainer/update_json_stubs_sitemap.py", +]