diff --git a/.circleci/config.yml b/.circleci/config.yml index 7bf951d..e295b84 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,17 +1,36 @@ version: 2.1 parameters: - poetry_version: + poetry-version: type: string default: "1.1.4" + +executors: + focal: + docker: + - image: cimg/base:stable-20.04 + bionic: + docker: + - image: cimg/base:stable-18.04 + + workflows: lint: jobs: - lint + deb: + jobs: + - build-deb: + matrix: + parameters: + os: [bionic, focal] + - test-installation-deb: + requires: + - build-deb deploy: jobs: - - upload_to_pypi: + - upload-to-pypi: filters: # Ignore any commit on any branch by default branches: @@ -21,60 +40,114 @@ workflows: only: /^v.+\..+\..+/ tests: jobs: - - test_installation + - test-installation-source jobs: lint: docker: - - image: circleci/python:3.6 + - image: cimg/python:3.6 steps: - checkout - - run: "curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py - | python3 - --version << pipeline.parameters.poetry_version >>" - run: poetry install --no-root - run: poetry run isort --check . - run: poetry run black --check . - run: poetry run mypy -p "brainframe.cli" - upload_to_pypi: + + build-deb: + parameters: + os: + type: executor + executor: << parameters.os >> + steps: + - run: sudo apt-get update + - run: sudo apt-get install -y software-properties-common dpkg-dev devscripts equivs + - run: sudo add-apt-repository ppa:jyrki-pulliainen/dh-virtualenv + - run: sudo apt-get install -y dh-virtualenv + - install-poetry + - checkout + - run: cp -r package/debian . + - run: cp package/setup.py . + - run: cp package/system_package_defaults.yaml brainframe/cli/defaults.yaml + - run: sudo mk-build-deps --install debian/control + - run: poetry export --output requirements.txt + - run: dpkg-buildpackage --unsigned-source --unsigned-changes --build=binary + - run: mkdir dist + - run: | + export CODENAME=$(lsb_release --codename --short) + mv ../brainframe-cli*.deb dist/brainframe-cli-${CODENAME}.deb + - store_artifacts: + path: dist + - persist_to_workspace: + root: . + paths: + - dist/* + + test-installation-deb: + machine: + image: ubuntu-2004:202010-01 + steps: + - run: sudo apt-get update + - attach_workspace: + at: /tmp/workspace + - run: sudo apt-get install /tmp/workspace/dist/brainframe-cli-focal.deb + - run: sudo brainframe install --noninteractive + - run: sudo brainframe compose up -d + - assert-core-container-running + - assert-core-responds-to-http + - run: sudo brainframe compose down + + upload-to-pypi: docker: - - image: circleci/python:3.6 + - image: cimg/python:3.6 environment: POETRY_HTTP_BASIC_PYPI_USERNAME: __token__ - steps: - checkout - - run: "curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py - | python3 - --version << pipeline.parameters.poetry_version >>" - run: poetry build - run: POETRY_HTTP_BASIC_PYPI_PASSWORD=${PYPI_PASSWORD} poetry publish - test_installation: + + test-installation-source: machine: image: ubuntu-2004:202010-01 steps: - run: sudo apt-get update - run: sudo apt-get install python3 python3-dev curl git -y - checkout - - run: "curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py - | python3 - --version << pipeline.parameters.poetry_version >>" - - run: echo "export PATH=$PATH:$HOME/.poetry/bin" >> $BASH_ENV + - install-poetry - run: sudo $(which poetry) install - run: sudo $(which poetry) run brainframe install --noninteractive - run: sudo $(which poetry) run brainframe compose up -d + - assert-core-container-running + - assert-core-responds-to-http + - run: sudo $(which poetry) run brainframe compose down + +commands: + install-poetry: + steps: + - run: sudo apt-get update && sudo apt-get install -y python3-pip + - run: > + curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py + | python3 - --version << pipeline.parameters.poetry-version >> + # Add to the PATH + - run: echo 'export PATH=$HOME/.poetry/bin:$PATH' >> $BASH_ENV + assert-core-container-running: + steps: - run: - name: Check BrainFrame containers are running + name: Check BrainFrame core container is running command: | until docker container inspect -f {{.State.Running}} $(docker ps -q -f name="brainframe_core") >/dev/null 2>/dev/null; do sleep 0.1; done; echo "BrainFrame core container is running." no_output_timeout: 1m + assert-core-responds-to-http: + steps: - run: - name: Check BrainFrame core service is up + name: Check BrainFrame core responds to HTTP requests command: | until curl -f http://localhost/api/version >/dev/null 2>/dev/null; do sleep 0.1; done; echo "BrainFrame core service is up." no_output_timeout: 1m - - run: sudo $(which poetry) run brainframe compose down diff --git a/brainframe/cli/commands/backup.py b/brainframe/cli/commands/backup.py index 81d1864..4630af3 100644 --- a/brainframe/cli/commands/backup.py +++ b/brainframe/cli/commands/backup.py @@ -5,9 +5,9 @@ import i18n from brainframe.cli import ( + config, dependencies, docker_compose, - env_vars, os_utils, print_utils, ) @@ -19,8 +19,8 @@ @command("backup") def backup(): - install_path = env_vars.install_path.get() - data_path = env_vars.data_path.get() + install_path = config.install_path.value + data_path = config.data_path.value args = _parse_args(data_path) diff --git a/brainframe/cli/commands/compose.py b/brainframe/cli/commands/compose.py index bae171b..806cf88 100644 --- a/brainframe/cli/commands/compose.py +++ b/brainframe/cli/commands/compose.py @@ -1,13 +1,12 @@ import sys -import i18n -from brainframe.cli import docker_compose, env_vars, os_utils, print_utils +from brainframe.cli import config, docker_compose from .utils import command @command("compose") def compose(): - install_path = env_vars.install_path.get() + install_path = config.install_path.value docker_compose.assert_installed(install_path) docker_compose.run(install_path, sys.argv[2:]) diff --git a/brainframe/cli/commands/info.py b/brainframe/cli/commands/info.py index 8dc822f..7f1108e 100644 --- a/brainframe/cli/commands/info.py +++ b/brainframe/cli/commands/info.py @@ -1,7 +1,7 @@ from argparse import ArgumentParser import i18n -from brainframe.cli import docker_compose, env_vars, print_utils +from brainframe.cli import config, docker_compose, print_utils from .utils import command, subcommand_parse_args @@ -10,15 +10,15 @@ def info(): args = _parse_args() - docker_compose.assert_installed(env_vars.install_path.get()) + docker_compose.assert_installed(config.install_path.value) server_version = docker_compose.check_existing_version( - env_vars.install_path.get() + config.install_path.value ) fields = { - "install_path": env_vars.install_path.get(), - "data_path": env_vars.data_path.get(), + "install_path": config.install_path.value, + "data_path": config.data_path.value, "server_version": server_version, } diff --git a/brainframe/cli/commands/install.py b/brainframe/cli/commands/install.py index c130c03..15100ee 100644 --- a/brainframe/cli/commands/install.py +++ b/brainframe/cli/commands/install.py @@ -3,9 +3,9 @@ import i18n from brainframe.cli import ( + config, dependencies, docker_compose, - env_vars, os_utils, print_utils, ) @@ -32,9 +32,6 @@ def install(): # Check all dependencies dependencies.curl.ensure(args.noninteractive, args.install_curl) dependencies.docker.ensure(args.noninteractive, args.install_docker) - dependencies.docker_compose.ensure( - args.noninteractive, args.install_docker_compose - ) _, _, download_version = docker_compose.check_download_version() print_utils.translate("install.install-version", version=download_version) @@ -55,8 +52,8 @@ def install(): # Ask the user if they want to specify special paths for installation print_utils.translate( "install.default-paths", - default_install_path=env_vars.install_path.default, - default_data_path=env_vars.data_path.default, + default_install_path=config.install_path.default, + default_data_path=config.data_path.default, ) use_default_paths = print_utils.ask_yes_no( "install.ask-use-default-paths", @@ -66,11 +63,10 @@ def install(): if args.noninteractive: install_path = args.install_path elif use_default_paths: - install_path = env_vars.install_path.default + install_path = config.install_path.default else: install_path = print_utils.ask_path( - "install.ask-brainframe-install-path", - env_vars.install_path.default, + "install.ask-brainframe-install-path", config.install_path.default, ) install_path.mkdir(exist_ok=True, parents=True) @@ -78,10 +74,10 @@ def install(): if args.noninteractive: data_path = args.data_path elif use_default_paths: - data_path = env_vars.data_path.default + data_path = config.data_path.default else: data_path = print_utils.ask_path( - "install.ask-data-path", env_vars.data_path.default + "install.ask-data-path", config.data_path.default ) data_path.mkdir(exist_ok=True, parents=True) @@ -120,15 +116,15 @@ def install(): # Recommend to the user to add their custom paths to environment variables # so that future invocations of the program will know where to look. if ( - install_path != env_vars.install_path.default - or data_path != env_vars.data_path.default + install_path != config.install_path.default + or data_path != config.data_path.default ): print() print_utils.translate("install.set-custom-directory-env-vars") print( f"\n" - f'export {env_vars.install_path.name}="{install_path}"\n' - f'export {env_vars.data_path.name}="{data_path}"\n' + f'export {config.install_path.name}="{install_path}"\n' + f'export {config.data_path.name}="{data_path}"\n' ) @@ -146,13 +142,13 @@ def _parse_args(): parser.add_argument( "--install-path", type=Path, - default=env_vars.install_path.default, + default=config.install_path.default, help=i18n.t("install.install-path-help"), ) parser.add_argument( "--data-path", type=Path, - default=env_vars.data_path.default, + default=config.data_path.default, help=i18n.t("install.data-path-help"), ) parser.add_argument( @@ -160,11 +156,6 @@ def _parse_args(): action="store_true", help=i18n.t("install.install-docker-help"), ) - parser.add_argument( - "--install-docker-compose", - action="store_true", - help=i18n.t("install.install-docker-compose-help"), - ) parser.add_argument( "--install-curl", action="store_true", diff --git a/brainframe/cli/commands/update.py b/brainframe/cli/commands/update.py index f276c5a..9183130 100644 --- a/brainframe/cli/commands/update.py +++ b/brainframe/cli/commands/update.py @@ -1,7 +1,7 @@ from argparse import ArgumentParser import i18n -from brainframe.cli import docker_compose, env_vars, print_utils +from brainframe.cli import config, docker_compose, print_utils from packaging import version from .utils import command, subcommand_parse_args @@ -11,7 +11,7 @@ def update(): args = _parse_args() - install_path = env_vars.install_path.get() + install_path = config.install_path.value docker_compose.assert_installed(install_path) diff --git a/brainframe/cli/config.py b/brainframe/cli/config.py new file mode 100644 index 0000000..d02e7ad --- /dev/null +++ b/brainframe/cli/config.py @@ -0,0 +1,79 @@ +import os +from distutils.util import strtobool +from pathlib import Path +from typing import Callable, Dict, Generic, Optional, TypeVar, Union + +import yaml + +from . import print_utils + +_DEFAULTS_FILE_PATH = Path(__file__).parent / "defaults.yaml" + + +T = TypeVar("T") + + +class Option(Generic[T]): + """A configuration option. + + Option values are determined using the following precedence: + 1. From an environment variable with the name "BRAINFRAME_" followed by the option + name in all caps + 2. From the defaults file shipped with this distribution + 3. The fallback value, which is None + """ + + name: str + value: Optional[T] = None + default: Optional[T] = None + + def __init__(self, name: str): + self.name = name + + @property + def env_var_name(self): + return "BRAINFRAME_" + self.name.upper() + + def load( + self, converter: Callable[[str], T], defaults: Dict[str, str] + ) -> None: + default = defaults.get(self.name) + + value: Optional[str] = os.environ.get(self.env_var_name, default) + + self.value = None if value is None else converter(value) + self.default = None if default is None else converter(default) + + +install_path = Option[Path]("install_path") +data_path = Option[Path]("data_path") + +is_staging = Option[bool]("staging") +staging_username = Option[str]("staging_username") +staging_password = Option[str]("staging_password") + + +def load() -> None: + """Initializes configuration options""" + if not _DEFAULTS_FILE_PATH.is_file(): + print_utils.fail_translate( + "general.missing-defaults-file", + defaults_file_path=_DEFAULTS_FILE_PATH, + ) + + with _DEFAULTS_FILE_PATH.open("r") as defaults_file: + defaults = yaml.load(defaults_file, Loader=yaml.FullLoader) + + install_path.load(Path, defaults) + data_path.load(Path, defaults) + + is_staging.load(_bool_converter, defaults) + staging_username.load(str, defaults) + staging_password.load(str, defaults) + + +def _bool_converter(value: Union[str, bool]) -> bool: + if isinstance(value, bool): + return value + + return strtobool(value) diff --git a/brainframe/cli/defaults.yaml b/brainframe/cli/defaults.yaml new file mode 100644 index 0000000..8e181a9 --- /dev/null +++ b/brainframe/cli/defaults.yaml @@ -0,0 +1,4 @@ +# Defaults used for when the BrainFrame CLI is installed manually + +install_path: /usr/local/share/brainframe +data_path: /var/local/brainframe diff --git a/brainframe/cli/dependencies.py b/brainframe/cli/dependencies.py index f3ffefc..f40962d 100644 --- a/brainframe/cli/dependencies.py +++ b/brainframe/cli/dependencies.py @@ -63,23 +63,6 @@ def _install_docker(): docker = Dependency("docker", "install.ask-install-docker", _install_docker,) -docker_compose = Dependency( - "docker-compose", - "install.ask-install-docker-compose", - lambda: os_utils.run( - [ - "pip3", - "install", - # Avoids warning message about touching the non-root user's cache - "--no-cache-dir", - # Installs globally even if the user has docker-compose installed - # locally - "--ignore-installed", - "docker-compose", - ] - ), -) - rsync = Dependency( "rsync", "install.ask-install-rsync", diff --git a/brainframe/cli/docker_compose.py b/brainframe/cli/docker_compose.py index 6e70f92..e7fcc00 100644 --- a/brainframe/cli/docker_compose.py +++ b/brainframe/cli/docker_compose.py @@ -1,12 +1,13 @@ import os import subprocess +import sys from pathlib import Path from typing import List, TextIO, Tuple, cast import i18n import yaml -from . import env_vars, os_utils, print_utils +from . import config, os_utils, print_utils # The URL to the docker-compose.yml BRAINFRAME_DOCKER_COMPOSE_URL = "https://{subdomain}aotu.ai/releases/brainframe/{version}/docker-compose.yml" @@ -23,7 +24,7 @@ def assert_installed(install_path: Path) -> None: if not compose_path.is_file(): print_utils.fail_translate( "general.brainframe-must-be-installed", - install_env_var=env_vars.install_path.name, + install_env_var=config.install_path.name, ) @@ -32,7 +33,13 @@ def run(install_path: Path, commands: List[str]) -> None: compose_path = install_path / "docker-compose.yml" - full_command = ["docker-compose", "--file", str(compose_path)] + full_command = [ + sys.executable, + "-m", + "compose", + "--file", + str(compose_path), + ] # Provide the override file if it exists compose_override_path = install_path / "docker-compose.override.yml" @@ -73,16 +80,16 @@ def check_download_version( # Add the flags to authenticate with staging if the user wants to download # from there - if env_vars.is_staging.is_set(): + if config.is_staging.value: subdomain = "staging." - username = env_vars.staging_username.get() - password = env_vars.staging_password.get() + username = config.staging_username.value + password = config.staging_password.value if username is None or password is None: print_utils.fail_translate( "general.staging-missing-credentials", - username_env_var=env_vars.staging_username.name, - password_env_var=env_vars.staging_password.name, + username_env_var=config.staging_username.name, + password_env_var=config.staging_password.name, ) auth_flags = ["--user", f"{username}:{password}"] diff --git a/brainframe/cli/env_vars.py b/brainframe/cli/env_vars.py deleted file mode 100644 index dd8915e..0000000 --- a/brainframe/cli/env_vars.py +++ /dev/null @@ -1,55 +0,0 @@ -import os -from pathlib import Path -from typing import Callable, Generic, Optional, Type, TypeVar, Union - -T = TypeVar("T") -"""The type of a configuration value""" - - -class EnvironmentVariable(Generic[T]): - """Manages a configuration option using system environment variables""" - - def __init__( - self, - name, - default: T = None, - converter: Union[Type, Callable[[str], T]] = str, - ): - """ - :param name: The environment variable name - :param default: The default value if the variable is unset - :param converter: Converts the string value of the environment variable - to the desired type - """ - self.name = name - self.default = default - self.converter = converter - - def get(self) -> Optional[T]: - if self.name in os.environ: - value = os.environ[self.name] - if value is None: - return None - return self.converter(value) - - return self.default - - def is_set(self) -> bool: - return self.name in os.environ - - -install_path = EnvironmentVariable[Path]( - "BRAINFRAME_INSTALL_PATH", - default=Path("/usr/local/share/brainframe/"), - converter=Path, -) - -data_path = EnvironmentVariable[Path]( - "BRAINFRAME_DATA_PATH", - default=Path("/var/local/brainframe"), - converter=Path, -) - -is_staging = EnvironmentVariable[str]("BRAINFRAME_STAGING") -staging_username = EnvironmentVariable[str]("BRAINFRAME_STAGING_USERNAME") -staging_password = EnvironmentVariable[str]("BRAINFRAME_STAGING_PASSWORD") diff --git a/brainframe/cli/main.py b/brainframe/cli/main.py index bce0dfb..0743cf8 100644 --- a/brainframe/cli/main.py +++ b/brainframe/cli/main.py @@ -1,14 +1,22 @@ +#!/usr/bin/env python3 + import os import signal import sys from argparse import ArgumentParser import i18n -from brainframe.cli import commands, env_vars, os_utils, print_utils +from brainframe.cli import ( + commands, + config, + os_utils, + print_utils, + translations, +) def main(): - i18n.load_path.append(_TRANSLATIONS_PATH) + i18n.load_path.append(str(translations.PATH)) parser = ArgumentParser( description=i18n.t("portal.description"), usage=i18n.t("portal.usage") @@ -18,10 +26,13 @@ def main(): "command", default=None, nargs="?", help=i18n.t("portal.command-help") ) + config.load() + # This environment variable must be set as it is used by the # docker-compose.yml to find the data path to volume mount - if not env_vars.data_path.is_set(): - os.environ[env_vars.data_path.name] = str(env_vars.data_path.default) + os.environ.setdefault( + config.data_path.env_var_name, str(config.data_path.default), + ) args = parser.parse_args(sys.argv[1:2]) @@ -48,7 +59,5 @@ def on_sigint(_sig, _frame): parser.print_help() -_TRANSLATIONS_PATH = os.path.join(os.path.dirname(__file__), "translations") - if __name__ == "__main__": main() diff --git a/brainframe/cli/translations/__init__.py b/brainframe/cli/translations/__init__.py new file mode 100644 index 0000000..b509612 --- /dev/null +++ b/brainframe/cli/translations/__init__.py @@ -0,0 +1,3 @@ +from pathlib import Path + +PATH = Path(__file__).parent diff --git a/brainframe/cli/translations/general.en.yml b/brainframe/cli/translations/general.en.yml index 5ffe87a..97235f8 100644 --- a/brainframe/cli/translations/general.en.yml +++ b/brainframe/cli/translations/general.en.yml @@ -28,3 +28,6 @@ en: unexpected-group-for-file: "File \"%{path}\" is not owned by the \"%{group}\" group, but probably should be. Please add this file to the group or try again as root." + missing-defaults-file: "This distribution is missing a defaults file, + expected at \"%{defaults_file_path}\". Please try re-installing the latest + version of the BrainFrame CLI and trying again." diff --git a/brainframe/cli/translations/install.en.yml b/brainframe/cli/translations/install.en.yml index 826352c..d09f618 100644 --- a/brainframe/cli/translations/install.en.yml +++ b/brainframe/cli/translations/install.en.yml @@ -22,9 +22,6 @@ en: data-path-help: "The path where BrainFrame will write data to at runtime" install-docker-help: "If provided, Docker will be automatically installed if it is not present. This is only available for supported operating systems." - install-docker-compose-help: "If provided, Docker Compose will be - automatically installed if it is not present. This is only available for - supported operating systems." install-curl-help: "If provided, curl will be automatically installed if not present. This is only available for supported operating systems." add-to-group-help: "If provided, the current user will be added to the diff --git a/package/debian/brainframe-cli.links b/package/debian/brainframe-cli.links new file mode 100644 index 0000000..984d400 --- /dev/null +++ b/package/debian/brainframe-cli.links @@ -0,0 +1 @@ +opt/venvs/brainframe-cli/bin/main.py usr/bin/brainframe diff --git a/package/debian/changelog b/package/debian/changelog new file mode 100644 index 0000000..cc6fd55 --- /dev/null +++ b/package/debian/changelog @@ -0,0 +1,5 @@ +brainframe-cli (0.2.0-1) unstable; urgency=medium + + * Initial release. + + -- Aotu.ai Mon, 03 May 2021 18:55:19 -0700 diff --git a/package/debian/compat b/package/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/package/debian/compat @@ -0,0 +1 @@ +9 diff --git a/package/debian/control b/package/debian/control new file mode 100644 index 0000000..cb46005 --- /dev/null +++ b/package/debian/control @@ -0,0 +1,12 @@ +Source: brainframe-cli +Section: python +Priority: optional +Maintainer: Aotu.ai +Build-Depends: debhelper (>= 9), python3, dh-virtualenv (>= 1.2) +Standards-Version: 3.9.5 + +Package: brainframe-cli +Architecture: any +Pre-Depends: +Depends: python3-distutils, rsync, curl +Description: A CLI that makes installing and managing a BrainFrame server easy diff --git a/package/debian/rules b/package/debian/rules new file mode 100755 index 0000000..5310fc4 --- /dev/null +++ b/package/debian/rules @@ -0,0 +1,4 @@ +#!/usr/bin/make -f + +%: + dh $@ --with python-virtualenv --python python3 diff --git a/package/setup.py b/package/setup.py new file mode 100644 index 0000000..30ebbba --- /dev/null +++ b/package/setup.py @@ -0,0 +1,25 @@ +import subprocess + +import setuptools + +poetry_result = subprocess.run( + ["poetry", "version"], + check=True, + stdout=subprocess.PIPE, + encoding="utf-8", +) +poetry_output = poetry_result.stdout.strip() + +name, version = poetry_output.split() + +setuptools.setup( + name=name, + version=version, + packages=setuptools.find_namespace_packages(include="brainframe*",), + scripts=["brainframe/cli/main.py"], + package_data={ + "brainframe.cli.translations": ["*.yml"], + "brainframe.cli": ["defaults.yaml"], + }, + include_package_data=True, +) diff --git a/package/system_package_defaults.yaml b/package/system_package_defaults.yaml new file mode 100644 index 0000000..dce4a9d --- /dev/null +++ b/package/system_package_defaults.yaml @@ -0,0 +1,4 @@ +# Defaults used for when the BrainFrame CLI is installed as a system package + +install_path: /usr/share/brainframe +data_path: /var/lib/brainframe diff --git a/poetry.lock b/poetry.lock index 6b350c4..abbf466 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,32 +1,48 @@ [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.4" [[package]] -category = "dev" -description = "Classes Without Boilerplate" name = "attrs" +version = "20.2.0" +description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.2.0" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -category = "dev" -description = "The uncompromising code formatter." -name = "black" +name = "bcrypt" +version = "3.2.0" +description = "Modern password hashing for your software and your servers" +category = "main" optional = false python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.1" +six = ">=1.4.1" + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "black" version = "19.10b0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" [package.dependencies] appdirs = "*" @@ -41,41 +57,208 @@ typed-ast = ">=1.4.0" d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "dev" -description = "Composable command line interface toolkit" -name = "click" +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "click" version = "7.1.2" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." category = "main" -description = "Distro - an OS platform information API" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "cryptography" +version = "3.4.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + +[[package]] name = "distro" +version = "1.5.0" +description = "Distro - an OS platform information API" +category = "main" optional = false python-versions = "*" -version = "1.5.0" [[package]] -category = "dev" -description = "A Python utility / library to sort Python imports." +name = "docker" +version = "5.0.0" +description = "A Python library for the Docker Engine API." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +paramiko = {version = ">=2.4.2", optional = true, markers = "extra == \"ssh\""} +pywin32 = {version = "227", markers = "sys_platform == \"win32\""} +requests = ">=2.14.2,<2.18.0 || >2.18.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.2)"] +tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=3.4.7)", "idna (>=2.0.0)"] + +[[package]] +name = "docker-compose" +version = "1.29.1" +description = "Multi-container orchestration for Docker" +category = "main" +optional = false +python-versions = ">=3.4" + +[package.dependencies] +cached-property = {version = ">=1.2.0,<2", markers = "python_version < \"3.8\""} +colorama = {version = ">=0.4,<1", markers = "sys_platform == \"win32\""} +distro = ">=1.5.0,<2" +docker = {version = ">=5", extras = ["ssh"]} +dockerpty = ">=0.4.1,<1" +docopt = ">=0.6.1,<1" +jsonschema = ">=2.5.1,<4" +python-dotenv = ">=0.13.0,<1" +PyYAML = ">=3.10,<6" +requests = ">=2.20.0,<3" +texttable = ">=0.9.0,<2" +websocket-client = ">=0.32.0,<1" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2)"] +tests = ["ddt (>=1.2.2,<2)", "pytest (<6)"] + +[[package]] +name = "dockerpty" +version = "0.4.1" +description = "Python library to use the pseudo-tty of a docker container" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.3.0" + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "importlib-metadata" +version = "4.0.1" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] name = "isort" +version = "5.6.4" +description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.6,<4.0" -version = "5.6.4" [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "jsonschema" +version = "3.2.0" +description = "An implementation of JSON Schema validation for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +attrs = ">=17.4.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +pyrsistent = ">=0.14.0" +six = ">=1.11.0" + +[package.extras] +format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] +format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"] [[package]] -category = "dev" -description = "Optional static typing for Python" name = "mypy" +version = "0.790" +description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "0.790" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -86,104 +269,236 @@ typing-extensions = ">=3.7.4" dmypy = ["psutil (>=4.0)"] [[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" optional = false python-versions = "*" -version = "0.4.3" [[package]] -category = "main" -description = "Core utilities for Python packages" name = "packaging" +version = "20.4" +description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" [package.dependencies] pyparsing = ">=2.0.2" six = "*" [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." +name = "paramiko" +version = "2.7.2" +description = "SSH2 protocol library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +bcrypt = ">=3.1.3" +cryptography = ">=2.5" +pynacl = ">=1.0.1" + +[package.extras] +all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] +gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=1.3)"] + +[[package]] name = "pathspec" +version = "0.8.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.0" [[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" category = "main" -description = "Python parsing module" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pynacl" +version = "1.4.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +cffi = ">=1.4.1" +six = "*" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] + +[[package]] name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" [[package]] +name = "pyrsistent" +version = "0.17.3" +description = "Persistent/Functional/Immutable data structures" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "python-dotenv" +version = "0.17.1" +description = "Read key-value pairs from a .env file and set them as environment variables" category = "main" -description = "Translation library for Python" -name = "python-i18n" optional = false python-versions = "*" + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-i18n" version = "0.3.9" +description = "Translation library for Python" +category = "main" +optional = false +python-versions = "*" [package.extras] yaml = ["pyyaml (>=3.10)"] [[package]] +name = "pywin32" +version = "227" +description = "Python for Window Extensions" category = "main" -description = "YAML parser and emitter for Python" +optional = false +python-versions = "*" + +[[package]] name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" [[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." name = "regex" +version = "2020.10.23" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2020.10.23" [[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." category = "main" -description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" [[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" +name = "texttable" +version = "1.6.3" +description = "module for creating simple ASCII tables" +category = "main" optional = false python-versions = "*" -version = "0.10.1" [[package]] +name = "toml" +version = "0.10.1" +description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" optional = false python-versions = "*" -version = "1.4.1" [[package]] +name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" -description = "Backported and Experimental Type Hints for Python 3.5+" -name = "typing-extensions" optional = false python-versions = "*" + +[[package]] +name = "typing-extensions" version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "urllib3" +version = "1.26.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotlipy (>=0.6.0)"] + +[[package]] +name = "websocket-client" +version = "0.58.0" +description = "WebSocket client for Python with low level API options" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +six = "*" + +[[package]] +name = "zipp" +version = "3.4.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -content-hash = "4357e10aa0b8b5f04a8cdca15c86428849718ff214064c97722990652cdd1471" lock-version = "1.1" python-versions = ">=3.6,<4.0" +content-hash = "521e77aa15f42ec0bba3cce7d1b8238eaad99e5e27ce72f71f3b755f6e7843fb" [metadata.files] appdirs = [ @@ -194,22 +509,125 @@ attrs = [ {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, ] +bcrypt = [ + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, + {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, + {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, + {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, +] black = [ {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] +cached-property = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +cffi = [ + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, +] +cryptography = [ + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, +] distro = [ {file = "distro-1.5.0-py2.py3-none-any.whl", hash = "sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799"}, {file = "distro-1.5.0.tar.gz", hash = "sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92"}, ] +docker = [ + {file = "docker-5.0.0-py2.py3-none-any.whl", hash = "sha256:fc961d622160e8021c10d1bcabc388c57d55fb1f917175afbe24af442e6879bd"}, + {file = "docker-5.0.0.tar.gz", hash = "sha256:3e8bc47534e0ca9331d72c32f2881bb13b93ded0bcdeab3c833fb7cf61c0a9a5"}, +] +docker-compose = [ + {file = "docker-compose-1.29.1.tar.gz", hash = "sha256:d2064934f5084db8a0c4805e226447bf1fd0c928419be95afb6bd1866838c1f1"}, + {file = "docker_compose-1.29.1-py2.py3-none-any.whl", hash = "sha256:6510302727fe20faff5177f493b0c9897c73132539eaf55709a195c914c1a012"}, +] +dockerpty = [ + {file = "dockerpty-0.4.1.tar.gz", hash = "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce"}, +] +docopt = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.0.1-py3-none-any.whl", hash = "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"}, + {file = "importlib_metadata-4.0.1.tar.gz", hash = "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581"}, +] isort = [ {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, ] +jsonschema = [ + {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, + {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, +] mypy = [ {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"}, @@ -234,18 +652,67 @@ packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, ] +paramiko = [ + {file = "paramiko-2.7.2-py2.py3-none-any.whl", hash = "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898"}, + {file = "paramiko-2.7.2.tar.gz", hash = "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035"}, +] pathspec = [ {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pynacl = [ + {file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574"}, + {file = "PyNaCl-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"}, + {file = "PyNaCl-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7"}, + {file = "PyNaCl-1.4.0-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122"}, + {file = "PyNaCl-1.4.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d"}, + {file = "PyNaCl-1.4.0-cp35-abi3-win32.whl", hash = "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634"}, + {file = "PyNaCl-1.4.0-cp35-abi3-win_amd64.whl", hash = "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6"}, + {file = "PyNaCl-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4"}, + {file = "PyNaCl-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25"}, + {file = "PyNaCl-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4"}, + {file = "PyNaCl-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6"}, + {file = "PyNaCl-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f"}, + {file = "PyNaCl-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f"}, + {file = "PyNaCl-1.4.0-cp38-cp38-win32.whl", hash = "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96"}, + {file = "PyNaCl-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420"}, + {file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"}, +] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] +pyrsistent = [ + {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, +] +python-dotenv = [ + {file = "python-dotenv-0.17.1.tar.gz", hash = "sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f"}, + {file = "python_dotenv-0.17.1-py2.py3-none-any.whl", hash = "sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544"}, +] python-i18n = [ {file = "python-i18n-0.3.9.tar.gz", hash = "sha256:df97f3d2364bf3a7ebfbd6cbefe8e45483468e52a9e30b909c6078f5f471e4e8"}, {file = "python_i18n-0.3.9-py3-none-any.whl", hash = "sha256:bda5b8d889ebd51973e22e53746417bd32783c9bd6780fd27cadbb733915651d"}, ] +pywin32 = [ + {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, + {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, + {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, + {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, + {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, + {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, + {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, + {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, + {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, + {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, + {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, + {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, +] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, @@ -288,10 +755,18 @@ regex = [ {file = "regex-2020.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:1a511470db3aa97432ac8c1bf014fcc6c9fbfd0f4b1313024d342549cf86bcd6"}, {file = "regex-2020.10.23.tar.gz", hash = "sha256:2278453c6a76280b38855a263198961938108ea2333ee145c5168c36b8e2b376"}, ] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] +texttable = [ + {file = "texttable-1.6.3-py2.py3-none-any.whl", hash = "sha256:f802f2ef8459058736264210f716c757cbf85007a30886d8541aa8c3404f1dda"}, + {file = "texttable-1.6.3.tar.gz", hash = "sha256:ce0faf21aa77d806bbff22b107cc22cce68dc9438f97a2df32c93e9afa4ce436"}, +] toml = [ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, @@ -327,3 +802,15 @@ typing-extensions = [ {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] +urllib3 = [ + {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, + {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, +] +websocket-client = [ + {file = "websocket_client-0.58.0-py2.py3-none-any.whl", hash = "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663"}, + {file = "websocket_client-0.58.0.tar.gz", hash = "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"}, +] +zipp = [ + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, +] diff --git a/pyproject.toml b/pyproject.toml index 287274f..4e1c851 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ homepage = "https://github.com/aotuai/brainframe_cli" packages = [ { include = "brainframe/cli/**/*.py" }, ] -include = ["brainframe/cli/translations/*"] +include = ["brainframe/cli/translations/*", "brainframe/cli/defaults.yaml"] [tool.poetry.dependencies] python = ">=3.6,<4.0" @@ -21,6 +21,7 @@ python-i18n = "^0.3" pyyaml = "^5.3" distro = "^1.5" packaging = "^20.4" +docker-compose = "^1.29.1" [tool.poetry.dev-dependencies] black = "^19.10b0"