Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nox.options which allows specifying command-line configuration in the Noxfile #145

Merged
merged 4 commits into from
Oct 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,42 @@ Nox will call your session functions with a :class:`Session` object. You use thi
.. autoclass:: Session
:members:
:undoc-members:


Modifying Nox's behavior in the Noxfile
---------------------------------------

Nox has various :doc:`command line arguments <usage>` that can be used to modify its behavior. Some of these can also be specified in the Noxfile using ``nox.options``. For example, if you wanted to store Nox's virtualenvs in a different directory without needing to pass it into ``nox`` every time:
theacodes marked this conversation as resolved.
Show resolved Hide resolved

.. code-block:: python

import nox

nox.options.envdir = ".cache"

@nox.session
def tests(session):
...

Or, if you wanted to provide a set of sessions that are run by default:

.. code-block:: python

import nox

nox.options.sessions = ["lint", "tests-3.6"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When does this get verified?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same place as the command-line arg, during tasks.filter_manifest -> manifest.filter_by_name. It'll barf if you pass garbage, ofc.


...

The following options can be specified in the Noxfile:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any plans for checking if this goes stale? Something like:

.. testsetup:: foo

    import nox
    def info(attr):
        return getattr(nox.options, attr).__doc__

.. doctest:: foo

    >>> names = sorted(dir(nox.options))
    >>> for name in names:
    ...     print(name)
    ...     print('    {}'.format(info(name))
    envdir
        Equivalent to specifying ...
    sessions
        Equivalent to specifying ...
    ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're going to remain relatively conservative on adding new command line args (I say this as I'm writing a PR to add a new one...). I also don't have doctest setup yet, so.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't have doctest setup yet, so.

That's a bad reason?

But it's up to you. That was just a suggestion. You wouldn't get pretty CSS in a doctest. Or maybe it's possible, I just don't know how.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a great reason to never do it, but it's a good reason not to do it right now. We can file a bug and pick it up later. (e.g. perfect being the enemy of good here, especially for collateral like documentation).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Feel free to file if you like the idea / if you want doctests. I love doctests and forbid code-block:: python in some projects (e.g. bezier)


* ``nox.options.envdir`` is equivalent to specifying :ref:`--envdir <opt-envdir>`.
* ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions <opt-sessions-and-keywords>`.
* ``nox.options.keywords`` is equivalent to specifying :ref:`-k or --keywords <opt-sessions-and-keywords>`.
* ``nox.options.reuse_existing_virtualenvs`` is equivalent to specifying :ref:`--reuse-existing-virtualenvs <opt-reuse-existing-virtualenvs>`. You can force this off by specifying ``--no-reuse-existing-virtualenvs`` during invocation.
* ``nox.options.stop_on_first_error`` is equivalent to specifying :ref:`--stop-on-first-error <opt-stop-on-first-error>`. You can force this off by specifying ``--no-stop-on-first-error`` during invocation.
* ``nox.options.error_on_missing_interpreters`` is equivalent to specifying :ref:`--error-on-missing-interpreters <opt-error-on-missing-interpreters>`. You can force this off by specifying ``--no-error-on-missing-interpreters`` during invocation.
* ``nox.options.report`` is equivalent to specifying :ref:`--report <opt-report>`.

dhermes marked this conversation as resolved.
Show resolved Hide resolved

When invoking ``nox``, any options specified on the command line take precedence over the options specified in the Noxfile. If either ``--sessions`` or ``--keywords`` is specified on the command line, *both* options specified in the Noxfile will be ignored.
24 changes: 24 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ You can run every session by just executing `nox` without any arguments::
The order that sessions are executed is the order that they appear in the Noxfile.


.. _opt-sessions-and-keywords:

Specifying one or more sessions
-------------------------------

Expand Down Expand Up @@ -76,6 +78,8 @@ Then running ``nox --session tests`` will actually run all parametrized versions
nox --session "tests(django='2.0')"


.. _opt-reuse-existing-virtualenvs:

Re-using virtualenvs
--------------------

Expand All @@ -85,13 +89,20 @@ By default nox deletes and recreates virtualenvs every time it is run. This is u
nox --reuse-existing-virtualenvs


If the Noxfile sets ``nox.options.reuse_existing_virtualenvs``, you can override the Noxfile setting from the command line by using ``--no-reuse-existing-virtualenvs``.

.. _opt-stop-on-first-error:

Stopping if any session fails
-----------------------------

By default nox will continue to run all sessions even if one fails. You can use ``--stop-on-first-error`` to make nox abort as soon as the first session fails::

nox --stop-on-first-error

If the Noxfile sets ``nox.options.stop_on_first_error``, you can override the Noxfile setting from the command line by using ``--no-stop-on-first-error``.

.. _opt-error-on-missing-interpreters:

Failing sessions when the interpreter is missing
------------------------------------------------
Expand All @@ -100,6 +111,7 @@ By default, Nox will skip sessions where the Python interpreter can't be found.

nox --error-on-missing-interpreters

If the Noxfile sets ``nox.options.error_on_missing_interpreters``, you can override the Noxfile setting from the command line by using ``--no-error-on-missing-interpreters``.

Specifying a different configuration file
-----------------------------------------
Expand All @@ -110,6 +122,8 @@ If for some reason your noxfile is not named *noxfile.py*, you can use ``--noxfi
nox -f something.py


.. _opt-envdir:

Storing virtualenvs in a different directory
--------------------------------------------

Expand All @@ -136,6 +150,16 @@ However, this will never output colorful logs::
nox --nocolor


.. _opt-report:

Outputting a machine-readable report
theacodes marked this conversation as resolved.
Show resolved Hide resolved
------------------------------------

You can output a report in ``json`` format by specifying ``--report``::

nox --report status.json


Windows
-------

Expand Down
3 changes: 2 additions & 1 deletion nox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from nox._options import options
from nox._parametrize import parametrize_decorator as parametrize
from nox.registry import session_decorator as session

__all__ = ["parametrize", "session"]
__all__ = ["parametrize", "session", "options"]
70 changes: 66 additions & 4 deletions nox/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,67 @@
from nox.logger import setup_logging


def _default_with_off_flag(current, default, off_flag):
"""Helper method for merging command line args and noxfile config.

Returns False if off_flag is set, otherwise, returns the default value if
set, otherwise, returns the current value.
"""
return (default or current) and not off_flag


class GlobalConfig:
def __init__(self, args):
self.noxfile = args.noxfile
self.envdir = os.path.abspath(args.envdir)
self.envdir = args.envdir
dhermes marked this conversation as resolved.
Show resolved Hide resolved
self.sessions = args.sessions
self.keywords = args.keywords
self.list_sessions = args.list_sessions
self.reuse_existing_virtualenvs = args.reuse_existing_virtualenvs
self.no_reuse_existing_virtualenvs = args.no_reuse_existing_virtualenvs
self.stop_on_first_error = args.stop_on_first_error
self.no_stop_on_first_error = args.no_stop_on_first_error
self.error_on_missing_interpreters = args.error_on_missing_interpreters
self.no_error_on_missing_interpreters = args.no_error_on_missing_interpreters
self.posargs = args.posargs
self.report = args.report

if self.posargs and self.posargs[0] == "--":
self.posargs.pop(0)

def merge_from_options(self, options):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a docstring? I'm mostly just unclear on the shape of options

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"""Update the config from the Noxfile-specified options.

The options function as "defaults" for the most part, with some small
caveats documented in the body of the function.

Args:
options (nox._options.options): The options set in the Noxfile.
"""
# If *either* sessions or keywords are specified on the command line,
# ignore *both* sessions and keywords in Noxfile.
if not self.sessions and not self.keywords:
self.sessions = options.sessions
self.keywords = options.keywords

self.envdir = self.envdir or options.envdir or ".nox"
self.reuse_existing_virtualenvs = _default_with_off_flag(
self.reuse_existing_virtualenvs,
options.reuse_existing_virtualenvs,
self.no_reuse_existing_virtualenvs,
)
self.stop_on_first_error = _default_with_off_flag(
self.stop_on_first_error,
options.stop_on_first_error,
self.no_stop_on_first_error,
)
self.error_on_missing_interpreters = _default_with_off_flag(
self.error_on_missing_interpreters,
options.error_on_missing_interpreters,
self.no_error_on_missing_interpreters,
)
self.report = self.report or options.report


def main():
parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -101,6 +146,11 @@ def main():
action="store_true",
help="Re-use existing virtualenvs instead of recreating them.",
)
secondary.add_argument(
"--no-reuse-existing-virtualenvs",
action="store_true",
help="Disables --reuse-existing-virtualenvs if it is enabled in the Noxfile.",
)

secondary.add_argument(
"-f",
Expand All @@ -110,7 +160,8 @@ def main():
)

secondary.add_argument(
"--envdir", default=".nox", help="Directory where nox will store virtualenvs."
"--envdir",
help="Directory where nox will store virtualenvs, this is .nox by default.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why the removal of .nox as default?
  2. Setting formatter_class=argparse.ArgumentDefaultsHelpFormatter will actually document your defaults so you don't need to write it in your prose

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving .nox as default makes it impossible for me to tell if it was specified at the command line or not. Leaving it as "None" allows me to distinguish that, and the default assignment is moved into merge_from_options.

)

secondary.add_argument(
Expand All @@ -119,15 +170,25 @@ def main():
action="store_true",
help="Stop after the first error.",
)
secondary.add_argument(
"--no-stop-on-first-error",
action="store_true",
help="Disables --stop-on-first-error if it is enabled in the Noxfile.",
)

secondary.add_argument(
"--error-on-missing-interpreters",
action="store_true",
help="Error instead of skip if an interpreter can not be located.",
)
secondary.add_argument(
"--no-error-on-missing-interpreters",
action="store_true",
help="Disables --error-on-missing-interpreters if it is enabled in the Noxfile.",
)

secondary.add_argument(
"--report", default=None, help="Output a report of all sessions."
"--report", help="Output a report of all sessions to the given filename."
)

secondary.add_argument(
Expand All @@ -141,7 +202,7 @@ def main():
"--forcecolor",
default=False,
action="store_true",
help=("Force color output, even if stdout is not an interactive " "terminal."),
help="Force color output, even if stdout is not an interactive terminal.",
)

args = parser.parse_args()
Expand All @@ -163,6 +224,7 @@ def main():
global_config=global_config,
workflow=(
tasks.load_nox_module,
tasks.merge_noxfile_options,
tasks.discover_manifest,
tasks.filter_manifest,
tasks.honor_list_request,
Expand Down
32 changes: 32 additions & 0 deletions nox/_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright 2018 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.


class options:
theacodes marked this conversation as resolved.
Show resolved Hide resolved
"""Options that are configurable in the Noxfile.

By setting properties on ``nox.options`` you can specify command line
arguments in your Noxfile. If an argument is specified in both the Noxfile
and on the command line, the command line arguments take precedence.

See :doc:`usage` for more details on these settings and their effect.
"""

sessions = None
keywords = None
envdir = None
reuse_existing_virtualenvs = False
stop_on_first_error = False
error_on_missing_interpreters = False
report = None
dhermes marked this conversation as resolved.
Show resolved Hide resolved
13 changes: 13 additions & 0 deletions nox/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import json
import os

from nox import _options
from nox import registry
from nox.logger import logger
from nox.manifest import Manifest
Expand Down Expand Up @@ -53,6 +54,18 @@ def load_nox_module(global_config):
return 2


def merge_noxfile_options(module, global_config):
"""Merges any modifications made to ``nox.options`` by the Noxfile module
into global_config.

Args:
module (module): The Noxfile module.
theacodes marked this conversation as resolved.
Show resolved Hide resolved
global_config (~nox.main.GlobalConfig): The global configuration.
"""
global_config.merge_from_options(_options.options)
return module


def discover_manifest(module, global_config):
"""Discover all session functions in the noxfile module.

Expand Down
Loading