Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added support for global settings file, `tango.yml`.
- Added 'include_package' (array of string) param to config spec.

## [v0.2.2](https://github.com/allenai/tango/releases/tag/v0.2.2) - 2021-10-19
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY : docs
docs :
rm -rf docs/build/
sphinx-autobuild -b html --watch tango/ --watch examples/ --open-browser docs/source/ docs/build/
sphinx-autobuild -b html --watch tango/ --watch examples/ docs/source/ docs/build/
16 changes: 16 additions & 0 deletions docs/source/api/commands.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Commands
========

.. automodule:: tango.__main__

Global settings
---------------

Some command-line options can set globally in a ``tango.yml`` or ``tango.yaml`` settings file.
Tango will check the current directory and ``~/.config/``, in that order.

The full spec of this config is defined by :class:`TangoGlobalSettings`.

.. autoclass:: tango.__main__.TangoGlobalSettings
:members:
:exclude-members: path,find_or_default
1 change: 1 addition & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ CHANGELOG
:hidden:
:caption: API Reference

api/commands
api/components/index
api/integrations/index
api/exceptions
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ log_format = %(asctime)s - %(levelname)s - %(name)s - %(message)s
log_level = DEBUG
markers =
filterwarnings =
doctest_optionflags = NORMALIZE_WHITESPACE
183 changes: 174 additions & 9 deletions tango/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,66 @@
"""
The Tango CLI is the recommended tool to run experiments with.
It also comes with several other useful commands.

You can see the the list of all available commands by running:

.. code-block::

$ tango --help

.. testcode::
:hide:

import subprocess
output = subprocess.run("tango --help".split(" "), capture_output=True)
output.check_returncode()
print(output.stdout.decode().replace("\\n\\n", "\\n").strip())

.. testoutput::

Usage: tango [OPTIONS] COMMAND [ARGS]...

Options:
--version Show the version and exit.
--config FILE Path to a global tango.yml settings file.
--log-level [debug|info|warning|error]
Set the global log level.
--no-logging Disable logging altogether.
--help Show this message and exit.

Commands:
info Get info about the current tango installation.
run Run a tango experiment.

To see all of the available arguments and options for a particular command, run

.. code-block::

$ tango [COMMAND] --help

For example,

.. code-block::

$ tango run --help

``tango run``
-------------

The ``run`` command is used to execute a tango experiment from an experiment configuration file.
See the `Configuration files </overview.html#configuration-files>`_ section in the overview
for a quick introduction to the format.

``tango info``
--------------

The ``info`` command just prints out some useful information about the current tango installation,
such as which integrations are available.

"""

from dataclasses import dataclass
from pathlib import Path
import logging
import os
from typing import Optional, Union, List, Sequence
Expand All @@ -6,6 +69,65 @@
from click_help_colors import HelpColorsCommand, HelpColorsGroup

from tango.version import VERSION
from tango.common.from_params import FromParams
from tango.common.params import Params
from tango.common.util import PathOrStr, install_sigterm_handler


@dataclass
class TangoGlobalSettings(FromParams):
"""
Defines global settings for tango.
"""

include_package: Optional[List[str]] = None
"""
An list of modules where custom registered steps or classes can be found.
"""

no_logging: bool = False
"""
If ``True``, logging is disabled.
"""

log_level: str = "info"
"""
The log level to use. Options are "debug", "info", "warning", and "error".
"""

_path: Optional[Path] = None

@classmethod
def find_or_default(cls, path: Optional[PathOrStr]) -> "TangoGlobalSettings":
"""
Initialize the config from files by checking the default locations
in order, or just return the default if none of the files can be found.
"""
if path is not None:
path = Path(path)
if not path.is_file():
raise FileNotFoundError(path)
return cls.from_file(path)
else:
for directory in (Path("."), Path.home() / ".config"):
for extension in ("yml", "yaml"):
path = directory / f"tango.{extension}"
if path.is_file():
return cls.from_file(path)
return cls()

@property
def path(self) -> Optional[Path]:
"""
The path to the file the config was read from.
"""
return self._path

@classmethod
def from_file(cls, path: Path) -> "TangoGlobalSettings":
params = Params.from_file(path)
params["_path"] = path
return cls.from_params(params)


@click.group(
Expand All @@ -15,19 +137,36 @@
context_settings={"max_content_width": 115},
)
@click.version_option(version=VERSION)
@click.option(
"--config",
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
help="Path to a global tango.yml settings file.",
)
@click.option(
"--log-level",
help="Set the global log level.",
type=click.Choice(["debug", "info", "warning", "error"], case_sensitive=False),
show_choices=True,
default="info",
)
@click.option(
"--no-logging",
is_flag=True,
help="Disable logging altogether.",
)
def main(log_level, no_logging):
@click.pass_context
def main(ctx, config: Optional[str], log_level: Optional[str], no_logging: bool):
config: TangoGlobalSettings = TangoGlobalSettings.find_or_default(config)

if log_level is None:
log_level = config.log_level
else:
config.log_level = log_level

if not no_logging:
no_logging = config.no_logging
else:
config.no_logging = no_logging

if not no_logging:
level = logging._nameToLevel[log_level.upper()]
logging.basicConfig(
Expand All @@ -38,11 +177,11 @@ def main(log_level, no_logging):
# important to say.
logging.getLogger("filelock").setLevel(max(level, logging.WARNING))

from tango.common.util import install_sigterm_handler

# We want to be able to catch SIGTERM signals in addition to SIGINT (keyboard interrupt).
install_sigterm_handler()

ctx.obj = config


@main.command(
cls=HelpColorsCommand,
Expand Down Expand Up @@ -81,7 +220,9 @@ def main(log_level, no_logging):
is_flag=True,
help="Outputs progress bar status on separate lines and slows refresh rate",
)
@click.pass_obj
def run(
config: TangoGlobalSettings,
experiment: str,
directory: Optional[Union[str, os.PathLike]] = None,
overrides: Optional[str] = None,
Expand All @@ -94,6 +235,7 @@ def run(
EXPERIMENT is the path to experiment's JSON/Jsonnet/YAML configuration file.
"""
_run(
config,
experiment,
directory=directory,
overrides=overrides,
Expand All @@ -108,13 +250,36 @@ def run(
help_headers_color="yellow",
context_settings={"max_content_width": 115},
)
def info():
@click.pass_obj
def info(config: TangoGlobalSettings):
"""
Get info about the current tango installation.
"""
import platform

from tango.common.util import find_integrations, import_module_and_submodules

click.echo(f"Tango version {VERSION} (python {platform.python_version()})\n")
click.echo("Integrations:")
click.echo(f"Tango version {VERSION} (python {platform.python_version()})")

# Show info about config.
if config.path is not None:
click.echo("\nConfig:")
click.secho(f" ✓ Loaded from {str(config.path)}", fg="green")
if config.include_package:
click.echo("\n Included packages:")
for package in config.include_package:
is_found = True
try:
import_module_and_submodules(package)
except (ModuleNotFoundError, ImportError):
is_found = False
if is_found:
click.secho(f" ✓ {package}", fg="green")
else:
click.secho(f" ✗ {package}", fg="red")

# Show info about integrations.
click.echo("\nIntegrations:")
for integration in find_integrations():
name = integration.split(".")[-1]
is_installed = True
Expand All @@ -129,6 +294,7 @@ def info():


def _run(
config: TangoGlobalSettings,
experiment: str,
directory: Optional[Union[str, os.PathLike]] = None,
overrides: Optional[str] = None,
Expand All @@ -138,8 +304,6 @@ def _run(
if file_friendly_logging:
os.environ["FILE_FRIENDLY_LOGGING"] = "true"

from pathlib import Path
from tango.common.params import Params
from tango.common.util import import_module_and_submodules
from tango.executor import Executor
from tango.step_cache import StepCache
Expand All @@ -154,6 +318,7 @@ def _run(
# custom Executor or StepCache.
include_package: List[str] = list(include_package or [])
include_package += params.pop("include_package", [])
include_package += config.include_package or []
for package_name in include_package:
import_module_and_submodules(package_name)

Expand Down
3 changes: 2 additions & 1 deletion tango/common/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from typing import Any, Dict, List, Union, Optional, TypeVar, Iterable, Set

from overrides import overrides
from cached_path import cached_path

# _jsonnet doesn't work on Windows, so we have to use fakes.
try:
Expand Down Expand Up @@ -509,6 +508,8 @@ def from_file(
ext_vars = {}

# redirect to cache, if necessary
from cached_path import cached_path

params_file: Path = Path(cached_path(params_file))

file_dict: Dict[str, Any]
Expand Down
3 changes: 2 additions & 1 deletion tango/common/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def run(
include_package: Optional[List[str]] = None,
) -> Path:
from .params import Params
from tango.__main__ import _run
from tango.__main__ import _run, TangoGlobalSettings

if isinstance(config, dict):
params = Params(config)
Expand All @@ -89,6 +89,7 @@ def run(

run_dir = self.TEST_DIR / "run"
_run(
TangoGlobalSettings(),
str(config),
directory=str(run_dir),
overrides=overrides,
Expand Down