diff --git a/CHANGES b/CHANGES index a9bff30a46d..432c55ba696 100644 --- a/CHANGES +++ b/CHANGES @@ -63,12 +63,16 @@ Dependencies Incompatible changes -------------------- +* #5497: Do not include MathJax.js and jsmath.js unless it is really needed + Deprecated ---------- Features added -------------- +* #5471: Show appropriate deprecation warnings + Bugs fixed ---------- @@ -80,6 +84,10 @@ Bugs fixed * #5495: csv-table directive with file option in included file is broken (refs: #4821) * #5498: autodoc: unable to find type hints for a ``functools.partial`` +* #5480: autodoc: unable to find type hints for unresolvable Forward references +* #5419: incompatible math_block node has been generated +* #5548: Fix ensuredir() in case of pre-existing file +* #5549: graphviz Correctly deal with non-existing static dir Testing -------- @@ -206,7 +214,7 @@ Deprecated * :confval:`autodoc_default_flags` is deprecated * quickstart: ``--epub`` option becomes default, so it is deprecated * Drop function based directive support. For now, Sphinx only supports class - based directives. + based directives (see :class:`~Directive`) * ``sphinx.util.docutils.directive_helper()`` is deprecated * ``sphinx.cmdline`` is deprecated * ``sphinx.make_mode`` is deprecated diff --git a/setup.cfg b/setup.cfg index cb199d257c6..bd52e0cec65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,6 +55,7 @@ strict_optional = False [tool:pytest] filterwarnings = + all ignore::DeprecationWarning:docutils.io [coverage:run] diff --git a/sphinx/__init__.py b/sphinx/__init__.py index cdd3b0418a0..eb05bcd834e 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -29,8 +29,7 @@ # by default, all DeprecationWarning under sphinx package will be emit. # Users can avoid this by using environment variable: PYTHONWARNINGS= if 'PYTHONWARNINGS' not in os.environ: - warnings.filterwarnings('default', - category=RemovedInNextVersionWarning, module='sphinx') + warnings.filterwarnings('default', category=RemovedInNextVersionWarning) # docutils.io using mode='rU' for open warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index b695337516d..1b1bf2d88da 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -205,7 +205,7 @@ def __getitem__(self, key): if key == 'latex' and 'latex' not in self.attributes: warnings.warn("math node for Sphinx was replaced by docutils'. " "Therefore please use ``node.astext()`` to get an equation instead.", - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.astext() else: return nodes.math.__getitem__(self, key) @@ -224,7 +224,7 @@ def __getitem__(self, key): if key == 'latex' and 'latex' not in self.attributes: warnings.warn("displaymath node for Sphinx was replaced by docutils'. " "Therefore please use ``node.astext()`` to get an equation instead.", - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.astext() else: return nodes.math_block.__getitem__(self, key) diff --git a/sphinx/application.py b/sphinx/application.py index dfc286dc995..3bb093e12da 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -403,7 +403,7 @@ def import_object(self, objname, source=None): """ warnings.warn('app.import_object() is deprecated. ' 'Use sphinx.util.add_object_type() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return import_object(objname, source=None) # event interface @@ -595,7 +595,7 @@ def enumerable_nodes(self): # type: () -> Dict[nodes.Node, Tuple[unicode, TitleGetter]] warnings.warn('app.enumerable_nodes() is deprecated. ' 'Use app.get_domain("std").enumerable_nodes instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.registry.enumerable_nodes def add_directive(self, name, obj, content=None, arguments=None, override=False, **options): # NOQA @@ -727,7 +727,7 @@ def override_domain(self, domain): """ warnings.warn('app.override_domain() is deprecated. ' 'Use app.add_domain() with override option instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) self.registry.add_domain(domain, override=True) def add_directive_to_domain(self, domain, name, obj, has_content=None, argument_spec=None, @@ -924,7 +924,7 @@ def add_javascript(self, filename, **kwargs): """An alias of :meth:`add_js_file`.""" warnings.warn('The app.add_javascript() is deprecated. ' 'Please use app.add_js_file() instead.', - RemovedInSphinx40Warning) + RemovedInSphinx40Warning, stacklevel=2) self.add_js_file(filename, **kwargs) def add_js_file(self, filename, **kwargs): @@ -999,7 +999,7 @@ def add_stylesheet(self, filename, alternate=False, title=None): """An alias of :meth:`add_css_file`.""" warnings.warn('The app.add_stylesheet() is deprecated. ' 'Please use app.add_css_file() instead.', - RemovedInSphinx40Warning) + RemovedInSphinx40Warning, stacklevel=2) attributes = {} # type: Dict[unicode, unicode] if alternate: diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 9689b34ffa7..761dece92ce 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -121,14 +121,14 @@ def insert(self, index, obj): # type: (int, unicode) -> None warnings.warn('builder.script_files is deprecated. ' 'Please use app.add_js_file() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) super(JSContainer, self).insert(index, obj) def extend(self, other): # type: ignore # type: (List[unicode]) -> None warnings.warn('builder.script_files is deprecated. ' 'Please use app.add_js_file() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) for item in other: self.append(item) @@ -136,7 +136,7 @@ def __iadd__(self, other): # type: ignore # type: (List[unicode]) -> JSContainer warnings.warn('builder.script_files is deprecated. ' 'Please use app.add_js_file() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) for item in other: self.append(item) return self @@ -1104,7 +1104,7 @@ def warn(*args, **kwargs): """Simple warn() wrapper for themes.""" warnings.warn('The template function warn() was deprecated. ' 'Use warning() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) logger.warning(*args, **kwargs) return '' # return empty string ctx['warn'] = warn diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index 10835d2e7e6..252f95bbc92 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -27,26 +27,26 @@ def handle_exception(app, args, exception, stderr=sys.stderr): # type: (Sphinx, Any, Union[Exception, KeyboardInterrupt], IO) -> None warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) build.handle_exception(app, args, exception, stderr) def jobs_argument(value): # type: (str) -> int warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return build.jobs_argument(value) def get_parser(): # type: () -> argparse.ArgumentParser warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return build.get_parser() def main(argv=sys.argv[1:]): # type: ignore # type: (List[unicode]) -> int warnings.warn('sphinx.cmdline module is deprecated. Use sphinx.cmd.build instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return build.main(argv) diff --git a/sphinx/config.py b/sphinx/config.py index c3a81291f88..45ba9f7d981 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -161,7 +161,7 @@ def __init__(self, *args): # old style arguments: (dirname, filename, overrides, tags) warnings.warn('The argument of Config() class has been changed. ' 'Use Config.read() to read configuration from conf.py.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) dirname, filename, overrides, tags = args if dirname is None: config = {} # type: Dict[unicode, Any] @@ -199,13 +199,13 @@ def read(cls, confdir, overrides=None, tags=None): def check_types(self): # type: () -> None warnings.warn('Config.check_types() is deprecated. Use check_confval_types() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) check_confval_types(None, self) def check_unicode(self): # type: () -> None warnings.warn('Config.check_unicode() is deprecated. Use check_unicode() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) check_unicode(self) def convert_overrides(self, name, value): diff --git a/sphinx/deprecation.py b/sphinx/deprecation.py index ea43a71f4bc..d5827ddc007 100644 --- a/sphinx/deprecation.py +++ b/sphinx/deprecation.py @@ -39,25 +39,25 @@ def __init__(self, data, message, warning): def __setitem__(self, key, value): # type: (unicode, Any) -> None - warnings.warn(self.message, self.warning) + warnings.warn(self.message, self.warning, stacklevel=2) super(DeprecatedDict, self).__setitem__(key, value) def setdefault(self, key, default=None): # type: (unicode, Any) -> None - warnings.warn(self.message, self.warning) + warnings.warn(self.message, self.warning, stacklevel=2) return super(DeprecatedDict, self).setdefault(key, default) def __getitem__(self, key): # type: (unicode) -> None - warnings.warn(self.message, self.warning) + warnings.warn(self.message, self.warning, stacklevel=2) return super(DeprecatedDict, self).__getitem__(key) def get(self, key, default=None): # type: (unicode, Any) -> None - warnings.warn(self.message, self.warning) + warnings.warn(self.message, self.warning, stacklevel=2) return super(DeprecatedDict, self).get(key, default) def update(self, other=None): # type: ignore # type: (Dict) -> None - warnings.warn(self.message, self.warning) + warnings.warn(self.message, self.warning, stacklevel=2) super(DeprecatedDict, self).update(other) diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index e14a451d9e5..e9f178b5e07 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -61,7 +61,7 @@ def run(self): # type: () -> List[nodes.Node] warnings.warn('highlightlang directive is deprecated. ' 'Please use highlight directive instead.', - RemovedInSphinx40Warning) + RemovedInSphinx40Warning, stacklevel=2) return Highlight.run(self) diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py index c88481746e5..d56f45e0eb2 100644 --- a/sphinx/domains/math.py +++ b/sphinx/domains/math.py @@ -44,6 +44,7 @@ class MathDomain(Domain): initial_data = { 'objects': {}, # labelid -> (docname, eqno) + 'has_equations': {}, # docname -> bool } # type: Dict[unicode, Dict[unicode, Tuple[unicode, int]]] dangling_warnings = { 'eq': 'equation not found: %(target)s', @@ -56,18 +57,30 @@ class MathDomain(Domain): 'numref': MathReferenceRole(), } + def process_doc(self, env, docname, document): + # type: (BuildEnvironment, unicode, nodes.Node) -> None + def math_node(node): + return isinstance(node, (nodes.math, nodes.math_block)) + + self.data['has_equations'][docname] = any(document.traverse(math_node)) + def clear_doc(self, docname): # type: (unicode) -> None for equation_id, (doc, eqno) in list(self.data['objects'].items()): if doc == docname: del self.data['objects'][equation_id] + self.data['has_equations'].pop(docname, None) + def merge_domaindata(self, docnames, otherdata): # type: (Iterable[unicode], Dict) -> None for labelid, (doc, eqno) in otherdata['objects'].items(): if doc in docnames: self.data['objects'][labelid] = (doc, eqno) + for docname in docnames: + self.data['has_equations'][docname] = otherdata['has_equations'][docname] + def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA assert typ in ('eq', 'numref') @@ -122,6 +135,10 @@ def get_next_equation_number(self, docname): targets = [eq for eq in self.data['objects'].values() if eq[0] == docname] return len(targets) + 1 + def has_equations(self): + # type: () -> bool + return any(self.data['has_equations'].values()) + def setup(app): # type: (Sphinx) -> Dict[unicode, Any] @@ -130,7 +147,7 @@ def setup(app): return { 'version': 'builtin', - 'env_version': 1, + 'env_version': 2, 'parallel_read_safe': True, 'parallel_write_safe': True, } diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 2bbc92f88b2..8cc0adb7ffc 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -952,7 +952,7 @@ def get_figtype(self, node): """ warnings.warn('StandardDomain.get_figtype() is deprecated. ' 'Please use get_enumerable_node_type() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.get_enumerable_node_type(node) def get_fignumber(self, env, builder, figtype, docname, target_node): diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 52cf7fa95de..ba137e30d8c 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -677,32 +677,32 @@ def check_consistency(self): def update(self, config, srcdir, doctreedir): # type: (Config, unicode, unicode) -> List[unicode] warnings.warn('env.update() is deprecated. Please use builder.read() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.app.builder.read() def _read_serial(self, docnames, app): # type: (List[unicode], Sphinx) -> None warnings.warn('env._read_serial() is deprecated. Please use builder.read() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.app.builder._read_serial(docnames) def _read_parallel(self, docnames, app, nproc): # type: (List[unicode], Sphinx, int) -> None warnings.warn('env._read_parallel() is deprecated. Please use builder.read() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.app.builder._read_parallel(docnames, nproc) def read_doc(self, docname, app=None): # type: (unicode, Sphinx) -> None warnings.warn('env.read_doc() is deprecated. Please use builder.read_doc() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) self.app.builder.read_doc(docname) def write_doctree(self, docname, doctree): # type: (unicode, nodes.Node) -> None warnings.warn('env.write_doctree() is deprecated. ' 'Please use builder.write_doctree() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) self.app.builder.write_doctree(docname, doctree) @property @@ -710,7 +710,7 @@ def _nitpick_ignore(self): # type: () -> List[unicode] warnings.warn('env._nitpick_ignore is deprecated. ' 'Please use config.nitpick_ignore instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.config.nitpick_ignore @staticmethod @@ -718,7 +718,7 @@ def load(f, app=None): # type: (IO, Sphinx) -> BuildEnvironment warnings.warn('BuildEnvironment.load() is deprecated. ' 'Please use pickle.load() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) try: env = pickle.load(f) except Exception as exc: @@ -735,7 +735,7 @@ def loads(cls, string, app=None): # type: (bytes, Sphinx) -> BuildEnvironment warnings.warn('BuildEnvironment.loads() is deprecated. ' 'Please use pickle.loads() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) io = BytesIO(string) return cls.load(io, app) @@ -744,7 +744,7 @@ def frompickle(cls, filename, app): # type: (unicode, Sphinx) -> BuildEnvironment warnings.warn('BuildEnvironment.frompickle() is deprecated. ' 'Please use pickle.load() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) with open(filename, 'rb') as f: return cls.load(f, app) @@ -753,7 +753,7 @@ def dump(env, f): # type: (BuildEnvironment, IO) -> None warnings.warn('BuildEnvironment.dump() is deprecated. ' 'Please use pickle.dump() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) pickle.dump(env, f, pickle.HIGHEST_PROTOCOL) @classmethod @@ -761,7 +761,7 @@ def dumps(cls, env): # type: (BuildEnvironment) -> unicode warnings.warn('BuildEnvironment.dumps() is deprecated. ' 'Please use pickle.dumps() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) io = BytesIO() cls.dump(env, io) return io.getvalue() @@ -770,7 +770,7 @@ def topickle(self, filename): # type: (unicode) -> None warnings.warn('env.topickle() is deprecated. ' 'Please use pickle.dump() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) with open(filename, 'wb') as f: self.dump(self, f) @@ -779,14 +779,14 @@ def versionchanges(self): # type: () -> Dict[unicode, List[Tuple[unicode, unicode, int, unicode, unicode, unicode]]] # NOQA warnings.warn('env.versionchanges() is deprecated. ' 'Please use ChangeSetDomain instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.domaindata['changeset']['changes'] def note_versionchange(self, type, version, node, lineno): # type: (unicode, unicode, nodes.Node, int) -> None warnings.warn('env.note_versionchange() is deprecated. ' 'Please use ChangeSetDomain.note_changeset() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) node['type'] = type node['version'] = version node.line = lineno diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 8411e1ac45a..ded95b2e35b 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1455,7 +1455,7 @@ def merge_autodoc_default_flags(app, config): # logger.warning() on 3.0.0 release. warnings.warn('autodoc_default_flags is now deprecated. ' 'Please use autodoc_default_options instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) for option in config.autodoc_default_flags: if isinstance(option, string_types): diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 57d63f120e9..ae10b2c25b4 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -26,7 +26,7 @@ from sphinx.locale import _, __ from sphinx.util import logging from sphinx.util.docutils import SphinxDirective -from sphinx.util.fileutil import copy_asset_file +from sphinx.util.fileutil import copy_asset from sphinx.util.i18n import search_image_for_language from sphinx.util.osutil import ensuredir, ENOENT, EPIPE, EINVAL @@ -412,7 +412,7 @@ def on_build_finished(app, exc): if exc is None: src = path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css') dst = path.join(app.outdir, '_static') - copy_asset_file(src, dst) + copy_asset(src, dst) def setup(app): diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py index 0ee42050c62..3babd408e7d 100644 --- a/sphinx/ext/jsmath.py +++ b/sphinx/ext/jsmath.py @@ -21,6 +21,7 @@ # For type annotation from typing import Any, Dict # NOQA from sphinx.application import Sphinx # NOQA + from sphinx.environment import BuildEnvironment # NOQA def html_visit_math(self, node): @@ -58,14 +59,16 @@ def html_visit_displaymath(self, node): raise nodes.SkipNode -def builder_inited(app): - # type: (Sphinx) -> None +def install_jsmath(app, env): + # type: (Sphinx, BuildEnvironment) -> None if app.builder.format != 'html' or app.builder.math_renderer_name != 'jsmath': # type: ignore # NOQA - pass - elif not app.config.jsmath_path: + return + if not app.config.jsmath_path: raise ExtensionError('jsmath_path config value must be set for the ' 'jsmath extension to work') - if app.builder.format == 'html': + + if env.get_domain('math').has_equations(): # type: ignore + # Enable jsmath only if equations exists app.builder.add_js_file(app.config.jsmath_path) # type: ignore @@ -76,5 +79,5 @@ def setup(app): (html_visit_displaymath, None)) app.add_config_value('jsmath_path', '', False) - app.connect('builder-inited', builder_inited) + app.connect('env-check-consistency', install_jsmath) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index 421ea6a0379..076edaf3718 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -32,14 +32,14 @@ class MathDirective(MathDirectiveBase): def run(self): warnings.warn('sphinx.ext.mathbase.MathDirective is moved to ' 'sphinx.directives.patches package.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return super(MathDirective, self).run() def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]): warnings.warn('sphinx.ext.mathbase.math_role() is deprecated. ' 'Please use docutils.parsers.rst.roles.math_role() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return math_role_base(role, rawtext, text, lineno, inliner, options, content) @@ -47,7 +47,7 @@ def get_node_equation_number(writer, node): # type: (Writer, nodes.Node) -> unicode warnings.warn('sphinx.ext.mathbase.get_node_equation_number() is moved to ' 'sphinx.util.math package.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) from sphinx.util.math import get_node_equation_number return get_node_equation_number(writer, node) @@ -56,7 +56,7 @@ def wrap_displaymath(text, label, numbering): # type: (unicode, unicode, bool) -> unicode warnings.warn('sphinx.ext.mathbase.wrap_displaymath() is moved to ' 'sphinx.util.math package.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) from sphinx.util.math import wrap_displaymath return wrap_displaymath(text, label, numbering) @@ -67,7 +67,7 @@ def is_in_section_title(node): from sphinx.util.nodes import traverse_parent warnings.warn('is_in_section_title() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) for ancestor in traverse_parent(node): if isinstance(ancestor, nodes.title) and \ @@ -80,6 +80,6 @@ def setup_math(app, htmlinlinevisitors, htmldisplayvisitors): # type: (Sphinx, Tuple[Callable, Callable], Tuple[Callable, Callable]) -> None warnings.warn('setup_math() is deprecated. ' 'Please use app.add_html_math_renderer() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) app.add_html_math_renderer('unknown', htmlinlinevisitors, htmldisplayvisitors) diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py index 50a2bae4d57..21406c4515b 100644 --- a/sphinx/ext/mathjax.py +++ b/sphinx/ext/mathjax.py @@ -24,6 +24,7 @@ # For type annotation from typing import Any, Dict # NOQA from sphinx.application import Sphinx # NOQA + from sphinx.environment import BuildEnvironment # NOQA def html_visit_math(self, node): @@ -68,14 +69,16 @@ def html_visit_displaymath(self, node): raise nodes.SkipNode -def builder_inited(app): - # type: (Sphinx) -> None +def install_mathjax(app, env): + # type: (Sphinx, BuildEnvironment) -> None if app.builder.format != 'html' or app.builder.math_renderer_name != 'mathjax': # type: ignore # NOQA - pass - elif not app.config.mathjax_path: + return + if not app.config.mathjax_path: raise ExtensionError('mathjax_path config value must be set for the ' 'mathjax extension to work') - if app.builder.format == 'html': + + if env.get_domain('math').has_equations(): # type: ignore + # Enable mathjax only if equations exists options = {'async': 'async'} if app.config.mathjax_options: options.update(app.config.mathjax_options) @@ -101,6 +104,6 @@ def setup(app): app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html') app.add_config_value('mathjax_display', [r'\[', r'\]'], 'html') app.add_config_value('mathjax_config', None, 'html') - app.connect('builder-inited', builder_inited) + app.connect('env-check-consistency', install_mathjax) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 38c34733b19..e3d21e36165 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -252,7 +252,7 @@ def migrate_viewcode_import(app, config): if config.viewcode_import is not None: warnings.warn('viewcode_import was renamed to viewcode_follow_imported_members. ' 'Please update your configuration.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) def setup(app): diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index e61a891a021..b12fa6e2e36 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -91,7 +91,7 @@ def __init__(self, dest='html', stylename='sphinx', trim_doctest_flags=None): self.trim_doctest_flags = trim_doctest_flags if trim_doctest_flags is not None: warnings.warn('trim_doctest_flags option for PygmentsBridge is now deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) def get_formatter(self, **kwargs): # type: (Any) -> Formatter @@ -101,7 +101,7 @@ def get_formatter(self, **kwargs): def unhighlighted(self, source): # type: (unicode) -> unicode warnings.warn('PygmentsBridge.unhighlighted() is now deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) if self.dest == 'html': return '
' + htmlescape(source) + '
\n' else: diff --git a/sphinx/io.py b/sphinx/io.py index f18a1e95fc9..e9328790495 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -129,13 +129,13 @@ def set_lineno_for_reporter(self, lineno): # type: (int) -> None """Stores the source line number of original text.""" warnings.warn('SphinxI18nReader.set_lineno_for_reporter() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) @property def line(self): # type: () -> int warnings.warn('SphinxI18nReader.line is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return 0 diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index a09447cb112..34aafffe6b7 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -182,7 +182,7 @@ def mygettext(string): not bound yet at that time. """ warnings.warn('sphinx.locale.mygettext() is deprecated. Please use `_()` instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return _(string) @@ -192,7 +192,7 @@ def lazy_gettext(string): # if isinstance(string, _TranslationProxy): # return string warnings.warn('sphinx.locale.laxy_gettext() is deprecated. Please use `_()` instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return _TranslationProxy(mygettext, string) # type: ignore @@ -327,7 +327,7 @@ def gettext(message, *args): def l_(*args): warnings.warn('sphinx.locale.l_() is deprecated. Please use `_()` instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return _(*args) diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py index 4510fc0ebd9..ffc609a031e 100644 --- a/sphinx/make_mode.py +++ b/sphinx/make_mode.py @@ -28,12 +28,12 @@ class Make(make_mode.Make): def __init__(self, *args): warnings.warn('sphinx.make_mode.Make is deprecated. ' 'Please use sphinx.cmd.make_mode.Make instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) super(Make, self).__init__(*args) def run_make_mode(args): warnings.warn('sphinx.make_mode.run_make_mode() is deprecated. ' 'Please use sphinx.cmd.make_mode.run_make_mode() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return make_mode.run_make_mode(args) diff --git a/sphinx/registry.py b/sphinx/registry.py index d641c0529cf..f47c5e71a38 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -186,7 +186,7 @@ def override_domain(self, domain): # type: (Type[Domain]) -> None warnings.warn('registry.override_domain() is deprecated. ' 'Use app.add_domain(domain, override=True) instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) self.add_domain(domain, override=True) def add_directive_to_domain(self, domain, name, obj, has_content=None, argument_spec=None, @@ -291,7 +291,7 @@ def add_source_parser(self, *args, **kwargs): # old style arguments: (suffix, source_parser) warnings.warn('app.add_source_parser() does not support suffix argument. ' 'Use app.add_source_suffix() instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=3) suffix = args[0] parser = args[1] @@ -301,7 +301,7 @@ def add_source_parser(self, *args, **kwargs): if len(parser.supported) == 0: warnings.warn('Old source_parser has been detected. Please fill Parser.supported ' 'attribute: %s' % parser.__name__, - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=3) # create a map from filetype to parser for filetype in parser.supported: diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index f28013b0bbc..84725e6a745 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -557,7 +557,7 @@ def init(self, options): dotted_path = self.splitters[type] warnings.warn('html_search_options["type"]: %s is deprecated. ' 'Please give "%s" instead.' % (type, dotted_path), - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) else: dotted_path = type try: diff --git a/sphinx/transforms/post_transforms/compat.py b/sphinx/transforms/post_transforms/compat.py index 9a03ff6b534..8065cf91c63 100644 --- a/sphinx/transforms/post_transforms/compat.py +++ b/sphinx/transforms/post_transforms/compat.py @@ -64,19 +64,23 @@ def apply(self): nowrap=node.get('nowrap'), docname=node.get('docname')) node.replace(alt) - else: - # case: old styled ``displaymath`` node generated by old extensions - for node in self.document.traverse(math_block): - if len(node) == 0: - warnings.warn("math node for Sphinx was replaced by docutils'. " - "Please use ``docutils.nodes.math_block`` instead.", - RemovedInSphinx30Warning) - if isinstance(node, displaymath): - newnode = nodes.math_block('', node['latex'], **node.attributes) - node.replace_self(newnode) - else: - latex = node['latex'] - node += nodes.Text(latex, latex) + elif getattr(self.app.builder, 'math_renderer_name', None) == 'unknown': + # case: math extension provides old styled math renderer + for node in self.document.traverse(nodes.math_block): + node['latex'] = node.astext() + + # case: old styled ``displaymath`` node generated by old extensions + for node in self.document.traverse(math_block): + if len(node) == 0: + warnings.warn("math node for Sphinx was replaced by docutils'. " + "Please use ``docutils.nodes.math_block`` instead.", + RemovedInSphinx30Warning) + if isinstance(node, displaymath): + newnode = nodes.math_block('', node['latex'], **node.attributes) + node.replace_self(newnode) + else: + latex = node['latex'] + node += nodes.Text(latex, latex) def setup(app): diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 8ea5641c96c..90e0e6a185e 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -208,7 +208,7 @@ def copy_static_entry(source, targetdir, builder, context={}, Handles all possible cases of files, directories and subdirectories. """ warnings.warn('sphinx.util.copy_static_entry is deprecated for removal', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) if exclude_matchers: relpath = relative_path(path.join(builder.srcdir, 'dummy'), source) diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 4e25115a742..eca5a8cf837 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -123,7 +123,7 @@ def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact """ if gettext_compact is not None: warnings.warn('gettext_compact argument for find_catalog_source_files() ' - 'is deprecated.', RemovedInSphinx30Warning) + 'is deprecated.', RemovedInSphinx30Warning, stacklevel=2) catalogs = set() # type: Set[CatalogInfo] diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 85fbf17f13b..d7e5de78748 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -85,7 +85,7 @@ def guess_mimetype(filename='', content=None, default=None): return mime_suffixes[ext] elif content: warnings.warn('The content argument of guess_mimetype() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return guess_mimetype_for_stream(BytesIO(content), default=default) elif path.exists(filename): with open(filename, 'rb') as f: diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 541d5f5c5d7..82814e00d98 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -322,19 +322,12 @@ def __init__(self, subject, bound_method=False, has_retval=True): raise try: - if ispartial(subject): - # get_type_hints() does not support partial objects - self.annotations = {} # type: Dict[str, Any] - else: - self.annotations = typing.get_type_hints(subject) # type: ignore - except Exception as exc: - if (3, 5, 0) <= sys.version_info < (3, 5, 3) and isinstance(exc, AttributeError): - # python 3.5.2 raises ValueError for classmethod-ized partial objects. - self.annotations = {} - else: - logger.warning('Invalid type annotation found on %r. Ignored: %r', - subject, exc) - self.annotations = {} + self.annotations = typing.get_type_hints(subject) # type: ignore + except Exception: + # get_type_hints() does not support some kind of objects like partial, + # ForwardRef and so on. For them, it raises an exception. In that case, + # we try to build annotations from argspec. + self.annotations = {} if bound_method: # client gives a hint that the subject is a bound method diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 11b082e7d79..c7c0b06905a 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -84,9 +84,10 @@ def ensuredir(path): """Ensure that a path exists.""" try: os.makedirs(path) - except OSError as err: - # 0 for Jython/Win32 - if err.errno not in [0, EEXIST]: + except OSError: + # If the path is already an existing directory (not a file!), + # that is OK. + if not os.path.isdir(path): raise @@ -155,7 +156,7 @@ def ustrftime(format, *args): # type: (unicode, Any) -> unicode """[DEPRECATED] strftime for unicode strings.""" warnings.warn('sphinx.util.osutil.ustrtime is deprecated for removal', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) if not args: # If time is not specified, try to use $SOURCE_DATE_EPOCH variable diff --git a/sphinx/versioning.py b/sphinx/versioning.py index e65c9fd8fa6..53729530fa8 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -186,6 +186,6 @@ def prepare(document): # type: (nodes.Node) -> None """Simple wrapper for UIDTransform.""" warnings.warn('versioning.prepare() is deprecated. Use UIDTransform instead.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) transform = UIDTransform(document) transform.apply() diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index e2899e8a4ee..66f3bf8f4bc 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -902,7 +902,7 @@ def unknown_visit(self, node): def highlightlang(self): # type: () -> unicode warnings.warn('HTMLTranslator.highlightlang is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.builder.config.highlight_language @property @@ -916,12 +916,12 @@ def highlightlang_base(self): def highlightopts(self): # type: () -> unicode warnings.warn('HTMLTranslator.highlightopts is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.builder.config.highlight_options @property def highlightlinenothreshold(self): # type: () -> int warnings.warn('HTMLTranslator.highlightlinenothreshold is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return sys.maxsize diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index c14775b9c5b..41c665cddff 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -853,26 +853,26 @@ def unknown_visit(self, node): def highlightlang(self): # type: () -> unicode warnings.warn('HTMLTranslator.highlightlang is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.builder.config.highlight_language @property def highlightlang_base(self): # type: () -> unicode warnings.warn('HTMLTranslator.highlightlang_base is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.builder.config.highlight_language @property def highlightopts(self): # type: () -> unicode warnings.warn('HTMLTranslator.highlightopts is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return self.builder.config.highlight_options @property def highlightlinenothreshold(self): # type: () -> int warnings.warn('HTMLTranslator.highlightlinenothreshold is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return sys.maxsize diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index f9af2a1f882..38148033109 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -217,7 +217,7 @@ def __init__(self, language_code, use_polyglossia=False): def get_shorthandoff(self): # type: () -> unicode warnings.warn('ExtBabel.get_shorthandoff() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return SHORTHANDOFF def uses_cyrillic(self): @@ -288,14 +288,14 @@ def __init__(self, node): def caption_footnotetexts(self): # type: () -> List[unicode] warnings.warn('table.caption_footnotetexts is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return [] @property def header_footnotetexts(self): # type: () -> List[unicode] warnings.warn('table.header_footnotetexts is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return [] def is_longtable(self): @@ -656,7 +656,7 @@ def popbody(self): def restrict_footnote(self, node): # type: (nodes.Node) -> None warnings.warn('LaTeXWriter.restrict_footnote() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) if self.footnote_restricted is False: self.footnote_restricted = node @@ -665,7 +665,7 @@ def restrict_footnote(self, node): def unrestrict_footnote(self, node): # type: (nodes.Node) -> None warnings.warn('LaTeXWriter.unrestrict_footnote() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) if self.footnote_restricted == node: self.footnote_restricted = False @@ -2504,60 +2504,60 @@ def unknown_visit(self, node): def footnotestack(self): # type: () -> List[Dict[unicode, List[Union[collected_footnote, bool]]]] warnings.warn('LaTeXWriter.footnotestack is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return [] @property def bibitems(self): # type: () -> List[List[unicode]] warnings.warn('LaTeXTranslator.bibitems() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return [] @property def in_container_literal_block(self): # type: () -> int warnings.warn('LaTeXTranslator.in_container_literal_block is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return 0 @property def next_section_ids(self): # type: () -> Set[unicode] warnings.warn('LaTeXTranslator.next_section_ids is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return set() @property def next_hyperlink_ids(self): # type: () -> Dict warnings.warn('LaTeXTranslator.next_hyperlink_ids is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return {} def push_hyperlink_ids(self, figtype, ids): # type: (unicode, Set[unicode]) -> None warnings.warn('LaTeXTranslator.push_hyperlink_ids() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) pass def pop_hyperlink_ids(self, figtype): # type: (unicode) -> Set[unicode] warnings.warn('LaTeXTranslator.pop_hyperlink_ids() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return set() @property def hlsettingstack(self): # type: () -> List[List[Union[unicode, int]]] warnings.warn('LaTeXTranslator.hlsettingstack is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) return [[self.builder.config.highlight_language, sys.maxsize]] def check_latex_elements(self): # type: () -> None warnings.warn('check_latex_elements() is deprecated.', - RemovedInSphinx30Warning) + RemovedInSphinx30Warning, stacklevel=2) for key in self.builder.config.latex_elements: if key not in self.elements: diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 5da8a90eef8..e2f74c93802 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -1296,7 +1296,7 @@ def test_latex_labels(app, status, warning): r'\label{\detokenize{index:figure1}}' r'\end{figure}' in result) assert (r'\caption{labeled figure}' - '\\label{\detokenize{index:figure3}}\n' + '\\label{\\detokenize{index:figure3}}\n' '\\begin{sphinxlegend}\nwith a legend\n\\end{sphinxlegend}\n' r'\end{figure}' in result) diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index 738ad8ce68b..5ccaa6047bb 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -255,7 +255,7 @@ def test_math_compat(app, status, warning): [nodes.math_block, "E = mc^2"])) -@pytest.mark.sphinx('html', testroot='basic', +@pytest.mark.sphinx('html', testroot='ext-math', confoverrides={'extensions': ['sphinx.ext.mathjax'], 'mathjax_config': {'extensions': ['tex2jax.js']}}) def test_mathjax_config(app, status, warning): @@ -265,3 +265,22 @@ def test_mathjax_config(app, status, warning): assert ('' in content) + + +@pytest.mark.sphinx('html', testroot='basic', + confoverrides={'extensions': ['sphinx.ext.mathjax']}) +def test_mathjax_is_not_installed_if_no_equations(app, status, warning): + app.builder.build_all() + + content = (app.outdir / 'index.html').text() + assert 'MathJax.js' not in content + + +@pytest.mark.sphinx('html', testroot='basic', + confoverrides={'extensions': ['sphinx.ext.jsmath'], + 'jsmath_path': 'jsmath.js'}) +def test_jsmath_is_not_installed_if_no_equations(app, status, warning): + app.builder.build_all() + + content = (app.outdir / 'index.html').text() + assert 'jsmath.js' not in content diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index bbaf4e97b38..6b57a620d96 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -193,7 +193,7 @@ def meth2(self, arg1, arg2): def test_Signature_annotations(): from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, - f11, f12, f13, f14, f15, f16, Node) + f11, f12, f13, f14, f15, f16, f17, Node) # Class annotations sig = inspect.Signature(f0).format_args() @@ -258,12 +258,16 @@ def test_Signature_annotations(): sig = inspect.Signature(f14).format_args() assert sig == '() -> Any' - # keyword only arguments (1) + # ForwardRef sig = inspect.Signature(f15).format_args() + assert sig == '(x: Unknown, y: int) -> Any' + + # keyword only arguments (1) + sig = inspect.Signature(f16).format_args() assert sig == '(arg1, arg2, *, arg3=None, arg4=None)' # keyword only arguments (2) - sig = inspect.Signature(f16).format_args() + sig = inspect.Signature(f17).format_args() assert sig == '(*, arg3, arg4)' # type hints by string diff --git a/tests/typing_test_data.py b/tests/typing_test_data.py index 183d075b6dd..2e0e27ced39 100644 --- a/tests/typing_test_data.py +++ b/tests/typing_test_data.py @@ -76,10 +76,15 @@ def f14() -> Any: pass -def f15(arg1, arg2, *, arg3=None, arg4=None): +def f15(x: "Unknown", y: "int") -> Any: pass -def f16(*, arg3, arg4): + +def f16(arg1, arg2, *, arg3=None, arg4=None): + pass + + +def f17(*, arg3, arg4): pass diff --git a/tox.ini b/tox.ini index 51a9d7e3090..971955ea895 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ setenv = PYTHONWARNINGS = all,ignore::ImportWarning:pkgutil,ignore::ImportWarning:importlib._bootstrap,ignore::ImportWarning:importlib._bootstrap_external,ignore::ImportWarning:pytest_cov.plugin,ignore::DeprecationWarning:site,ignore::DeprecationWarning:_pytest.assertion.rewrite,ignore::DeprecationWarning:_pytest.fixtures,ignore::DeprecationWarning:distutils SPHINX_TEST_TEMPDIR = {envdir}/testbuild commands= - pytest -Wall --durations 25 {posargs} + pytest --durations 25 {posargs} [testenv:flake8] basepython = python3