diff -Nru python-nox-2021.10.1/CHANGELOG.md python-nox-2022.1.7/CHANGELOG.md --- python-nox-2021.10.1/CHANGELOG.md 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/CHANGELOG.md 2022-01-07 23:23:23.000000000 +0000 @@ -1,5 +1,63 @@ # Changelog +## 2022.1.7 + +Claudio Jolowicz, Diego Ramirez, and Tom Fleet have become maintainers of Nox. We'd like to thank the following folks who contributed to this release: + +- @brettcannon +- @cjolowicz +- @dhermes +- @DiddiLeija +- @FollowTheProcess +- @franekmagiera +- @henryiii +- @jugmac00 +- @maciej-lech +- @nawatts +- @Tolker-KU + +New features: +- Add `mamba` backend (#444, #448, #546, #551) +- Add `session.debug` to show debug-level messages (#489) +- Add cookbook page to the documentation (#483) +- Add support for the `FORCE_COLOR` environment variable (#524, #548) +- Allow using `session.chdir()` as a context manager (#543) +- Deprecate use of `session.install()` without a valid backend (#537) + +Improvements: +- Test against Python 3.10 (#495, $502, #506) +- Add support for the `channel` option when using the `conda` backend (#522) +- Show more specific error message when the `--keywords` expression contains a syntax error (#493) +- Include reference to `session.notify()` in tutorial page (#500) +- Document how `session.run()` fails and how to handle failures (#533) +- Allow the list of sessions to be empty (#523) + +Bugfixes: +- Fix broken temporary directory when using `session.chdir()` (#555, #556) +- Set the `CONDA_PREFIX` environment variable (#538) +- Fix `bin` directory for the `conda` backend on Windows (#535) + +Internal changes: +- Replace deprecated `load_module` with `exec_module` (#498) +- Include tests with source distributions (#552) +- Add missing copyright notices (#509) +- Use the new ReadTheDocs configurations (#527) +- Bump the Python version used by ReadTheDocs to 3.8 (#496) +- Improve the Sphinx config file (#499) +- Update all linter versions (#528) +- Add pre-commit and new checks (#530, #539) +- Check `MANIFEST.in` during CI (#552) +- Remove redundant `LICENSE` from `MANIFEST.in` (#505) +- Make `setuptools` use the standard library's `distutils` to work around `virtualenv` bug. (#547, #549) +- Use `shlex.join()` when logging a command (#490) +- Use `shutil.rmtree()` over shelling out to `rm -rf` in noxfile (#519) +- Fix missing Python 3.9 CI session (#529) +- Unpin docs session and add `--error-on-missing-interpreter` to CI (#532) +- Enable color output from Nox, pytest, and pre-commit during CI (#542) +- Only run `conda_tests` session by default if user has conda installed (#521) +- Update dependencies in `requirements-conda-test.txt` (#536) + + ## 2021.10.1 New features: diff -Nru python-nox-2021.10.1/CONTRIBUTING.md python-nox-2022.1.7/CONTRIBUTING.md --- python-nox-2021.10.1/CONTRIBUTING.md 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/CONTRIBUTING.md 2022-01-07 23:23:23.000000000 +0000 @@ -54,4 +54,4 @@ ## Getting paid -Contributions to Nox can be expensed through [our Open Collective](https://opencollective.com/python-nox). The maintainers will let you know when and for how much you can expense contributions, but always feel free to ask. +Contributions to Nox can be expensed through [our Open Collective](https://opencollective.com/python-nox). The maintainers will let you know when and for how much you can expense contributions, but always feel free to ask. diff -Nru python-nox-2021.10.1/debian/changelog python-nox-2022.1.7/debian/changelog --- python-nox-2021.10.1/debian/changelog 2021-10-05 13:31:34.000000000 +0000 +++ python-nox-2022.1.7/debian/changelog 2022-02-01 11:16:32.000000000 +0000 @@ -1,3 +1,16 @@ +python-nox (2022.1.7-1) unstable; urgency=low + + * New upstream release. + * Refresh patches. + * Clean up nox.egg-info/not-zip-safe to allow two builds in a row. + * Add missing test dependencies to Build-Depends. + * Set Rules-Requires-Root: no. + * Update year in d/copyright. + * Run upstream tests during build. + * Enable upstream testsuite for autopkgtests. + + -- Michael Fladischer Tue, 01 Feb 2022 11:16:32 +0000 + python-nox (2021.10.1-1) unstable; urgency=low * Team upload. diff -Nru python-nox-2021.10.1/debian/clean python-nox-2022.1.7/debian/clean --- python-nox-2021.10.1/debian/clean 2021-10-05 13:11:02.000000000 +0000 +++ python-nox-2022.1.7/debian/clean 2022-02-01 11:16:32.000000000 +0000 @@ -4,3 +4,4 @@ nox.egg-info/entry_points.txt nox.egg-info/requires.txt nox.egg-info/top_level.txt +nox.egg-info/not-zip-safe diff -Nru python-nox-2021.10.1/debian/control python-nox-2022.1.7/debian/control --- python-nox-2021.10.1/debian/control 2021-10-05 13:28:34.000000000 +0000 +++ python-nox-2022.1.7/debian/control 2022-02-01 11:16:32.000000000 +0000 @@ -8,14 +8,21 @@ debhelper-compat (= 13), dh-sequence-python3, python3-all, + python3-argcomplete, + python3-colorlog, + python3-py, + python3-pytest, python3-recommonmark, python3-setuptools, python3-sphinx, + python3-venv, + tox, Standards-Version: 4.6.0 Homepage: https://github.com/theacodes/nox/ Vcs-Browser: https://salsa.debian.org/python-team/packages/python-nox Vcs-Git: https://salsa.debian.org/python-team/packages/python-nox.git Testsuite: autopkgtest-pkg-python +Rules-Requires-Root: no Package: nox Architecture: all diff -Nru python-nox-2021.10.1/debian/copyright python-nox-2022.1.7/debian/copyright --- python-nox-2021.10.1/debian/copyright 2021-10-05 13:11:02.000000000 +0000 +++ python-nox-2022.1.7/debian/copyright 2022-02-01 11:16:32.000000000 +0000 @@ -4,11 +4,11 @@ Source: https://github.com/theacodes/nox/ Files: * -Copyright: 2016, Alethea Katherine Flowers +Copyright: 2016-2021, Alethea Katherine Flowers License: Apache-2 Files: debian/* -Copyright: 2019, Michael Fladischer +Copyright: 2019-2022, Michael Fladischer License: Apache-2 License: Apache-2 diff -Nru python-nox-2021.10.1/debian/patches/0001-Disable-withchazel-pygments-style-as-it-is-not-packa.patch python-nox-2022.1.7/debian/patches/0001-Disable-withchazel-pygments-style-as-it-is-not-packa.patch --- python-nox-2021.10.1/debian/patches/0001-Disable-withchazel-pygments-style-as-it-is-not-packa.patch 2021-10-05 13:31:27.000000000 +0000 +++ python-nox-2022.1.7/debian/patches/0001-Disable-withchazel-pygments-style-as-it-is-not-packa.patch 2022-02-01 11:16:32.000000000 +0000 @@ -7,15 +7,15 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py -index a7029aa..d11d24c 100644 +index e78ab2c..50a2b87 100644 --- a/docs/conf.py +++ b/docs/conf.py -@@ -104,7 +104,7 @@ add_module_names = False - #show_authors = False +@@ -102,7 +102,7 @@ add_module_names = False + # show_authors = False # The name of the Pygments (syntax highlighting) style to use. --pygments_style = 'witchhazel.WitchHazelStyle' -+#pygments_style = 'witchhazel.WitchHazelStyle' +-pygments_style = "witchhazel.WitchHazelStyle" ++#pygments_style = "witchhazel.WitchHazelStyle" # A list of ignored prefixes for module index sorting. - #modindex_common_prefix = [] + # modindex_common_prefix = [] diff -Nru python-nox-2021.10.1/debian/patches/0002-Disable-privacy-breaches-in-HTML-documentation.patch python-nox-2022.1.7/debian/patches/0002-Disable-privacy-breaches-in-HTML-documentation.patch --- python-nox-2021.10.1/debian/patches/0002-Disable-privacy-breaches-in-HTML-documentation.patch 2021-10-05 13:31:27.000000000 +0000 +++ python-nox-2022.1.7/debian/patches/0002-Disable-privacy-breaches-in-HTML-documentation.patch 2022-02-01 11:16:32.000000000 +0000 @@ -8,7 +8,7 @@ 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/_static/custom.css b/docs/_static/custom.css -index 101a133..5411da9 100644 +index 399bb9c..a427cc3 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -1,5 +1,3 @@ @@ -18,15 +18,15 @@ color: #027bab; } diff --git a/docs/conf.py b/docs/conf.py -index d11d24c..dfb0598 100644 +index 50a2b87..cebe338 100644 --- a/docs/conf.py +++ b/docs/conf.py -@@ -131,7 +131,7 @@ html_theme_options = { - 'description': 'Flexible test automation', - 'github_user': 'theacodes', - 'github_repo': 'nox', -- 'github_banner': True, -+ 'github_banner': False, - 'github_button': False, - 'travis_button': False, - 'codecov_button': False, +@@ -129,7 +129,7 @@ html_theme_options = { + "description": "Flexible test automation", + "github_user": "theacodes", + "github_repo": "nox", +- "github_banner": True, ++ "github_banner": False, + "github_button": False, + "travis_button": False, + "codecov_button": False, diff -Nru python-nox-2021.10.1/debian/pybuild.testfiles python-nox-2022.1.7/debian/pybuild.testfiles --- python-nox-2021.10.1/debian/pybuild.testfiles 1970-01-01 00:00:00.000000000 +0000 +++ python-nox-2022.1.7/debian/pybuild.testfiles 2022-02-01 11:16:32.000000000 +0000 @@ -0,0 +1,2 @@ +tests +nox.egg-info diff -Nru python-nox-2021.10.1/debian/rules python-nox-2022.1.7/debian/rules --- python-nox-2021.10.1/debian/rules 2021-10-05 13:28:34.000000000 +0000 +++ python-nox-2022.1.7/debian/rules 2022-02-01 11:16:32.000000000 +0000 @@ -3,7 +3,8 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 -export PYBUILD_DISABLE=test # Nox does not support using system packages in its virtualenvs yet. +export PYBUILD_TEST_PYTEST=1 +export PYBUILD_TEST_ARGS=-k "not test_create_reuse_stale_virtualenv_environment and not test_create_reuse_venv_environment and not test_create_venv_backend" %: dh $@ --with sphinxdoc --buildsystem=pybuild diff -Nru python-nox-2021.10.1/debian/tests/control python-nox-2022.1.7/debian/tests/control --- python-nox-2021.10.1/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 +++ python-nox-2022.1.7/debian/tests/control 2022-02-01 11:16:32.000000000 +0000 @@ -0,0 +1,6 @@ +Tests: upstream +Depends: + python3-all, + @, + @builddeps@, +Restrictions: allow-stderr diff -Nru python-nox-2021.10.1/debian/tests/upstream python-nox-2022.1.7/debian/tests/upstream --- python-nox-2021.10.1/debian/tests/upstream 1970-01-01 00:00:00.000000000 +0000 +++ python-nox-2022.1.7/debian/tests/upstream 2022-02-01 11:16:32.000000000 +0000 @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +cp -r tests ${AUTOPKGTEST_TMP} +cd ${AUTOPKGTEST_TMP} +for p in $(py3versions -s); do + $p -m pytest -k "not test_create_reuse_stale_virtualenv_environment and not test_create_reuse_venv_environment and not test_create_venv_backend" tests +done diff -Nru python-nox-2021.10.1/docs/CHANGELOG.md python-nox-2022.1.7/docs/CHANGELOG.md --- python-nox-2021.10.1/docs/CHANGELOG.md 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/docs/CHANGELOG.md 2022-01-07 23:23:23.000000000 +0000 @@ -1,5 +1,63 @@ # Changelog +## 2022.1.7 + +Claudio Jolowicz, Diego Ramirez, and Tom Fleet have become maintainers of Nox. We'd like to thank the following folks who contributed to this release: + +- @brettcannon +- @cjolowicz +- @dhermes +- @DiddiLeija +- @FollowTheProcess +- @franekmagiera +- @henryiii +- @jugmac00 +- @maciej-lech +- @nawatts +- @Tolker-KU + +New features: +- Add `mamba` backend (#444, #448, #546, #551) +- Add `session.debug` to show debug-level messages (#489) +- Add cookbook page to the documentation (#483) +- Add support for the `FORCE_COLOR` environment variable (#524, #548) +- Allow using `session.chdir()` as a context manager (#543) +- Deprecate use of `session.install()` without a valid backend (#537) + +Improvements: +- Test against Python 3.10 (#495, $502, #506) +- Add support for the `channel` option when using the `conda` backend (#522) +- Show more specific error message when the `--keywords` expression contains a syntax error (#493) +- Include reference to `session.notify()` in tutorial page (#500) +- Document how `session.run()` fails and how to handle failures (#533) +- Allow the list of sessions to be empty (#523) + +Bugfixes: +- Fix broken temporary directory when using `session.chdir()` (#555, #556) +- Set the `CONDA_PREFIX` environment variable (#538) +- Fix `bin` directory for the `conda` backend on Windows (#535) + +Internal changes: +- Replace deprecated `load_module` with `exec_module` (#498) +- Include tests with source distributions (#552) +- Add missing copyright notices (#509) +- Use the new ReadTheDocs configurations (#527) +- Bump the Python version used by ReadTheDocs to 3.8 (#496) +- Improve the Sphinx config file (#499) +- Update all linter versions (#528) +- Add pre-commit and new checks (#530, #539) +- Check `MANIFEST.in` during CI (#552) +- Remove redundant `LICENSE` from `MANIFEST.in` (#505) +- Make `setuptools` use the standard library's `distutils` to work around `virtualenv` bug. (#547, #549) +- Use `shlex.join()` when logging a command (#490) +- Use `shutil.rmtree()` over shelling out to `rm -rf` in noxfile (#519) +- Fix missing Python 3.9 CI session (#529) +- Unpin docs session and add `--error-on-missing-interpreter` to CI (#532) +- Enable color output from Nox, pytest, and pre-commit during CI (#542) +- Only run `conda_tests` session by default if user has conda installed (#521) +- Update dependencies in `requirements-conda-test.txt` (#536) + + ## 2021.10.1 New features: diff -Nru python-nox-2021.10.1/docs/config.rst python-nox-2022.1.7/docs/config.rst --- python-nox-2021.10.1/docs/config.rst 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/docs/config.rst 2022-01-07 23:23:23.000000000 +0000 @@ -149,6 +149,14 @@ def tests(session): pass +Use of :func:`session.install()` is deprecated without a virtualenv since it modifies the global Python environment. If this is what you really want, use :func:`session.run()` and pip instead. + +.. code-block:: python + + @nox.session(python=False) + def tests(session): + session.run("pip", "install", "nox") + You can also specify that the virtualenv should *always* be reused instead of recreated every time: .. code-block:: python @@ -159,7 +167,7 @@ def tests(session): pass -You are not limited to virtualenv, there is a selection of backends you can choose from as venv, conda or virtualenv (default): +You are not limited to virtualenv, there is a selection of backends you can choose from as venv, conda, mamba, or virtualenv (default): .. code-block:: python @@ -418,7 +426,7 @@ The following options can be specified in the Noxfile: * ``nox.options.envdir`` is equivalent to specifying :ref:`--envdir `. -* ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions `. +* ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions `. If set to an empty list, no sessions will be run if no sessions were given on the command line, and the list of available sessions will be shown instead. * ``nox.options.pythons`` is equivalent to specifying :ref:`-p or --pythons `. * ``nox.options.keywords`` is equivalent to specifying :ref:`-k or --keywords `. * ``nox.options.default_venv_backend`` is equivalent to specifying :ref:`-db or --default-venv-backend `. diff -Nru python-nox-2021.10.1/docs/conf.py python-nox-2022.1.7/docs/conf.py --- python-nox-2021.10.1/docs/conf.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/docs/conf.py 2022-01-07 23:23:23.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # nox documentation build configuration file, created by # sphinx-quickstart on Sun Feb 28 00:05:25 2016. @@ -26,48 +25,47 @@ # Note: even though nox is installed when the docs are built, there's a # possibility it's installed as a bytecode-compiled binary (.egg). So, # include the source anyway. -sys.path.insert(0, os.path.abspath('..')) -sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath("..")) +sys.path.insert(0, os.path.abspath(".")) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', - 'recommonmark', + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "recommonmark", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Nox' -copyright = u'2016, Alethea Katherine Flowers' -author = u'Alethea Katherine Flowers' +project = "Nox" +copyright = "2016, Alethea Katherine Flowers" +author = "Alethea Katherine Flowers" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = metadata.version('nox') +version = metadata.version("nox") # The full version, including alpha/beta/rc tags. release = version @@ -80,20 +78,20 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). @@ -101,16 +99,16 @@ # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'witchhazel.WitchHazelStyle' +pygments_style = "witchhazel.WitchHazelStyle" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -120,179 +118,172 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - 'logo': 'alice.png', - 'logo_name': True, - 'description': 'Flexible test automation', - 'github_user': 'theacodes', - 'github_repo': 'nox', - 'github_banner': True, - 'github_button': False, - 'travis_button': False, - 'codecov_button': False, - 'analytics_id': False, # TODO - 'font_family': "'Roboto', Georgia, sans", - 'head_font_family': "'Roboto', Georgia, serif", - 'code_font_family': "'Roboto Mono', 'Consolas', monospace", - 'pre_bg': '#433e56' + "logo": "alice.png", + "logo_name": True, + "description": "Flexible test automation", + "github_user": "theacodes", + "github_repo": "nox", + "github_banner": True, + "github_button": False, + "travis_button": False, + "codecov_button": False, + "analytics_id": False, # TODO + "font_family": "'Roboto', Georgia, sans", + "head_font_family": "'Roboto', Georgia, serif", + "code_font_family": "'Roboto Mono', 'Consolas', monospace", + "pre_bg": "#433e56", } # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # 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_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "relations.html", + "searchbox.html", + "donate.html", ] } # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'noxdoc' +htmlhelp_basename = "noxdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', + # Latex figure (float) alignment + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'nox.tex', u'nox Documentation', - u'Alethea Katherine Flowers', 'manual'), + (master_doc, "nox.tex", "nox Documentation", "Alethea Katherine Flowers", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'nox', u'nox Documentation', - [author], 1) -] +man_pages = [(master_doc, "nox", "nox Documentation", [author], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -301,19 +292,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'nox', u'nox Documentation', - author, 'nox', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "nox", + "nox Documentation", + author, + "nox", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff -Nru python-nox-2021.10.1/docs/CONTRIBUTING.md python-nox-2022.1.7/docs/CONTRIBUTING.md --- python-nox-2021.10.1/docs/CONTRIBUTING.md 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/docs/CONTRIBUTING.md 2022-01-07 23:23:23.000000000 +0000 @@ -54,4 +54,4 @@ ## Getting paid -Contributions to Nox can be expensed through [our Open Collective](https://opencollective.com/python-nox). The maintainers will let you know when and for how much you can expense contributions, but always feel free to ask. +Contributions to Nox can be expensed through [our Open Collective](https://opencollective.com/python-nox). The maintainers will let you know when and for how much you can expense contributions, but always feel free to ask. diff -Nru python-nox-2021.10.1/docs/cookbook.rst python-nox-2022.1.7/docs/cookbook.rst --- python-nox-2021.10.1/docs/cookbook.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-nox-2022.1.7/docs/cookbook.rst 2022-01-07 23:23:23.000000000 +0000 @@ -0,0 +1,129 @@ +The Nox Cookbook +================ + +The What? +--------- + +A lot of people and a lot of projects use Nox for their python automation powers. + +Some of these sessions are the classic "run pytest and linting", some are more unique and more interesting! + +The Nox cookbook is a collection of these such sessions. + +Nox is super easy to get started with, and super powerful right out of the box. But when things get complex or you want to chain together some more powerful tasks, often the only examples can be found hunting around GitHub for novel sessions. + +The kind of sessions that make you think "I didn't know you could do that!" + +This cookbook is intended to be a centralized, community-driven repository of awesome Nox sessions to act as a source of inspiration and a reference guide for nox's users. If you're doing something cool with Nox, why not add your session here? + + +Contributing a Session +---------------------- + +Anyone can contribute sessions to the cookbook. However, there are a few guiding principles you should keep in mind: + +* Your session should be interesting or unique, it should do something out of the ordinary or otherwise interesting. +* You should explain briefly what it does and why it's interesting. + +For general advice on how to contribute to Nox see our :doc:`CONTRIBUTING` guide + +Recipes +------- + +Instant Dev Environment +^^^^^^^^^^^^^^^^^^^^^^^ + +A common sticking point in contributing to python projects (especially for beginners) is the problem of wrangling virtual environments and installing dependencies. + +Enter the ``dev`` nox session: + +.. code-block:: python + + import os + + import nox + + # It's a good idea to keep your dev session out of the default list + # so it's not run twice accidentally + nox.options.sessions = [...] # Sessions other than 'dev' + + @nox.session + def dev(session: nox.Session) -> None: + """ + Sets up a python development environment for the project. + + This session will: + - Create a python virtualenv for the session + - Install the `virtualenv` cli tool into this environment + - Use `virtualenv` to create a global project virtual environment + - Invoke the python interpreter from the global project environment to install + the project and all it's development dependencies. + """ + + session.install("virtualenv") + # VENV_DIR here is a pathlib.Path location of the project virtualenv + # e.g. .venv + session.run("virtualenv", os.fsdecode(VENV_DIR), silent=True) + + python = os.fsdecode(VENV_DIR.joinpath("bin/python")) + + # Use the venv's interpreter to install the project along with + # all it's dev dependencies, this ensures it's installed in the right way + session.run(python, "-m", "pip", "install", "-e", ".[dev]", external=True) + +With this, a user can simply run ``nox -s dev`` and have their entire environment set up automatically! + + +The Auto-Release +^^^^^^^^^^^^^^^^ + +Releasing a new version of an open source project can be a real pain, with lots of intricate steps. Tools like `Bump2Version `_ really help here. + +Even more so with a sprinkling of Nox: + +.. code-block:: python + + import nox + + @nox.session + def release(session: nox.Session) -> None: + """ + Kicks off an automated release process by creating and pushing a new tag. + + Invokes bump2version with the posarg setting the version. + + Usage: + $ nox -s release -- [major|minor|patch] + """ + parser = argparse.ArgumentParser(description="Release a semver version.") + parser.add_argument( + "version", + type=str, + nargs=1, + help="The type of semver release to make.", + choices={"major", "minor", "patch"}, + ) + args: argparse.Namespace = parser.parse_args(args=session.posargs) + version: str = args.version.pop() + + # If we get here, we should be good to go + # Let's do a final check for safety + confirm = input( + f"You are about to bump the {version!r} version. Are you sure? [y/n]: " + ) + + # Abort on anything other than 'y' + if confirm.lower().strip() != "y": + session.error(f"You said no when prompted to bump the {version!r} version.") + + + session.install("bump2version") + + session.log(f"Bumping the {version!r} version") + session.run("bump2version", version) + + session.log("Pushing the new tag") + session.run("git", "push", external=True) + session.run("git", "push", "--tags", external=True) + +Now a simple ``nox -s release -- patch`` will automate your release (provided you have Bump2Version set up to change your files). This is especially powerful if you have a CI/CD pipeline set up! diff -Nru python-nox-2021.10.1/docs/index.rst python-nox-2022.1.7/docs/index.rst --- python-nox-2021.10.1/docs/index.rst 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/docs/index.rst 2022-01-07 23:23:23.000000000 +0000 @@ -8,6 +8,7 @@ tutorial config usage + cookbook CONTRIBUTING CHANGELOG @@ -89,9 +90,12 @@ Our maintainers are (in alphabetical order): * `Chris Wilcox `__ +* `Claudio Jolowicz `__ * `Danny Hermes `__ +* `Diego Ramirez `__ * `Luke Sneeringer `__ * `Santos Gallegos `__ * `Thea Flowers `__ +* `Tom Fleet `__ Nox also exists due to the various patches and work contributed by `the community `__. If you'd like to get involved, see :doc:`CONTRIBUTING`. We pay our contributors using `Open Collective `__. diff -Nru python-nox-2021.10.1/docs/_static/custom.css python-nox-2022.1.7/docs/_static/custom.css --- python-nox-2021.10.1/docs/_static/custom.css 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/docs/_static/custom.css 2022-01-07 23:23:23.000000000 +0000 @@ -120,7 +120,7 @@ div.footer { text-align: center; - color: #029be2; + color: #029be2; } div.footer a { diff -Nru python-nox-2021.10.1/docs/tutorial.rst python-nox-2022.1.7/docs/tutorial.rst --- python-nox-2021.10.1/docs/tutorial.rst 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/docs/tutorial.rst 2022-01-07 23:23:23.000000000 +0000 @@ -192,7 +192,6 @@ See :func:`nox.sessions.Session.run` for more options and examples for running programs. - Selecting which sessions to run ------------------------------- @@ -259,9 +258,64 @@ nox > ... nox > Session lint was successful. + +In the noxfile, you can specify a default set of sessions to run. If so, a plain +``nox`` call will only trigger certain sessions: + +.. code-block:: python + + import nox + + nox.options.sessions = ["lint", "test"] + +If you set this to an empty list, Nox will not run any sessions by default, and +will print a helpful message with the ``--list`` output when a user does not +specify a session to run. + There are many more ways to select and run sessions! You can read more about invoking Nox in :doc:`usage`. +Queuing sessions +----------------- + +If you want to queue up (or "notify") another session from the current one, you can use the ``session.notify`` function: + +.. code-block:: python + + @nox.session + def tests(session): + session.install("pytest") + session.run("pytest") + # Here we queue up the test coverage session to run next + session.notify("coverage") + + @nox.session + def coverage(session): + session.install("coverage") + session.run("coverage") + +You can queue up any session you want, not just test and coverage sessions, but this is a very commonly +used pattern. + +Now running ``nox --session tests`` will run the tests session and then the coverage session. + +You can also pass the notified session positional arguments: + +.. code-block:: python + + @nox.session + def prepare_thing(session): + thing_path = "./path/to/thing" + session.run("prepare", "thing", thing_path) + session.notify("consume_thing", posargs=[thing_path]) + + @nox.session + def consume_thing(session): + # The 'consume' command has the arguments + # sent to it from the 'prepare_thing' session + session.run("consume", "thing", session.posargs) + +Note that this will only have the desired effect if selecting sessions to run via the ``--session/-s`` flag. If you simply run ``nox``, all selected sessions will be run. Testing against different and multiple Pythons ---------------------------------------------- @@ -335,6 +389,9 @@ session.install("contexter", "--no-deps") session.install("-e", ".", "--no-deps") +``"mamba"`` is also allowed as a choice for ``venv_backend``, which will +use/require `mamba `_ instead of conda. + Parametrization --------------- diff -Nru python-nox-2021.10.1/docs/usage.rst python-nox-2022.1.7/docs/usage.rst --- python-nox-2021.10.1/docs/usage.rst 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/docs/usage.rst 2022-01-07 23:23:23.000000000 +0000 @@ -110,7 +110,7 @@ Changing the sessions default backend ------------------------------------- -By default nox uses ``virtualenv`` as the virtual environment backend for the sessions, but it also supports ``conda`` and ``venv`` as well as no backend (passthrough to whatever python environment nox is running on). You can change the default behaviour by using ``-db `` or ``--default-venv-backend ``. Supported names are ``('none', 'virtualenv', 'conda', 'venv')``. +By default nox uses ``virtualenv`` as the virtual environment backend for the sessions, but it also supports ``conda``, ``mamba``, and ``venv`` as well as no backend (passthrough to whatever python environment nox is running on). You can change the default behaviour by using ``-db `` or ``--default-venv-backend ``. Supported names are ``('none', 'virtualenv', 'conda', 'mamba', 'venv')``. .. code-block:: console @@ -128,7 +128,7 @@ Forcing the sessions backend ---------------------------- -You might work in a different environment than a project's default continuous integration setttings, and might wish to get a quick way to execute the same tasks but on a different venv backend. For this purpose, you can temporarily force the backend used by **all** sessions in the current nox execution by using ``-fb `` or ``--force-venv-backend ``. No exceptions are made, the backend will be forced for all sessions run whatever the other options values and nox file configuration. Supported names are ``('none', 'virtualenv', 'conda', 'venv')``. +You might work in a different environment than a project's default continuous integration settings, and might wish to get a quick way to execute the same tasks but on a different venv backend. For this purpose, you can temporarily force the backend used by **all** sessions in the current nox execution by using ``-fb `` or ``--force-venv-backend ``. No exceptions are made, the backend will be forced for all sessions run whatever the other options values and nox file configuration. Supported names are ``('none', 'virtualenv', 'conda', 'venv')``. .. code-block:: console @@ -329,7 +329,8 @@ By default, Nox will output colorful logs if you're using in an interactive terminal. However, if you are redirecting ``stderr`` to a file or otherwise not using an interactive terminal, or the environment variable ``NO_COLOR`` is -set, nox will output in plaintext. +set, nox will output in plaintext. If this is not set, and ``FORCE_COLOR`` is +present, color will be forced. You can manually control Nox's output using the ``--nocolor`` and ``--forcecolor`` flags. diff -Nru python-nox-2021.10.1/.github/workflows/ci.yml python-nox-2022.1.7/.github/workflows/ci.yml --- python-nox-2021.10.1/.github/workflows/ci.yml 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/.github/workflows/ci.yml 2022-01-07 23:23:23.000000000 +0000 @@ -1,12 +1,18 @@ name: CI on: [push, pull_request] +env: + FORCE_COLOR: "1" + PRE_COMMIT_COLOR: "always" + # See https://github.com/theacodes/nox/issues/545 + # and https://github.com/pre-commit/pre-commit/issues/2178#issuecomment-1002163763 + SETUPTOOLS_USE_DISTUTILS: "stdlib" jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-20.04, windows-2019] - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -22,54 +28,33 @@ run: | python -m pip install --disable-pip-version-check . - name: Run tests on ${{ matrix.os }} - run: nox --non-interactive --session "tests-${{ matrix.python-version }}" -- --full-trace - - build-py310: - name: Build python 3.10 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-20.04, windows-2019] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: "3.10.0-rc.2" - # Conda does not support 3.10 yet, hence why it's skipped here - # TODO: Merge the two build jobs when 3.10 is released for conda - - name: Install Nox-under-test - run: | - python -m pip install --disable-pip-version-check . - - name: Run tests on ${{ matrix.os }} - run: nox --non-interactive --session "tests-3.10" -- --full-trace - + run: nox --non-interactive --error-on-missing-interpreter --session "tests-${{ matrix.python-version }}" -- --full-trace lint: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 - name: Install Nox-under-test run: | python -m pip install --disable-pip-version-check . - name: Lint - run: nox --non-interactive --session "lint" + run: nox --non-interactive --error-on-missing-interpreter --session "lint" docs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 - name: Install Nox-under-test run: | python -m pip install --disable-pip-version-check . - name: Docs - run: nox --non-interactive --session "docs" + run: nox --non-interactive --error-on-missing-interpreter --session "docs" deploy: needs: build runs-on: ubuntu-20.04 diff -Nru python-nox-2021.10.1/.gitignore python-nox-2022.1.7/.gitignore --- python-nox-2021.10.1/.gitignore 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/.gitignore 2022-01-07 23:23:23.000000000 +0000 @@ -76,3 +76,6 @@ # PyCharm .idea/ + +# Standard venv location +.venv diff -Nru python-nox-2021.10.1/MANIFEST.in python-nox-2022.1.7/MANIFEST.in --- python-nox-2021.10.1/MANIFEST.in 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/MANIFEST.in 2022-01-07 23:23:23.000000000 +0000 @@ -1,3 +1,3 @@ -include LICENSE recursive-include nox *.jinja2 include nox/py.typed +recursive-include tests *.py diff -Nru python-nox-2021.10.1/nox/command.py python-nox-2022.1.7/nox/command.py --- python-nox-2021.10.1/nox/command.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/command.py 2022-01-07 23:23:23.000000000 +0000 @@ -13,6 +13,7 @@ # limitations under the License. import os +import shlex import sys from typing import Any, Iterable, List, Optional, Sequence, Union @@ -31,7 +32,7 @@ """Raised when an executed command returns a non-success status code.""" def __init__(self, reason: Optional[str] = None) -> None: - super(CommandFailed, self).__init__(reason) + super().__init__(reason) self.reason = reason @@ -67,6 +68,11 @@ return clean_env +def _shlex_join(args: Sequence[str]) -> str: + # shlex.join() was added in Python 3.8 + return " ".join(shlex.quote(arg) for arg in args) + + def run( args: Sequence[str], *, @@ -84,7 +90,7 @@ success_codes = [0] cmd, args = args[0], args[1:] - full_cmd = f"{cmd} {' '.join(args)}" + full_cmd = f"{cmd} {_shlex_join(args)}" cmd_path = which(cmd, paths) diff -Nru python-nox-2021.10.1/nox/_decorators.py python-nox-2022.1.7/nox/_decorators.py --- python-nox-2021.10.1/nox/_decorators.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/_decorators.py 2022-01-07 23:23:23.000000000 +0000 @@ -1,8 +1,22 @@ +# Copyright 2020 Alethea Katherine Flowers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import copy import functools import inspect import types -from typing import Any, Callable, Dict, Iterable, List, Optional, cast +from typing import Any, Callable, Dict, Iterable, List, Optional from . import _typing @@ -15,20 +29,20 @@ cls, func: Callable[..., Any], *args: Any, **kwargs: Any ) -> "FunctionDecorator": obj = super().__new__(cls) - return cast("FunctionDecorator", functools.wraps(func)(obj)) + return functools.wraps(func)(obj) def _copy_func(src: Callable, name: Optional[str] = None) -> Callable: dst = types.FunctionType( src.__code__, - src.__globals__, # type: ignore + src.__globals__, # type: ignore[attr-defined] name=name or src.__name__, - argdefs=src.__defaults__, # type: ignore - closure=src.__closure__, # type: ignore + argdefs=src.__defaults__, # type: ignore[attr-defined] + closure=src.__closure__, # type: ignore[attr-defined] ) dst.__dict__.update(copy.deepcopy(src.__dict__)) dst = functools.update_wrapper(dst, src) - dst.__kwdefaults__ = src.__kwdefaults__ # type: ignore + dst.__kwdefaults__ = src.__kwdefaults__ # type: ignore[attr-defined] return dst diff -Nru python-nox-2021.10.1/nox/logger.py python-nox-2022.1.7/nox/logger.py --- python-nox-2021.10.1/nox/logger.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/logger.py 2022-01-07 23:23:23.000000000 +0000 @@ -71,7 +71,7 @@ return super().format(record) -class LoggerWithSuccessAndOutput(logging.getLoggerClass()): # type: ignore +class LoggerWithSuccessAndOutput(logging.getLoggerClass()): # type: ignore[misc] def __init__(self, name: str, level: int = logging.NOTSET): super().__init__(name, level) logging.addLevelName(SUCCESS, "SUCCESS") diff -Nru python-nox-2021.10.1/nox/__main__.py python-nox-2022.1.7/nox/__main__.py --- python-nox-2021.10.1/nox/__main__.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/__main__.py 2022-01-07 23:23:23.000000000 +0000 @@ -50,7 +50,6 @@ tasks.discover_manifest, tasks.filter_manifest, tasks.honor_list_request, - tasks.verify_manifest_nonempty, tasks.run_manifest, tasks.print_summary, tasks.create_report, diff -Nru python-nox-2021.10.1/nox/manifest.py python-nox-2022.1.7/nox/manifest.py --- python-nox-2021.10.1/nox/manifest.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/manifest.py 2022-01-07 23:23:23.000000000 +0000 @@ -65,11 +65,11 @@ global_config: argparse.Namespace, module_docstring: Optional[str] = None, ) -> None: - self._all_sessions = [] # type: List[SessionRunner] - self._queue = [] # type: List[SessionRunner] - self._consumed = [] # type: List[SessionRunner] - self._config = global_config # type: argparse.Namespace - self.module_docstring = module_docstring # type: Optional[str] + self._all_sessions: List[SessionRunner] = [] + self._queue: List[SessionRunner] = [] + self._consumed: List[SessionRunner] = [] + self._config: argparse.Namespace = global_config + self.module_docstring: Optional[str] = module_docstring # Create the sessions based on the provided session functions. for name, func in session_functions.items(): @@ -202,7 +202,7 @@ """ sessions = [] - # if backend is none we wont parametrize the pythons + # If the backend is "none", we won't parametrize `python`. backend = ( self._config.force_venv_backend or func.venv_backend @@ -217,7 +217,7 @@ if self._config.extra_pythons: # If extra python is provided, expand the func.python list to # include additional python interpreters - extra_pythons = self._config.extra_pythons # type: List[str] + extra_pythons: List[str] = self._config.extra_pythons if isinstance(func.python, (list, tuple, set)): func.python = _unique_list(*func.python, *extra_pythons) elif not multi and func.python: @@ -241,7 +241,7 @@ return sessions # Simple case: If this function is not parametrized, then make - # a simple session + # a simple session. if not hasattr(func, "parametrize"): long_names = [] if not multi: @@ -253,7 +253,7 @@ # Since this function is parametrized, we need to add a distinct # session for each permutation. - parametrize = func.parametrize # type: ignore + parametrize = func.parametrize # type: ignore[attr-defined] calls = Call.generate_calls(func, parametrize) for call in calls: long_names = [] diff -Nru python-nox-2021.10.1/nox/_option_set.py python-nox-2022.1.7/nox/_option_set.py --- python-nox-2021.10.1/nox/_option_set.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/_option_set.py 2022-01-07 23:23:23.000000000 +0000 @@ -191,12 +191,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.parser_args = args self.parser_kwargs = kwargs - self.options = ( + self.options: "collections.OrderedDict[str, Option]" = collections.OrderedDict() + self.groups: "collections.OrderedDict[str, OptionGroup]" = ( collections.OrderedDict() - ) # type: collections.OrderedDict[str, Option] - self.groups = ( - collections.OrderedDict() - ) # type: collections.OrderedDict[str, OptionGroup] + ) def add_options(self, *args: Option) -> None: """Adds a sequence of Options to the OptionSet. @@ -242,8 +240,8 @@ argument = groups[option.group.name].add_argument( *option.flags, help=option.help, default=option.default, **option.kwargs ) - if getattr(option, "completer"): - setattr(argument, "completer", option.completer) + if option.completer: + argument.completer = option.completer # type: ignore[attr-defined] return parser diff -Nru python-nox-2021.10.1/nox/_options.py python-nox-2022.1.7/nox/_options.py --- python-nox-2021.10.1/nox/_options.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/_options.py 2022-01-07 23:23:23.000000000 +0000 @@ -316,8 +316,8 @@ noxfile=True, merge_func=_default_venv_backend_merge_func, help="Virtual environment backend to use by default for nox sessions, this is ``'virtualenv'`` by default but " - "any of ``('virtualenv', 'conda', 'venv')`` are accepted.", - choices=["none", "virtualenv", "conda", "venv"], + "any of ``('virtualenv', 'conda', 'mamba', 'venv')`` are accepted.", + choices=["none", "virtualenv", "conda", "mamba", "venv"], ), _option_set.Option( "force_venv_backend", @@ -327,9 +327,9 @@ noxfile=True, merge_func=_force_venv_backend_merge_func, help="Virtual environment backend to force-use for all nox sessions in this run, overriding any other venv " - "backend declared in the nox file and ignoring the default backend. Any of ``('virtualenv', 'conda', 'venv')`` " + "backend declared in the nox file and ignoring the default backend. Any of ``('virtualenv', 'conda', 'mamba', 'venv')`` " "are accepted.", - choices=["none", "virtualenv", "conda", "venv"], + choices=["none", "virtualenv", "conda", "mamba", "venv"], ), _option_set.Option( "no_venv", @@ -463,7 +463,7 @@ "--forcecolor", "--force-color", group=options.groups["reporting"], - default=False, + default=lambda: "FORCE_COLOR" in os.environ, action="store_true", help="Force color output, even if stdout is not an interactive terminal.", ), diff -Nru python-nox-2021.10.1/nox/_parametrize.py python-nox-2022.1.7/nox/_parametrize.py --- python-nox-2021.10.1/nox/_parametrize.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/_parametrize.py 2022-01-07 23:23:23.000000000 +0000 @@ -81,7 +81,7 @@ def _apply_param_specs(param_specs: List[Param], f: Any) -> Any: previous_param_specs = getattr(f, "parametrize", None) new_param_specs = update_param_specs(previous_param_specs, param_specs) - setattr(f, "parametrize", new_param_specs) + f.parametrize = new_param_specs return f @@ -119,7 +119,7 @@ # If there's only one arg_name, arg_values_list should be a single item # or list. Transform it so it'll work with the combine step. - _arg_values_list = [] # type: List[Union[Param, Iterable[Union[Any, ArgValue]]]] + _arg_values_list: List[Union[Param, Iterable[Union[Any, ArgValue]]]] = [] if len(arg_names) == 1: # In this case, the arg_values_list can also just be a single item. # Must be mutable for the transformation steps @@ -141,7 +141,7 @@ ids = [] # Generate params for each item in the param_args_values list. - param_specs = [] # type: List[Param] + param_specs: List[Param] = [] for param_arg_values, param_id in itertools.zip_longest(_arg_values_list, ids): if isinstance(param_arg_values, Param): param_spec = param_arg_values @@ -155,7 +155,7 @@ def update_param_specs( - param_specs: Iterable[Param], new_specs: List[Param] + param_specs: Optional[Iterable[Param]], new_specs: List[Param] ) -> List[Param]: """Produces all combinations of the given sets of specs.""" if not param_specs: diff -Nru python-nox-2021.10.1/nox/py.typed python-nox-2022.1.7/nox/py.typed --- python-nox-2021.10.1/nox/py.typed 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/py.typed 2022-01-07 23:23:23.000000000 +0000 @@ -1 +1 @@ -# Marker file for PEP 561. The nox package uses inline types. \ No newline at end of file +# Marker file for PEP 561. The nox package uses inline types. diff -Nru python-nox-2021.10.1/nox/registry.py python-nox-2022.1.7/nox/registry.py --- python-nox-2021.10.1/nox/registry.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/registry.py 2022-01-07 23:23:23.000000000 +0000 @@ -22,7 +22,7 @@ F = TypeVar("F", bound=Callable[..., Any]) -_REGISTRY = collections.OrderedDict() # type: collections.OrderedDict[str, Func] +_REGISTRY: "collections.OrderedDict[str, Func]" = collections.OrderedDict() @overload diff -Nru python-nox-2021.10.1/nox/sessions.py python-nox-2022.1.7/nox/sessions.py --- python-nox-2021.10.1/nox/sessions.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/sessions.py 2022-01-07 23:23:23.000000000 +0000 @@ -20,6 +20,8 @@ import re import sys import unicodedata +import warnings +from types import TracebackType from typing import ( Any, Callable, @@ -30,6 +32,7 @@ Optional, Sequence, Tuple, + Type, Union, ) @@ -115,6 +118,23 @@ SKIPPED = 2 +class _WorkingDirContext: + def __init__(self, dir: Union[str, os.PathLike]) -> None: + self._prev_working_dir = os.getcwd() + os.chdir(dir) + + def __enter__(self) -> "_WorkingDirContext": + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + os.chdir(self._prev_working_dir) + + class Session: """The Session object is passed into each user-defined session function. @@ -128,7 +148,7 @@ self._runner = runner @property - def __dict__(self) -> "Dict[str, SessionRunner]": # type: ignore + def __dict__(self) -> "Dict[str, SessionRunner]": # type: ignore[override] """Attribute dictionary for object inspection. This is needed because ``__slots__`` turns off ``__dict__`` by @@ -182,7 +202,7 @@ """Create, and return, a temporary directory.""" tmpdir = os.path.join(self._runner.envdir, "tmp") os.makedirs(tmpdir, exist_ok=True) - self.env["TMPDIR"] = tmpdir + self.env["TMPDIR"] = os.path.abspath(tmpdir) return tmpdir @property @@ -209,10 +229,21 @@ """ return self._runner.global_config.invoked_from - def chdir(self, dir: Union[str, os.PathLike]) -> None: - """Change the current working directory.""" + def chdir(self, dir: Union[str, os.PathLike]) -> _WorkingDirContext: + """Change the current working directory. + + Can be used as a context manager to automatically restore the working directory:: + + with session.chdir("somewhere/deep/in/monorepo"): + # Runs in "/somewhere/deep/in/monorepo" + session.run("pytest") + + # Runs in original working directory + session.run("flake8") + + """ self.log(f"cd {dir}") - os.chdir(dir) + return _WorkingDirContext(dir) cd = chdir """An alias for :meth:`chdir`.""" @@ -262,6 +293,16 @@ session.run('cmd', '/c', 'del', 'docs/modules.rst') + If ``session.run`` fails, it will stop the session and will not run the next steps. + Basically, this will raise a Python exception. Taking this in count, you can use a + ``try...finally`` block for cleanup runs, that will run even if the other runs fail:: + + try: + session.run("coverage", "run", "-m", "pytest") + finally: + # Display coverage report even when tests fail. + session.run("coverage", "report") + :param env: A dictionary of environment variables to expose to the command. By default, all environment variables are passed. :type env: dict or None @@ -360,7 +401,11 @@ return nox.command.run(args, env=env, paths=self.bin_paths, **kwargs) def conda_install( - self, *args: str, auto_offline: bool = True, **kwargs: Any + self, + *args: str, + auto_offline: bool = True, + channel: Union[str, Sequence[str]] = "", + **kwargs: Any, ) -> None: """Install invokes `conda install`_ to install packages inside of the session's environment. @@ -369,7 +414,7 @@ session.conda_install('pandas') session.conda_install('numpy', 'scipy') - session.conda_install('--channel=conda-forge', 'dask==2.1.0') + session.conda_install('dask==2.1.0', channel='conda-forge') To install packages from a ``requirements.txt`` file:: @@ -387,13 +432,17 @@ # Install in editable mode. session.install('-e', '.', '--no-deps') + You can specify a conda channel using `channel=`; a falsy value will + not change the current channels. You can specify a list of channels if + needed. + Additional keyword args are the same as for :meth:`run`. .. _conda install: """ venv = self._runner.venv - prefix_args = () # type: Tuple[str, ...] + prefix_args: Tuple[str, ...] = () if isinstance(venv, CondaEnv): prefix_args = ("--prefix", venv.location) elif not isinstance(venv, PassthroughEnv): # pragma: no cover @@ -413,15 +462,21 @@ if "silent" not in kwargs: kwargs["silent"] = True - extraopts = () # type: Tuple[str, ...] + extraopts: List[str] = [] if auto_offline and venv.is_offline(): logger.warning( "Automatically setting the `--offline` flag as conda repo seems unreachable." ) - extraopts = ("--offline",) + extraopts.append("--offline") + + if channel: + if isinstance(channel, str): + extraopts.append(f"--channel={channel}") + else: + extraopts += [f"--channel={c}" for c in channel] self._run( - "conda", + venv.conda_cmd, "install", "--yes", *extraopts, @@ -464,6 +519,15 @@ raise ValueError( "A session without a virtualenv can not install dependencies." ) + if isinstance(venv, PassthroughEnv): + warnings.warn( + f"Session {self.name} does not have a virtual environment, " + "so use of session.install() is deprecated since it would modify " + "the global Python environment. If you're really sure that is " + 'what you want to do, use session.run("pip", "install", ...) instead.', + category=FutureWarning, + stacklevel=2, + ) if not args: raise ValueError("At least one argument required to install().") @@ -520,6 +584,10 @@ """Outputs a warning during the session.""" logger.warning(*args, **kwargs) + def debug(self, *args: Any, **kwargs: Any) -> None: + """Outputs a debug-level message during the session.""" + logger.debug(*args, **kwargs) + def error(self, *args: Any) -> "_typing.NoReturn": """Immediately aborts the session and optionally logs an error.""" raise _SessionQuit(*args) @@ -584,28 +652,29 @@ if backend is None or backend == "virtualenv": self.venv = VirtualEnv( self.envdir, - interpreter=self.func.python, # type: ignore + interpreter=self.func.python, # type: ignore[arg-type] reuse_existing=reuse_existing, venv_params=self.func.venv_params, ) - elif backend == "conda": + elif backend in {"conda", "mamba"}: self.venv = CondaEnv( self.envdir, - interpreter=self.func.python, # type: ignore + interpreter=self.func.python, # type: ignore[arg-type] reuse_existing=reuse_existing, venv_params=self.func.venv_params, + conda_cmd=backend, ) elif backend == "venv": self.venv = VirtualEnv( self.envdir, - interpreter=self.func.python, # type: ignore + interpreter=self.func.python, # type: ignore[arg-type] reuse_existing=reuse_existing, venv=True, venv_params=self.func.venv_params, ) else: raise ValueError( - f"Expected venv_backend one of ('virtualenv', 'conda', 'venv'), but got '{backend}'." + f"Expected venv_backend one of ('virtualenv', 'conda', 'mamba', 'venv'), but got '{backend}'." ) self.venv.create() diff -Nru python-nox-2021.10.1/nox/tasks.py python-nox-2022.1.7/nox/tasks.py --- python-nox-2021.10.1/nox/tasks.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/tasks.py 2022-01-07 23:23:23.000000000 +0000 @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import importlib.machinery -import io +import ast +import importlib.util import json import os +import sys import types from argparse import Namespace from typing import List, Union @@ -30,6 +31,41 @@ from nox.sessions import Result +def _load_and_exec_nox_module(global_config: Namespace) -> types.ModuleType: + """ + Loads, executes, then returns the global_config nox module. + + Args: + global_config (Namespace): The global config. + + Raises: + IOError: If the nox module cannot be loaded. This + exception is chosen such that it will be caught + by load_nox_module and logged appropriately. + + Returns: + types.ModuleType: The initialised nox module. + """ + spec = importlib.util.spec_from_file_location( + "user_nox_module", global_config.noxfile + ) + if not spec: + raise OSError(f"Could not get module spec from {global_config.noxfile}") + + module = importlib.util.module_from_spec(spec) + if not module: + raise OSError(f"Noxfile {global_config.noxfile} is not a valid python module.") + + sys.modules["user_nox_module"] = module + + loader = spec.loader + if not loader: # pragma: no cover + raise OSError(f"Could not get module loader for {global_config.noxfile}") + # See https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly + loader.exec_module(module) # type: ignore[attr-defined] + return module + + def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]: """Load the user's noxfile and return the module object for it. @@ -63,9 +99,8 @@ # guess. The original working directory (the directory that Nox was # invoked from) gets stored by the .invoke_from "option" in _options. os.chdir(noxfile_parent_dir) - return importlib.machinery.SourceFileLoader( - "user_nox_module", global_config.noxfile - ).load_module() + + return _load_and_exec_nox_module(global_config) except (VersionCheckFailed, InvalidVersionSpecifier) as error: logger.error(str(error)) @@ -75,7 +110,7 @@ f"Failed to load Noxfile {global_config.noxfile}, no such file exists." ) return 2 - except (IOError, OSError): + except OSError: logger.exception(f"Failed to load Noxfile {global_config.noxfile}") return 2 @@ -132,51 +167,61 @@ the manifest otherwise (to be sent to the next task). """ + # Shouldn't happen unless the noxfile is empty + if not manifest: + logger.error(f"No sessions found in {global_config.noxfile}.") + return 3 + # Filter by the name of any explicit sessions. # This can raise KeyError if a specified session does not exist; - # log this if it happens. - if global_config.sessions: + # log this if it happens. The sessions does not come from the noxfile + # if keywords is not empty. + if global_config.sessions is not None: try: manifest.filter_by_name(global_config.sessions) except KeyError as exc: logger.error("Error while collecting sessions.") logger.error(exc.args[0]) return 3 + if not manifest and not global_config.list_sessions: + print("No sessions selected. Please select a session with -s .\n") + _produce_listing(manifest, global_config) + return 0 # Filter by python interpreter versions. - # This function never errors, but may cause an empty list of sessions - # (which is an error condition later). if global_config.pythons: manifest.filter_by_python_interpreter(global_config.pythons) + if not manifest and not global_config.list_sessions: + logger.error("Python version selection caused no sessions to be selected.") + return 3 # Filter by keywords. - # This function never errors, but may cause an empty list of sessions - # (which is an error condition later). if global_config.keywords: + try: + ast.parse(global_config.keywords, mode="eval") + except SyntaxError: + logger.error( + "Error while collecting sessions: keywords argument must be a Python expression." + ) + return 3 + + # This function never errors, but may cause an empty list of sessions + # (which is an error condition later). manifest.filter_by_keywords(global_config.keywords) + if not manifest and not global_config.list_sessions: + logger.error("No sessions selected after filtering by keyword.") + return 3 + # Return the modified manifest. return manifest -def honor_list_request( - manifest: Manifest, global_config: Namespace -) -> Union[Manifest, int]: - """If --list was passed, simply list the manifest and exit cleanly. - - Args: - manifest (~.Manifest): The manifest of sessions to be run. - global_config (~nox.main.GlobalConfig): The global configuration. - - Returns: - Union[~.Manifest,int]: ``0`` if a listing is all that is requested, - the manifest otherwise (to be sent to the next task). - """ - if not global_config.list_sessions: - return manifest - +def _produce_listing(manifest: Manifest, global_config: Namespace) -> None: # If the user just asked for a list of sessions, print that - # and any docstring specified in noxfile.py and be done. + # and any docstring specified in noxfile.py and be done. This + # can also be called if noxfile sessions is an empty list. + if manifest.module_docstring: print(manifest.module_docstring.strip(), end="\n\n") @@ -212,25 +257,27 @@ print( f"\nsessions marked with {selected_color}*{reset} are selected, sessions marked with {skipped_color}-{reset} are skipped." ) - return 0 -def verify_manifest_nonempty( +def honor_list_request( manifest: Manifest, global_config: Namespace ) -> Union[Manifest, int]: - """Abort with an error code if the manifest is empty. + """If --list was passed, simply list the manifest and exit cleanly. Args: manifest (~.Manifest): The manifest of sessions to be run. global_config (~nox.main.GlobalConfig): The global configuration. Returns: - Union[~.Manifest,int]: ``3`` on an empty manifest, the manifest - otherwise. + Union[~.Manifest,int]: ``0`` if a listing is all that is requested, + the manifest otherwise (to be sent to the next task). """ - if not manifest: - return 3 - return manifest + if not global_config.list_sessions: + return manifest + + _produce_listing(manifest, global_config) + + return 0 def run_manifest(manifest: Manifest, global_config: Namespace) -> List[Result]: @@ -316,7 +363,7 @@ return results # Write the JSON report. - with io.open(global_config.report, "w") as report_file: + with open(global_config.report, "w") as report_file: json.dump( { "result": int(all(results)), diff -Nru python-nox-2021.10.1/nox/tox_to_nox.py python-nox-2022.1.7/nox/tox_to_nox.py --- python-nox-2021.10.1/nox/tox_to_nox.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/tox_to_nox.py 2022-01-07 23:23:23.000000000 +0000 @@ -15,7 +15,6 @@ """Naively converts tox.ini files into noxfile.py files.""" import argparse -import io import pkgutil from typing import Any, Iterator @@ -23,7 +22,7 @@ import tox.config _TEMPLATE = jinja2.Template( - pkgutil.get_data(__name__, "tox_to_nox.jinja2").decode("utf-8"), # type: ignore + pkgutil.get_data(__name__, "tox_to_nox.jinja2").decode("utf-8"), # type: ignore[union-attr] extensions=["jinja2.ext.do"], ) @@ -41,5 +40,5 @@ config = tox.config.parseconfig([]) output = _TEMPLATE.render(config=config, wrapjoin=wrapjoin) - with io.open(args.output, "w") as outfile: + with open(args.output, "w") as outfile: outfile.write(output) diff -Nru python-nox-2021.10.1/nox/_typing.py python-nox-2022.1.7/nox/_typing.py --- python-nox-2021.10.1/nox/_typing.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/_typing.py 2022-01-07 23:23:23.000000000 +0000 @@ -1,3 +1,17 @@ +# Copyright 2020 Alethea Katherine Flowers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + __all__ = ["TYPE_CHECKING", "ClassVar", "NoReturn", "Python"] import typing as _typing diff -Nru python-nox-2021.10.1/nox/_version.py python-nox-2022.1.7/nox/_version.py --- python-nox-2021.10.1/nox/_version.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/_version.py 2022-01-07 23:23:23.000000000 +0000 @@ -36,7 +36,7 @@ def get_nox_version() -> str: """Return the version of the installed Nox package.""" - return metadata.version("nox") # type: ignore + return metadata.version("nox") # type: ignore[no-untyped-call] def _parse_string_constant(node: ast.AST) -> Optional[str]: # pragma: no cover diff -Nru python-nox-2021.10.1/nox/virtualenv.py python-nox-2022.1.7/nox/virtualenv.py --- python-nox-2021.10.1/nox/virtualenv.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/virtualenv.py 2022-01-07 23:23:23.000000000 +0000 @@ -52,7 +52,7 @@ is_sandboxed = False # Special programs that aren't included in the environment. - allowed_globals = () # type: _typing.ClassVar[Tuple[Any, ...]] + allowed_globals: _typing.ClassVar[Tuple[Any, ...]] = () def __init__( self, bin_paths: None = None, env: Optional[Mapping[str, str]] = None @@ -92,8 +92,8 @@ """Find the Python executable using the Windows Launcher. This is based on :pep:397 which details that executing - ``py.exe -{version}`` should execute python with the requested - version. We then make the python process print out its full + ``py.exe -{version}`` should execute Python with the requested + version. We then make the Python process print out its full executable path which we use as the location for the version- specific Python interpreter. @@ -156,6 +156,8 @@ hints about the actual env. """ + conda_cmd = "conda" + @staticmethod def is_offline() -> bool: """As of now this is only used in conda_install""" @@ -180,10 +182,11 @@ If not specified, this will use the currently running Python. reuse_existing (Optional[bool]): Flag indicating if the conda environment should be reused if it already exists at ``location``. + conda_cmd (str): The name of the command, can be "conda" (default) or "mamba". """ is_sandboxed = True - allowed_globals = ("conda",) + allowed_globals = ("conda", "mamba") def __init__( self, @@ -191,13 +194,16 @@ interpreter: Optional[str] = None, reuse_existing: bool = False, venv_params: Any = None, + *, + conda_cmd: str = "conda", ): self.location_name = location self.location = os.path.abspath(location) self.interpreter = interpreter self.reuse_existing = reuse_existing self.venv_params = venv_params if venv_params else [] - super(CondaEnv, self).__init__() + self.conda_cmd = conda_cmd + super().__init__(env={"CONDA_PREFIX": self.location}) def _clean_location(self) -> bool: """Deletes existing conda environment""" @@ -205,7 +211,14 @@ if self.reuse_existing: return False else: - cmd = ["conda", "remove", "--yes", "--prefix", self.location, "--all"] + cmd = [ + self.conda_cmd, + "remove", + "--yes", + "--prefix", + self.location, + "--all", + ] nox.command.run(cmd, silent=True, log=False) # Make sure that location is clean try: @@ -218,9 +231,16 @@ @property def bin_paths(self) -> List[str]: """Returns the location of the conda env's bin folder.""" - # see https://docs.anaconda.com/anaconda/user-guide/tasks/integration/python-path/#examples + # see https://github.com/conda/conda/blob/f60f0f1643af04ed9a51da3dd4fa242de81e32f4/conda/activate.py#L563-L572 if _SYSTEM == "Windows": - return [self.location, os.path.join(self.location, "Scripts")] + return [ + self.location, + os.path.join(self.location, "Library", "mingw-w64", "bin"), + os.path.join(self.location, "Library", "usr", "bin"), + os.path.join(self.location, "Library", "bin"), + os.path.join(self.location, "Scripts"), + os.path.join(self.location, "bin"), + ] else: return [os.path.join(self.location, "bin")] @@ -233,7 +253,7 @@ return False - cmd = ["conda", "create", "--yes", "--prefix", self.location] + cmd = [self.conda_cmd, "create", "--yes", "--prefix", self.location] cmd.extend(self.venv_params) @@ -265,7 +285,7 @@ # DNS resolution to detect situation (1) or (2). host = gethostbyname("repo.anaconda.com") return host is None - except: # pragma: no cover # noqa E722 + except BaseException: # pragma: no cover return True @@ -303,11 +323,11 @@ self.location_name = location self.location = os.path.abspath(location) self.interpreter = interpreter - self._resolved = None # type: Union[None, str, InterpreterNotFound] + self._resolved: Union[None, str, InterpreterNotFound] = None self.reuse_existing = reuse_existing self.venv_or_virtualenv = "venv" if venv else "virtualenv" self.venv_params = venv_params if venv_params else [] - super(VirtualEnv, self).__init__(env={"VIRTUAL_ENV": self.location}) + super().__init__(env={"VIRTUAL_ENV": self.location}) def _clean_location(self) -> bool: """Deletes any existing virtual environment""" diff -Nru python-nox-2021.10.1/nox/workflow.py python-nox-2022.1.7/nox/workflow.py --- python-nox-2021.10.1/nox/workflow.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/nox/workflow.py 2022-01-07 23:23:23.000000000 +0000 @@ -43,7 +43,7 @@ return_value = None for function_ in workflow: # Send the previous task's return value if there was one. - args = [] # type: List[Any] + args: List[Any] = [] if return_value is not None: args.append(return_value) return_value = function_(*args, global_config=global_config) diff -Nru python-nox-2021.10.1/noxfile.py python-nox-2022.1.7/noxfile.py --- python-nox-2021.10.1/noxfile.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/noxfile.py 2022-01-07 23:23:23.000000000 +0000 @@ -16,12 +16,18 @@ import functools import os import platform +import shutil import sys import nox ON_WINDOWS_CI = "CI" in os.environ and platform.system() == "Windows" +# Skip 'conda_tests' if user doesn't have conda installed +nox.options.sessions = ["tests", "cover", "lint", "docs"] +if shutil.which("conda"): + nox.options.sessions.append("conda_tests") + def is_python_version(session, version): if not version.startswith(session.python): @@ -53,8 +59,7 @@ session.notify("cover") -# TODO: When conda supports 3.10 on GHA, add here too -@nox.session(python=["3.6", "3.7", "3.8", "3.9"], venv_backend="conda") +@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"], venv_backend="conda") def conda_tests(session): """Run test suite with pytest.""" session.create_tmp() @@ -85,41 +90,31 @@ session.run("coverage", "erase") -@nox.session(python="3.8") -def blacken(session): - """Run black code formatter.""" - session.install("black==21.5b2", "isort==5.8.0") - files = ["nox", "tests", "noxfile.py"] - session.run("black", *files) - session.run("isort", *files) - - -@nox.session(python="3.8") +@nox.session(python="3.9") def lint(session): - session.install( - "flake8==3.9.2", - "black==21.6b0", - "isort==5.8.0", - "mypy==0.902", - "types-jinja2", - "packaging", - "importlib_metadata", + """Run pre-commit linting.""" + session.install("pre-commit") + # See https://github.com/theacodes/nox/issues/545 + # and https://github.com/pre-commit/pre-commit/issues/2178#issuecomment-1002163763 + session.run( + "pre-commit", + "run", + "--all-files", + "--show-diff-on-failure", + "--hook-stage=manual", + env={"SETUPTOOLS_USE_DISTUTILS": "stdlib"}, + *session.posargs, ) - session.run("mypy") - files = ["nox", "tests", "noxfile.py"] - session.run("black", "--check", *files) - session.run("isort", "--check", *files) - session.run("flake8", *files) -@nox.session(python="3.8") +@nox.session def docs(session): """Build the documentation.""" output_dir = os.path.join(session.create_tmp(), "output") doctrees, html = map( functools.partial(os.path.join, output_dir), ["doctrees", "html"] ) - session.run("rm", "-rf", output_dir, external=True) + shutil.rmtree(output_dir, ignore_errors=True) session.install("-r", "requirements-test.txt") session.install(".") session.cd("docs") diff -Nru python-nox-2021.10.1/.pre-commit-config.yaml python-nox-2022.1.7/.pre-commit-config.yaml --- python-nox-2021.10.1/.pre-commit-config.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-nox-2022.1.7/.pre-commit-config.yaml 2022-01-07 23:23:23.000000000 +0000 @@ -0,0 +1,95 @@ +ci: + autoupdate_commit_msg: "chore: update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + +repos: +- repo: https://github.com/psf/black + rev: 21.12b0 + hooks: + - id: black + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: trailing-whitespace + +- repo: https://github.com/asottile/pyupgrade + rev: v2.29.1 + hooks: + - id: pyupgrade + args: [--py36-plus] + +- repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + +- repo: https://github.com/asottile/setup-cfg-fmt + rev: v1.20.0 + hooks: + - id: setup-cfg-fmt + +- repo: https://github.com/hadialqattan/pycln + rev: v1.1.0 + hooks: + - id: pycln + args: [--config=pyproject.toml] + +- repo: https://github.com/asottile/yesqa + rev: v1.3.0 + hooks: + - id: yesqa + additional_dependencies: &flake8-dependencies + - flake8-bugbear + +- repo: https://github.com/pycqa/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + exclude: docs/conf.py + additional_dependencies: *flake8-dependencies + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.920 + hooks: + - id: mypy + files: ^nox/ + args: [--show-error-codes] + additional_dependencies: + - types-jinja2 + - packaging + - importlib_metadata + +- repo: https://github.com/codespell-project/codespell + rev: v2.1.0 + hooks: + - id: codespell + +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.9.0 + hooks: + - id: python-check-blanket-noqa + - id: python-check-blanket-type-ignore + - id: python-no-log-warn + exclude: ^tests/test_sessions.py$ + - id: python-no-eval + exclude: ^nox/manifest.py$ + - id: python-use-type-annotations + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + +- repo: https://github.com/mgedmin/check-manifest + rev: "0.47" + hooks: + - id: check-manifest + stages: [manual] diff -Nru python-nox-2021.10.1/pyproject.toml python-nox-2022.1.7/pyproject.toml --- python-nox-2021.10.1/pyproject.toml 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/pyproject.toml 2022-01-07 23:23:23.000000000 +0000 @@ -41,4 +41,14 @@ [[tool.mypy.overrides]] module = [ "argcomplete", "colorlog.*", "py", "tox.*" ] -ignore_missing_imports = true \ No newline at end of file +ignore_missing_imports = true + +[tool.check-manifest] +ignore = [ + "docs/**", + "noxfile.py", + "requirements-conda-test.txt", + "requirements-test.txt", + "*.md", + ".*", +] diff -Nru python-nox-2021.10.1/.readthedocs.yml python-nox-2022.1.7/.readthedocs.yml --- python-nox-2021.10.1/.readthedocs.yml 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/.readthedocs.yml 2022-01-07 23:23:23.000000000 +0000 @@ -1,10 +1,11 @@ -formats: [] - +version: 2 build: - image: latest + os: ubuntu-20.04 + tools: + python: "3.10" python: - version: 3.6 - pip_install: true - -requirements_file: requirements-test.txt + install: + - requirements: requirements-test.txt + - method: pip + path: . diff -Nru python-nox-2021.10.1/requirements-conda-test.txt python-nox-2022.1.7/requirements-conda-test.txt --- python-nox-2021.10.1/requirements-conda-test.txt 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/requirements-conda-test.txt 2022-01-07 23:23:23.000000000 +0000 @@ -1,7 +1,7 @@ argcomplete >=1.9.4,<2.0 -colorlog >=2.6.1,<4.0.0 -py >=1.4.0,<2.0.0 -virtualenv >=14.0.0 +colorlog >=2.6.1,<7.0.0 jinja2 +py >=1.4.0,<2.0.0 +pytest tox -pytest \ No newline at end of file +virtualenv >=14.0.0 diff -Nru python-nox-2021.10.1/requirements-test.txt python-nox-2022.1.7/requirements-test.txt --- python-nox-2021.10.1/requirements-test.txt 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/requirements-test.txt 2022-01-07 23:23:23.000000000 +0000 @@ -1,7 +1,7 @@ flask pytest pytest-cov +recommonmark sphinx sphinx-autobuild -recommonmark witchhazel diff -Nru python-nox-2021.10.1/setup.cfg python-nox-2022.1.7/setup.cfg --- python-nox-2021.10.1/setup.cfg 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/setup.cfg 2022-01-07 23:23:23.000000000 +0000 @@ -1,6 +1,6 @@ [metadata] name = nox -version = 2021.10.1 +version = 2022.1.7 description = Flexible test automation. long_description = file: README.rst long_description_content_type = text/x-rst @@ -25,6 +25,7 @@ Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Topic :: Software Development :: Testing keywords = testing automation tox project_urls = @@ -40,9 +41,9 @@ colorlog>=2.6.1,<7.0.0 packaging>=20.9 py>=1.4.0,<2.0.0 - typing_extensions>=3.7.4;python_version < '3.8' virtualenv>=14.0.0 - importlib_metadata;python_version < '3.8' + importlib-metadata;python_version < '3.8' + typing-extensions>=3.7.4;python_version < '3.8' python_requires = >=3.6 include_package_data = True zip_safe = False @@ -61,8 +62,6 @@ nox = py.typed [flake8] -# Ignore black styles. -ignore = E501, W503, E203 -# Imports +extend-ignore = E501, W503, E203 import-order-style = google application-import-names = nox,tests diff -Nru python-nox-2021.10.1/tests/test_command.py python-nox-2022.1.7/tests/test_command.py --- python-nox-2021.10.1/tests/test_command.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/tests/test_command.py 2022-01-07 23:23:23.000000000 +0000 @@ -121,7 +121,7 @@ def test_run_env_systemroot(): - systemroot = os.environ.setdefault("SYSTEMROOT", str("sigil")) + systemroot = os.environ.setdefault("SYSTEMROOT", "sigil") result = nox.command.run( [PYTHON, "-c", 'import os; print(os.environ["SYSTEMROOT"])'], silent=True @@ -460,7 +460,7 @@ def test_output_decoding_non_ascii() -> None: - result = nox.popen.decode_output("ü".encode("utf-8")) + result = nox.popen.decode_output("ü".encode()) assert result == "ü" diff -Nru python-nox-2021.10.1/tests/test_main.py python-nox-2022.1.7/tests/test_main.py --- python-nox-2021.10.1/tests/test_main.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/tests/test_main.py 2022-01-07 23:23:23.000000000 +0000 @@ -602,6 +602,7 @@ @pytest.mark.parametrize(("isatty_value", "expected"), [(True, True), (False, False)]) def test_main_color_from_isatty(monkeypatch, isatty_value, expected): + monkeypatch.delenv("FORCE_COLOR", raising=False) monkeypatch.setattr(sys, "argv", [sys.executable]) with mock.patch("nox.workflow.execute") as execute: execute.return_value = 0 @@ -626,6 +627,7 @@ ], ) def test_main_color_options(monkeypatch, color_opt, expected): + monkeypatch.delenv("FORCE_COLOR", raising=False) monkeypatch.setattr(sys, "argv", [sys.executable, color_opt]) with mock.patch("nox.workflow.execute") as execute: execute.return_value = 0 diff -Nru python-nox-2021.10.1/tests/test_manifest.py python-nox-2022.1.7/tests/test_manifest.py --- python-nox-2021.10.1/tests/test_manifest.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/tests/test_manifest.py 2022-01-07 23:23:23.000000000 +0000 @@ -48,7 +48,7 @@ def test__normalize_arg(): assert _normalize_arg('test(foo="bar")') == _normalize_arg('test(foo="bar")') - # In the case of SyntaxError it should fallback to strng + # In the case of SyntaxError it should fallback to string assert ( _normalize_arg("datetime.datetime(1990; 2, 18),") == "datetime.datetime(1990; 2, 18)," @@ -81,7 +81,7 @@ assert "baz" not in manifest # Establish that __contains__ works post-iteration. - for session in manifest: + for _session in manifest: pass assert "foo" in manifest assert "bar" in manifest @@ -104,7 +104,7 @@ # Establish that the sessions are still present even after being # consumed by iteration. - for session in manifest: + for _session in manifest: pass assert manifest["foo"].func is sessions["foo"] assert manifest["bar"].func is sessions["bar"] @@ -144,7 +144,7 @@ sessions = create_mock_sessions() manifest = Manifest(sessions, create_mock_config()) assert len(manifest) == 2 - for session in manifest: + for _session in manifest: assert len(manifest) == 2 diff -Nru python-nox-2021.10.1/tests/test__parametrize.py python-nox-2022.1.7/tests/test__parametrize.py --- python-nox-2021.10.1/tests/test__parametrize.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/tests/test__parametrize.py 2022-01-07 23:23:23.000000000 +0000 @@ -34,7 +34,7 @@ def test_param_eq_fail(): with pytest.raises(NotImplementedError): - _parametrize.Param() == "a" + _parametrize.Param() == "a" # noqa: B015 def test_parametrize_decorator_one(): diff -Nru python-nox-2021.10.1/tests/test_sessions.py python-nox-2022.1.7/tests/test_sessions.py --- python-nox-2021.10.1/tests/test_sessions.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/tests/test_sessions.py 2022-01-07 23:23:23.000000000 +0000 @@ -84,7 +84,7 @@ with tempfile.TemporaryDirectory() as root: runner.global_config.envdir = root tmpdir = session.create_tmp() - assert session.env["TMPDIR"] == tmpdir + assert session.env["TMPDIR"] == os.path.abspath(tmpdir) assert tmpdir.startswith(root) def test_create_tmp_twice(self): @@ -94,7 +94,7 @@ runner.venv.bin = bin session.create_tmp() tmpdir = session.create_tmp() - assert session.env["TMPDIR"] == tmpdir + assert session.env["TMPDIR"] == os.path.abspath(tmpdir) assert tmpdir.startswith(root) def test_properties(self): @@ -164,6 +164,19 @@ assert os.getcwd() == cdto os.chdir(current_cwd) + def test_chdir_ctx(self, tmpdir): + cdto = str(tmpdir.join("cdbby").ensure(dir=True)) + current_cwd = os.getcwd() + + session, _ = self.make_session_and_runner() + + with session.chdir(cdto): + assert os.getcwd() == cdto + + assert os.getcwd() == current_cwd + + os.chdir(current_cwd) + def test_invoked_from(self, tmpdir): cdto = str(tmpdir.join("cdbby").ensure(dir=True)) current_cwd = os.getcwd() @@ -384,7 +397,13 @@ "auto_offline", [False, True], ids="auto_offline={}".format ) @pytest.mark.parametrize("offline", [False, True], ids="offline={}".format) - def test_conda_install(self, auto_offline, offline): + @pytest.mark.parametrize("conda", ["conda", "mamba"], ids=str) + @pytest.mark.parametrize( + "channel", + ["", "conda-forge", ["conda-forge", "bioconda"]], + ids=["default", "conda-forge", "bioconda"], + ) + def test_conda_install(self, auto_offline, offline, conda, channel): runner = nox.sessions.SessionRunner( name="test", signatures=["test"], @@ -396,6 +415,7 @@ runner.venv.location = "/path/to/conda/env" runner.venv.env = {} runner.venv.is_offline = lambda: offline + runner.venv.conda_cmd = conda class SessionNoSlots(nox.sessions.Session): pass @@ -403,10 +423,16 @@ session = SessionNoSlots(runner=runner) with mock.patch.object(session, "_run", autospec=True) as run: - args = ("--offline",) if auto_offline and offline else () - session.conda_install("requests", "urllib3", auto_offline=auto_offline) + args = ["--offline"] if auto_offline and offline else [] + if channel and isinstance(channel, str): + args.append(f"--channel={channel}") + else: + args += [f"--channel={c}" for c in channel] + session.conda_install( + "requests", "urllib3", auto_offline=auto_offline, channel=channel + ) run.assert_called_once_with( - "conda", + conda, "install", "--yes", *args, @@ -434,6 +460,7 @@ runner.venv.location = "/path/to/conda/env" runner.venv.env = {} runner.venv.is_offline = lambda: True + runner.venv.conda_cmd = "conda" runner.global_config.no_install = no_install runner.venv._reused = reused @@ -460,6 +487,7 @@ runner.venv.location = "/path/to/conda/env" runner.venv.env = {} runner.venv.is_offline = lambda: False + runner.venv.conda_cmd = "conda" class SessionNoSlots(nox.sessions.Session): pass @@ -563,6 +591,39 @@ external="error", ) + def test_install_no_venv_deprecated(self): + runner = nox.sessions.SessionRunner( + name="test", + signatures=["test"], + func=mock.sentinel.func, + global_config=_options.options.namespace(posargs=[]), + manifest=mock.create_autospec(nox.manifest.Manifest), + ) + runner.venv = mock.create_autospec(nox.virtualenv.PassthroughEnv) + runner.venv.env = {} + + class SessionNoSlots(nox.sessions.Session): + pass + + session = SessionNoSlots(runner=runner) + + with mock.patch.object(session, "_run", autospec=True) as run: + with pytest.warns( + FutureWarning, + match=r"use of session\.install\(\) is deprecated since it would modify the global Python environment", + ): + session.install("requests", "urllib3") + run.assert_called_once_with( + "python", + "-m", + "pip", + "install", + "requests", + "urllib3", + silent=True, + external="error", + ) + def test_notify(self): session, runner = self.make_session_and_runner() @@ -609,6 +670,14 @@ assert "meep" in caplog.text + def test_debug(self, caplog): + caplog.set_level(logging.DEBUG) + session, _ = self.make_session_and_runner() + + session.debug("meep") + + assert "meep" in caplog.text + def test_error(self, caplog): caplog.set_level(logging.ERROR) session, _ = self.make_session_and_runner() diff -Nru python-nox-2021.10.1/tests/test_tasks.py python-nox-2022.1.7/tests/test_tasks.py --- python-nox-2021.10.1/tests/test_tasks.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/tests/test_tasks.py 2022-01-07 23:23:23.000000000 +0000 @@ -13,8 +13,8 @@ # limitations under the License. import argparse +import builtins import copy -import io import json import os import platform @@ -95,9 +95,7 @@ our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py") config = _options.options.namespace(noxfile=str(our_noxfile)) - with mock.patch( - "nox.tasks.importlib.machinery.SourceFileLoader.load_module" - ) as mock_load: + with mock.patch("nox.tasks.importlib.util.module_from_spec") as mock_load: mock_load.side_effect = IOError assert tasks.load_nox_module(config) == 2 @@ -112,15 +110,35 @@ our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py") config = _options.options.namespace(noxfile=str(our_noxfile)) - with mock.patch( - "nox.tasks.importlib.machinery.SourceFileLoader.load_module" - ) as mock_load: + with mock.patch("nox.tasks.importlib.util.module_from_spec") as mock_load: mock_load.side_effect = OSError assert tasks.load_nox_module(config) == 2 assert "Failed to load Noxfile" in caplog.text +def test_load_nox_module_invalid_spec(): + our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py") + config = _options.options.namespace(noxfile=str(our_noxfile)) + + with mock.patch("nox.tasks.importlib.util.spec_from_file_location") as mock_spec: + mock_spec.return_value = None + + with pytest.raises(IOError): + tasks._load_and_exec_nox_module(config) + + +def test_load_nox_module_invalid_module(): + our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py") + config = _options.options.namespace(noxfile=str(our_noxfile)) + + with mock.patch("nox.tasks.importlib.util.module_from_spec") as mock_spec: + mock_spec.return_value = None + + with pytest.raises(IOError): + tasks._load_and_exec_nox_module(config) + + @pytest.fixture def reset_needs_version(): """Do not leak ``nox.needs_version`` between tests.""" @@ -191,7 +209,7 @@ def test_filter_manifest(): config = _options.options.namespace( - sessions=(), pythons=(), keywords=(), posargs=[] + sessions=None, pythons=(), keywords=(), posargs=[] ) manifest = Manifest({"foo": session_func, "bar": session_func}, config) return_value = tasks.filter_manifest(manifest, config) @@ -210,7 +228,7 @@ def test_filter_manifest_pythons(): config = _options.options.namespace( - sessions=(), pythons=("3.8",), keywords=(), posargs=[] + sessions=None, pythons=("3.8",), keywords=(), posargs=[] ) manifest = Manifest( {"foo": session_func_with_python, "bar": session_func, "baz": session_func}, @@ -221,9 +239,22 @@ assert len(manifest) == 1 +def test_filter_manifest_pythons_not_found(caplog): + config = _options.options.namespace( + sessions=None, pythons=("1.2",), keywords=(), posargs=[] + ) + manifest = Manifest( + {"foo": session_func_with_python, "bar": session_func, "baz": session_func}, + config, + ) + return_value = tasks.filter_manifest(manifest, config) + assert return_value == 3 + assert "Python version selection caused no sessions to be selected." in caplog.text + + def test_filter_manifest_keywords(): config = _options.options.namespace( - sessions=(), pythons=(), keywords="foo or bar", posargs=[] + sessions=None, pythons=(), keywords="foo or bar", posargs=[] ) manifest = Manifest( {"foo": session_func, "bar": session_func, "baz": session_func}, config @@ -233,6 +264,27 @@ assert len(manifest) == 2 +def test_filter_manifest_keywords_not_found(caplog): + config = _options.options.namespace( + sessions=None, pythons=(), keywords="mouse or python", posargs=[] + ) + manifest = Manifest( + {"foo": session_func, "bar": session_func, "baz": session_func}, config + ) + return_value = tasks.filter_manifest(manifest, config) + assert return_value == 3 + assert "No sessions selected after filtering by keyword." in caplog.text + + +def test_filter_manifest_keywords_syntax_error(): + config = _options.options.namespace( + sessions=None, pythons=(), keywords="foo:bar", posargs=[] + ) + manifest = Manifest({"foo_bar": session_func, "foo_baz": session_func}, config) + return_value = tasks.filter_manifest(manifest, config) + assert return_value == 3 + + def test_honor_list_request_noop(): config = _options.options.namespace(list_sessions=False) manifest = {"thing": mock.sentinel.THING} @@ -319,20 +371,43 @@ assert "Hello I'm a docstring" not in out +def test_empty_session_list_in_noxfile(capsys): + config = _options.options.namespace(noxfile="noxfile.py", sessions=(), posargs=[]) + manifest = Manifest({"session": session_func}, config) + return_value = tasks.filter_manifest(manifest, global_config=config) + assert return_value == 0 + assert "No sessions selected." in capsys.readouterr().out + + +def test_empty_session_None_in_noxfile(capsys): + config = _options.options.namespace(noxfile="noxfile.py", sessions=None, posargs=[]) + manifest = Manifest({"session": session_func}, config) + return_value = tasks.filter_manifest(manifest, global_config=config) + assert return_value == manifest + + def test_verify_manifest_empty(): config = _options.options.namespace(sessions=(), keywords=()) manifest = Manifest({}, config) - return_value = tasks.verify_manifest_nonempty(manifest, global_config=config) + return_value = tasks.filter_manifest(manifest, global_config=config) assert return_value == 3 def test_verify_manifest_nonempty(): - config = _options.options.namespace(sessions=(), keywords=(), posargs=[]) + config = _options.options.namespace(sessions=None, keywords=(), posargs=[]) manifest = Manifest({"session": session_func}, config) - return_value = tasks.verify_manifest_nonempty(manifest, global_config=config) + return_value = tasks.filter_manifest(manifest, global_config=config) assert return_value == manifest +def test_verify_manifest_list(capsys): + config = _options.options.namespace(sessions=(), keywords=(), posargs=[]) + manifest = Manifest({"session": session_func}, config) + return_value = tasks.filter_manifest(manifest, global_config=config) + assert return_value == 0 + assert "Please select a session" in capsys.readouterr().out + + @pytest.mark.parametrize("with_warnings", [False, True], ids="with_warnings={}".format) def test_run_manifest(with_warnings): # Set up a valid manifest. @@ -429,7 +504,7 @@ def test_create_report_noop(): config = _options.options.namespace(report=None) - with mock.patch.object(io, "open", autospec=True) as open_: + with mock.patch.object(builtins, "open", autospec=True) as open_: results = tasks.create_report(mock.sentinel.RESULTS, config) assert not open_.called assert results is mock.sentinel.RESULTS @@ -445,7 +520,7 @@ status=sessions.Status.SUCCESS, ) ] - with mock.patch.object(io, "open", autospec=True) as open_: + with mock.patch.object(builtins, "open", autospec=True) as open_: with mock.patch.object(json, "dump", autospec=True) as dump: answer = tasks.create_report(results, config) assert answer is results diff -Nru python-nox-2021.10.1/tests/test__version.py python-nox-2022.1.7/tests/test__version.py --- python-nox-2021.10.1/tests/test__version.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/tests/test__version.py 2022-01-07 23:23:23.000000000 +0000 @@ -46,7 +46,7 @@ def test_get_nox_version() -> None: """It returns something that looks like a Nox version.""" result = get_nox_version() - year, month, day = [int(part) for part in result.split(".")[:3]] + year, month, day, *_ = (int(part) for part in result.split(".")) assert year >= 2020 diff -Nru python-nox-2021.10.1/tests/test_virtualenv.py python-nox-2022.1.7/tests/test_virtualenv.py --- python-nox-2021.10.1/tests/test_virtualenv.py 2021-10-01 13:02:43.000000000 +0000 +++ python-nox-2022.1.7/tests/test_virtualenv.py 2022-01-07 23:23:23.000000000 +0000 @@ -13,7 +13,9 @@ # limitations under the License. import os +import re import shutil +import subprocess import sys from textwrap import dedent from unittest import mock @@ -230,14 +232,40 @@ @mock.patch("nox.virtualenv._SYSTEM", new="Windows") def test_condaenv_bin_windows(make_conda): venv, dir_ = make_conda() - assert [dir_.strpath, dir_.join("Scripts").strpath] == venv.bin_paths + assert [ + dir_.strpath, + dir_.join("Library", "mingw-w64", "bin").strpath, + dir_.join("Library", "usr", "bin").strpath, + dir_.join("Library", "bin").strpath, + dir_.join("Scripts").strpath, + dir_.join("bin").strpath, + ] == venv.bin_paths +@pytest.mark.skipif(not HAS_CONDA, reason="Missing conda command.") def test_condaenv_(make_conda): venv, dir_ = make_conda() assert not venv.is_offline() +@pytest.mark.skipif(not HAS_CONDA, reason="Missing conda command.") +def test_condaenv_detection(make_conda): + venv, dir_ = make_conda() + venv.create() + + proc_result = subprocess.run( + [py.path.local.sysfind("conda").strpath, "list"], + env=venv.env, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + output = proc_result.stdout.decode() + path_regex = re.compile(r"packages in environment at (?P.+):") + + assert path_regex.search(output).group("env_dir") == dir_.strpath + + def test_constructor_defaults(make_one): venv, _ = make_one() assert venv.location