Esempio n. 1
0
def test_argument():
    arg = argument("foo", "Foo")

    assert "Foo" == arg.description
    assert arg.is_required()
    assert not arg.is_optional()
    assert not arg.is_multi_valued()
    assert arg.default is None

    arg = argument("foo", "Foo", optional=True, default="bar")

    assert not arg.is_required()
    assert arg.is_optional()
    assert not arg.is_multi_valued()
    assert "bar" == arg.default

    arg = argument("foo", "Foo", multiple=True)

    assert arg.is_required()
    assert not arg.is_optional()
    assert arg.is_multi_valued()
    assert [] == arg.default

    arg = argument("foo", "Foo", optional=True, multiple=True, default=["bar"])

    assert not arg.is_required()
    assert arg.is_optional()
    assert arg.is_multi_valued()
    assert ["bar"] == arg.default
Esempio n. 2
0
class SetCommand(Command):
    name = "set"

    description = "Set value of the key"
    help = "\n".join([
        "",
        "# Set specific value in the config",
        "set settings.json key value",
        "set settings.json key=value",
        "",
        "# Set environment variable in current shell",
        "set key value",
        "set key=value",
    ])

    arguments = [
        argument(
            name="key",
            description=
            "Config key ('my.key') or key-value pair ('my.key=231')",
        ),
        argument(
            name="value",
            description=
            "[optional] Value of the key when <key> argument is just a key name",
            optional=True,
        ),
    ]

    options = [file_type, *scopes]

    def handle(self):
        print("Wow, you've set it")
Esempio n. 3
0
class CloneCommand(Command):
    name = 'clone'
    description = 'clone repository from github'
    arguments = [
        argument('repository', 'name of repository that you want to clone'),
        argument('target', 'target directory name', optional=True),
    ]
    options = [option('url', 'u', 'echo only url, not clone')]

    def handle(self):
        clone_url = url_parsing(self.argument('repository'))
        if self.option('url'):
            self.line(clone_url)
            return 1
        clone(clone_url, self.argument('target'))
Esempio n. 4
0
class SearchCommand(Command):

    name = "search"
    description = "Searches for packages on remote repositories."

    arguments = [
        argument("tokens", "The tokens to search for.", multiple=True)
    ]
    options = [option("only-name", "N", "Search only by name.")]

    def handle(self):
        from poetry.repositories.pypi_repository import PyPiRepository

        flags = PyPiRepository.SEARCH_FULLTEXT
        if self.option("only-name"):
            flags = PyPiRepository.SEARCH_NAME

        results = PyPiRepository().search(self.argument("tokens"), flags)

        for result in results:
            self.line("")
            name = "<info>{}</>".format(result.name)

            name += " (<comment>{}</>)".format(result.version)

            self.line(name)

            if result.description:
                self.line(" {}".format(result.description))
Esempio n. 5
0
File: time.py Progetto: jrog612/kal
class KSTCommand(TimeCommand):
    name = 'kst'
    description = 'convert KST time to target timezone'
    arguments = [
        argument('time', 'target time to change'),
        argument('tz',
                 'target timezone to change',
                 default='UTC',
                 optional=True)
    ]

    def handle(self):
        time = self.argument('time')
        tz = self.argument('tz')
        parsed = self.time_parse(time, 'Asia/Seoul', tz)
        self.line("{} in {} is {}".format(time, tz, parsed.strftime('%H:%M')))
Esempio n. 6
0
class DiscoverCommand(Command):
    name = "discover"
    description = "Test dsicovery functionality"

    arguments = [
        argument("parameter", description="What to discover", optional=False)
    ]
    options = [
        option("input-glob", flag=False),
        option("output-glob", flag=False)
    ]

    def handle(self) -> None:
        parameter = self.argument("parameter")

        if parameter == "tests":
            input_glob = self.option("input-glob")
            output_glob = self.option("output-glob")

            if not input_glob:
                self.line("<error>input-glob option must be specified</error>")
                return

            if not output_glob:
                self.line(
                    "<error>output-glob option must be specified</error>")
                return

            tests = Discovery.find_test_cases(input_glob, output_glob)
            self.line(f"len of tests: {len(tests)}")

            for t in tests:
                print(f"key: {t}; input: {tests[t][0]}; output: {tests[t][1]}")
Esempio n. 7
0
class RunCommand(EnvCommand):

    name = "run"
    description = "Runs a command in the appropriate environment."

    arguments = [
        argument("args",
                 "The command and arguments/options to run.",
                 multiple=True)
    ]

    def __init__(self):  # type: () -> None
        from poetry.console.args.run_args_parser import RunArgsParser

        super(RunCommand, self).__init__()

        self.config.set_args_parser(RunArgsParser())

    def handle(self):
        args = self.argument("args")
        script = args[0]
        scripts = self.poetry.local_config.get("scripts")

        if scripts and script in scripts:
            return self.run_script(scripts[script], args)

        return self.env.execute(*args)

    def run_script(self, script, args):
        if isinstance(script, dict):
            script = script["callable"]

        module, callable_ = script.split(":")

        src_in_sys_path = "sys.path.append('src'); " if self._module.is_in_src(
        ) else ""

        cmd = ["python", "-c"]

        cmd += [
            '"import sys; '
            "from importlib import import_module; "
            "sys.argv = {!r}; {}"
            "import_module('{}').{}()\"".format(args, src_in_sys_path, module,
                                                callable_)
        ]

        return self.env.run(*cmd, shell=True, call=True)

    @property
    def _module(self):
        from ...masonry.utils.module import Module

        poetry = self.poetry
        package = poetry.package
        path = poetry.file.parent
        module = Module(package.name, path.as_posix(), package.packages)

        return module
Esempio n. 8
0
class ConfigureCommand(Command):
    name = "configure"
    description = "Configure the environment"

    arguments = [
        argument("command",
                 description="Which command to execute: show, get, or set",
                 optional=False),
        argument("parameter",
                 description="Which parameter to set or get",
                 optional=True)
    ]
    options = [
        option(
            "user",
            description=
            "Save configuration file in user's home directory when setting parameter",
            flag=True),
        option(
            "system",
            description=
            "Save configuration file in system directory when setting parameters",
            flag=True)
    ]

    def handle(self) -> None:
        command = self.argument("command")
        parameter = self.argument("parameter")

        if command == "show":
            config = Configuration()
            config.build()
            table = config.as_table()
            self.render_table(*table)

        elif command == "get":
            if not parameter:
                self.line("<error>Parameter name must be specified</error>")

        elif command == "set":
            if not parameter:
                self.line("<error>Parameter name must be specified</error>")

        else:
            self.line(f"<error>Wrong command: {command}</error>")
Esempio n. 9
0
class StopCommand(Command):
    name = 'stop'
    description = 'stop docker container'
    arguments = [
        argument('name', 'name of container'),
    ]

    def handle(self):
        name = self.argument('name')
        self.shell_call('docker stop kal-{}'.format(name))
Esempio n. 10
0
class RunCommand(Command):
    name = 'run'
    description = 'Run docker container for local development'
    arguments = [
        argument('name', 'name of container'),
    ]

    def handle(self):
        name = self.argument('name')
        command = Config.docker(name)
        self.shell_call(command)
Esempio n. 11
0
class OcelCommand(Command):
    name = 'ocel'
    description = 'ocel command'
    arguments = [
        argument('template', 'name of template', optional=True),
        argument('target', 'target path', optional=True)
    ]
    options = [
        option('list', 'l', 'list of templates'),
        option(
            'update',
            'u',
            'update ocel from github',
        )
    ]

    def handle(self):
        ls = self.option('list')
        update = self.option('update')
        template = self.argument('template')
        target = self.argument('target')
        ocel = Ocel()

        if template and (ls or update):
            raise CommandError('Invalid format of command')

        if template:
            ocel.run(template, target)
            self.line('Finished.')
        elif ls:
            template_list = ocel.template_list(True)
            table = self.table(['Name', 'Desc'])
            for name, meta in template_list.items():
                table.add_row([name, meta.get('desc', '')])
            table.render(self.io)
        elif update:
            ocel.update()
        else:
            raise CommandError('Invalid format of command')
Esempio n. 12
0
class TrainSentencePieceModelCommand(BaseCommand):
    name = "train-spm"
    description = "Train Sentence Piece Model for BPE encoding."
    arguments = [argument("config", description="Config to use for SPM training.")]
    options = [
        option(
            "serialization-dir",
            "s",
            description="Directory to save trained SPM Model.",
            flag=False,
            value_required=False,
        ),
        option(
            "extra-vars",
            None,
            description=(
                "Extra variables to inject in JsonNet config in such format: "
                "{key_name1}={new_value1},{key_name2}={new_value2},..."
            ),
            flag=False,
            value_required=False,
        ),
    ]

    def handle(self) -> None:
        extra_vars = self.parse_extra_vars()
        config = Params.from_file(self.argument("config"), ext_vars=extra_vars)
        # Add serialization directory to config and create it
        serialization_dir = Path(self.option("serialization-dir"))
        self.prepare_directory(serialization_dir)
        # Log config to console and save
        config["model_prefix"] = str(serialization_dir / config["model_prefix"])
        logger.info(
            "Config: {}".format(json.dumps(config.as_flat_dict(), indent=2, ensure_ascii=False))
        )
        with (serialization_dir / "config.json").open("w", encoding="utf-8") as file:
            json.dump(config.as_dict(quiet=True), file, indent=2, ensure_ascii=False)
        # Train SPM Model
        spm.SentencePieceTrainer.train(**config)
        self.line(
            f"Finished SentencePiece model training and saved at path: `{serialization_dir}`",
            style="info",
        )

    def parse_extra_vars(self) -> Dict[str, str]:
        extra_vars = self.option("extra-vars")
        regex = r"([a-z0-9\_\-\.\+\\\/]+)=([a-z0-9\_\-\.\+\\\/]+)"
        return (
            {param: value for param, value in re.findall(regex, extra_vars, flags=re.I)}
            if extra_vars is not None else None
        )
Esempio n. 13
0
    def test_argument(self):
        validator = Integer()
        arg = argument(
            'foo', 'The foo argument.',
            required=False, is_list=True,
            default=['default'], validator=validator
        )

        self.assertIsInstance(arg, InputArgument)
        self.assertEqual('foo', arg.get_name())
        self.assertEqual('The foo argument.', arg.get_description())
        self.assertEqual(['default'], arg.get_default())
        self.assertTrue(arg.is_list())
        self.assertFalse(arg.is_required())
        self.assertEqual(validator, arg.get_validator())
Esempio n. 14
0
class RunCommand(EnvCommand):

    name = "run"
    description = "Runs a command in the appropriate environment."

    arguments = [
        argument("args",
                 "The command and arguments/options to run.",
                 multiple=True)
    ]

    def __init__(self):  # type: () -> None
        from poetry.console.args.run_args_parser import RunArgsParser

        super(RunCommand, self).__init__()

        self.config.set_args_parser(RunArgsParser())

    def handle(self):  # type: () -> Any
        args = self.argument("args")
        script = args[0]
        scripts = self.poetry.local_config.get("scripts")

        if scripts and script in scripts:
            return self.run_script(scripts[script], args)

        return self.env.execute(*args)

    def run_script(self, script, args):  # type: (Union[str, dict], str) -> Any
        if isinstance(script, dict):
            script = script["callable"]

        module, callable_ = script.split(":")

        src_in_sys_path = "sys.path.append('src'); " if self._module.is_in_src(
        ) else ""

        cmd = ["python", "-c"]

        cmd += [
            "import sys; "
            "from importlib import import_module; "
            "sys.argv = {!r}; {}"
            "import_module('{}').{}()".format(args, src_in_sys_path, module,
                                              callable_)
        ]

        return self.env.execute(*cmd)
Esempio n. 15
0
class EnvRemoveCommand(Command):

    name = "remove"
    description = "Removes a specific virtualenv associated with the project."

    arguments = [
        argument("python", "The python executable to remove the virtualenv for.")
    ]

    def handle(self):  # type: () -> None
        from poetry.utils.env import EnvManager

        manager = EnvManager(self.poetry)
        venv = manager.remove(self.argument("python"))

        self.line("Deleted virtualenv: <comment>{}</comment>".format(venv.path))
Esempio n. 16
0
class GetCommand(Command):
    name = "get"
    description = "Get value from the configuration"

    arguments = [
        argument(
            name="key",
            description="Key to retrieve",
            optional=False,
        )
    ]

    options = [file_type, *scopes]

    def handle(self):
        print("Here comes value")
Esempio n. 17
0
File: run.py Progetto: vduseev/fate
class RunCommand(Command):
    name = "run"
    description = "Run fate"

    arguments = [
        argument("path",
                 "The directory in which to run fate or path to executable",
                 optional=True)
    ]
    options = [
        option("output-type",
               description=
               "Instruct executable to print output to file or to stdout",
               value_required=True),
        option("execution-environment",
               description="Which environment to use to run the solution",
               value_required=True),
        option("docker",
               description="Use docker to run the solution",
               flag=True),
        option("debug", description="Run solution in debug mode", flag=True),
        option("input-file",
               description="Path input file of the test case",
               value_required=True),
        option("output-file",
               description="Path to output file of the test case",
               value_required=True),
        option("test-case-name",
               description="Name of the test case to execute",
               value_required=True),
        option(
            "concurrent",
            description=
            "Run tests concurrently (optionally specify number of concurent executions)",
            value_required=False),
        option("memory-limit",
               description=
               "Impose a memory limit (MB) when running in docker container",
               value_required=True),
        option("time-limit",
               description="Impose a time limit (ms) for each execution",
               value_required=True)
    ]

    def handle(self) -> None:
        pass
Esempio n. 18
0
class UpdateCommand(EnvCommand):

    name = "update"
    description = (
        "Update dependencies as according to the <comment>pyproject.toml</> file."
    )

    arguments = [
        argument("packages",
                 "The packages to update",
                 optional=True,
                 multiple=True)
    ]
    options = [
        option("no-dev", None, "Do not update dev dependencies."),
        option(
            "dry-run",
            None,
            "Output the operations but do not execute anything "
            "(implicitly enables --verbose).",
        ),
        option("lock", None,
               "Do not perform operations (only update the lockfile)."),
    ]

    loggers = ["poetry.repositories.pypi_repository"]

    def handle(self):
        from poetry.installation import Installer

        packages = self.argument("packages")

        installer = Installer(self.io, self.env, self.poetry.package,
                              self.poetry.locker, self.poetry.pool)

        if packages:
            installer.whitelist({name: "*" for name in packages})

        installer.dev_mode(not self.option("no-dev"))
        installer.dry_run(self.option("dry-run"))
        installer.execute_operations(not self.option("lock"))

        # Force update
        installer.update(True)

        return installer.run()
Esempio n. 19
0
class StartAppCommand(Command):

    name = "startapp"
    description = "Create a new flubber app"

    arguments = [argument("name", "The app name.")]

    options = [option("path", None, "The path to create the app at.", flag=False)]

    def handle(self) -> None:
        from flubber.console.commands.utils import get_flubber_path

        if self.option("path"):
            path = Path(self.option("path")) / Path(self.argument("name"))
        else:
            path = Path.cwd() / Path(self.argument("name"))

        name = self.argument("name")

        if path.exists():
            if list(path.glob("*")):
                # Directory is not empty. Aborting.
                raise RuntimeError(
                    "Destination <fg=yellow>{}</> "
                    "exists and is not empty".format(path)
                )

        self.copy_project_folder(src=get_flubber_path(), dst=path)

        self.line(
            "Created flubber project <info>{}</> in <fg=blue>{}</>".format(
                name, path.relative_to(Path.cwd())
            )
        )

    def copy_project_folder(self, src: str, dst: str) -> None:
        import shutil

        self.line("<info>Creating project ...</>")

        template_project_path = Path(src) / Path("conf/app_template")

        dst_path = Path(dst)
        shutil.copytree(template_project_path, dst_path)
        return
Esempio n. 20
0
class UpdateCommand(InstallerCommand):

    name = "update"
    description = (
        "Update the dependencies as according to the <comment>pyproject.toml</> file."
    )

    arguments = [
        argument("packages",
                 "The packages to update",
                 optional=True,
                 multiple=True)
    ]
    options = [
        option("no-dev", None, "Do not update the development dependencies."),
        option(
            "dry-run",
            None,
            "Output the operations but do not execute anything "
            "(implicitly enables --verbose).",
        ),
        option("lock", None,
               "Do not perform operations (only update the lockfile)."),
    ]

    loggers = ["poetry.repositories.pypi_repository"]

    def handle(self):  # type: () -> int
        packages = self.argument("packages")

        self._installer.use_executor(
            self.poetry.config.get("experimental.new-installer", False))

        if packages:
            self._installer.whitelist({name: "*" for name in packages})

        self._installer.dev_mode(not self.option("no-dev"))
        self._installer.dry_run(self.option("dry-run"))
        self._installer.execute_operations(not self.option("lock"))

        # Force update
        self._installer.update(True)

        return self._installer.run()
Esempio n. 21
0
class EnvUseCommand(Command):

    name = "use"
    description = "Activates or creates a new virtualenv for the current project."

    arguments = [argument("python", "The python executable to use.")]

    def handle(self):  # type: () -> None
        from poetry.utils.env import EnvManager

        manager = EnvManager(self.poetry)

        if self.argument("python") == "system":
            manager.deactivate(self._io)

            return

        env = manager.activate(self.argument("python"), self._io)

        self.line("Using virtualenv: <comment>{}</>".format(env.path))
Esempio n. 22
0
class SearchCommand(Command):

    name = "search"
    description = "Searches for packages on remote repositories."

    arguments = [argument("tokens", "The tokens to search for.", multiple=True)]

    def handle(self):
        from poetry.repositories.pypi_repository import PyPiRepository

        results = PyPiRepository().search(self.argument("tokens"))

        for result in results:
            self.line("")
            name = "<info>{}</>".format(result.name)

            name += " (<comment>{}</>)".format(result.version)

            self.line(name)

            if result.description:
                self.line(" {}".format(result.description))
Esempio n. 23
0
class MakeShardsCommand(BaseCommand):
    name = "make-shards"
    description = "Construct shards for Distributed Training from one txt file."
    arguments = [argument("path", description="Path to txt file.")]
    options = [
        option(
            "shards",
            "s",
            description="Number of shrads to split txt file.",
            flag=False,
            value_required=False,
        ),
        option(
            "seed",
            None,
            default=13,
            description="Random seed for to randomly assign sample to a shard.",
            flag=False,
            value_required=False,
        ),
    ]

    def handle(self) -> None:
        rng = random.Random(int(self.option("seed")))
        shards = int(self.option("shards"))
        file_path = Path(self.argument("path"))
        directory = file_path.parent / file_path.stem
        self.prepare_directory(directory)
        with file_path.open("r", encoding="utf-8") as file:
            for line in tqdm(map(lambda x: x.strip(), file),
                             desc="Sharding dataset"):
                choice = rng.randint(0, shards - 1)
                self.write_to_shard(line, directory / f"shard-{choice}.txt")

    @staticmethod
    def write_to_shard(line: str, shard_path: Path) -> None:
        with shard_path.open("a", encoding="utf-8") as file:
            file.write(line + "\n")
Esempio n. 24
0
class DebugResolveCommand(InitCommand):

    name = "resolve"
    description = "Debugs dependency resolution."

    arguments = [
        argument("package", "The packages to resolve.", optional=True, multiple=True)
    ]
    options = [
        option(
            "extras",
            "E",
            "Extras to activate for the dependency.",
            flag=False,
            multiple=True,
        ),
        option("python", None, "Python version(s) to use for resolution.", flag=False),
        option("tree", None, "Display the dependency tree."),
        option("install", None, "Show what would be installed for the current system."),
    ]

    loggers = ["poetry.repositories.pypi_repository"]

    def handle(self):
        from poetry.core.packages.project_package import ProjectPackage
        from poetry.io.null_io import NullIO
        from poetry.puzzle import Solver
        from poetry.repositories.pool import Pool
        from poetry.repositories.repository import Repository
        from poetry.utils.env import EnvManager

        packages = self.argument("package")

        if not packages:
            package = self.poetry.package
        else:
            # Using current pool for determine_requirements()
            self._pool = self.poetry.pool

            package = ProjectPackage(
                self.poetry.package.name, self.poetry.package.version
            )

            # Silencing output
            is_quiet = self.io.output.is_quiet()
            if not is_quiet:
                self.io.output.set_quiet(True)

            requirements = self._determine_requirements(packages)

            if not is_quiet:
                self.io.output.set_quiet(False)

            for constraint in requirements:
                name = constraint.pop("name")
                dep = package.add_dependency(name, constraint)
                extras = []
                for extra in self.option("extras"):
                    if " " in extra:
                        extras += [e.strip() for e in extra.split(" ")]
                    else:
                        extras.append(extra)

                for ex in extras:
                    dep.extras.append(ex)

        package.python_versions = self.option("python") or (
            self.poetry.package.python_versions
        )

        pool = self.poetry.pool

        solver = Solver(package, pool, Repository(), Repository(), self._io)

        ops = solver.solve()

        self.line("")
        self.line("Resolution results:")
        self.line("")

        if self.option("tree"):
            show_command = self.application.find("show")
            show_command.init_styles(self.io)

            packages = [op.package for op in ops]
            repo = Repository(packages)

            requires = package.requires + package.dev_requires
            for pkg in repo.packages:
                for require in requires:
                    if pkg.name == require.name:
                        show_command.display_package_tree(self.io, pkg, repo)
                        break

            return 0

        table = self.table([], style="borderless")
        rows = []

        if self.option("install"):
            env = EnvManager(self.poetry).get()
            pool = Pool()
            locked_repository = Repository()
            for op in ops:
                locked_repository.add_package(op.package)

            pool.add_repository(locked_repository)

            solver = Solver(package, pool, Repository(), Repository(), NullIO())
            with solver.use_environment(env):
                ops = solver.solve()

        for op in ops:
            if self.option("install") and op.skipped:
                continue

            pkg = op.package
            row = [
                "<c1>{}</c1>".format(pkg.name),
                "<b>{}</b>".format(pkg.version),
                "",
            ]

            if not pkg.marker.is_any():
                row[2] = str(pkg.marker)

            rows.append(row)

        table.set_rows(rows)
        table.render(self.io)
Esempio n. 25
0
class ConfigCommand(Command):

    name = "config"
    description = "Manages configuration settings."

    arguments = [
        argument("key", "Setting key.", optional=True),
        argument("value", "Setting value.", optional=True, multiple=True),
    ]

    options = [
        option("list", None, "List configuration settings."),
        option("unset", None, "Unset configuration setting."),
        option("local", None,
               "Set/Get from the project's local configuration."),
    ]

    help = """This command allows you to edit the poetry config settings and repositories.

To add a repository:

    <comment>poetry config repositories.foo https://bar.com/simple/</comment>

To remove a repository (repo is a short alias for repositories):

    <comment>poetry config --unset repo.foo</comment>"""

    LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"}

    @property
    def unique_config_values(self):
        from poetry.config.config import boolean_normalizer
        from poetry.config.config import boolean_validator
        from poetry.locations import CACHE_DIR
        from poetry.utils._compat import Path

        unique_config_values = {
            "cache-dir": (
                str,
                lambda val: str(Path(val)),
                str(Path(CACHE_DIR) / "virtualenvs"),
            ),
            "virtualenvs.create":
            (boolean_validator, boolean_normalizer, True),
            "virtualenvs.in-project":
            (boolean_validator, boolean_normalizer, False),
            "virtualenvs.path": (
                str,
                lambda val: str(Path(val)),
                str(Path(CACHE_DIR) / "virtualenvs"),
            ),
        }

        return unique_config_values

    def handle(self):
        from poetry.config.file_config_source import FileConfigSource
        from poetry.locations import CONFIG_DIR
        from poetry.utils._compat import Path
        from poetry.utils._compat import basestring
        from poetry.utils.toml_file import TomlFile

        config = Factory.create_config(self.io)
        config_file = TomlFile(Path(CONFIG_DIR) / "config.toml")

        try:
            local_config_file = TomlFile(self.poetry.file.parent /
                                         "poetry.toml")
            if local_config_file.exists():
                config.merge(local_config_file.read())
        except RuntimeError:
            local_config_file = TomlFile(Path.cwd() / "poetry.toml")

        if self.option("local"):
            config.set_config_source(FileConfigSource(local_config_file))

        if not config_file.exists():
            config_file.path.parent.mkdir(parents=True, exist_ok=True)
            config_file.touch(mode=0o0600)

        if self.option("list"):
            self._list_configuration(config.all(), config.raw())

            return 0

        setting_key = self.argument("key")
        if not setting_key:
            return 0

        if self.argument("value") and self.option("unset"):
            raise RuntimeError(
                "You can not combine a setting value with --unset")

        # show the value if no value is provided
        if not self.argument("value") and not self.option("unset"):
            m = re.match(r"^repos?(?:itories)?(?:\.(.+))?",
                         self.argument("key"))
            if m:
                if not m.group(1):
                    value = {}
                    if config.get("repositories") is not None:
                        value = config.get("repositories")
                else:
                    repo = config.get("repositories.{}".format(m.group(1)))
                    if repo is None:
                        raise ValueError(
                            "There is no {} repository defined".format(
                                m.group(1)))

                    value = repo

                self.line(str(value))
            else:
                values = self.unique_config_values
                if setting_key not in values:
                    raise ValueError(
                        "There is no {} setting.".format(setting_key))

                value = config.get(setting_key)

                if not isinstance(value, basestring):
                    value = json.dumps(value)

                self.line(value)

            return 0

        values = self.argument("value")

        unique_config_values = self.unique_config_values
        if setting_key in unique_config_values:
            if self.option("unset"):
                return config.config_source.remove_property(setting_key)

            return self._handle_single_value(
                config.config_source,
                setting_key,
                unique_config_values[setting_key],
                values,
            )

        # handle repositories
        m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key"))
        if m:
            if not m.group(1):
                raise ValueError(
                    "You cannot remove the [repositories] section")

            if self.option("unset"):
                repo = config.get("repositories.{}".format(m.group(1)))
                if repo is None:
                    raise ValueError(
                        "There is no {} repository defined".format(m.group(1)))

                config.config_source.remove_property("repositories.{}".format(
                    m.group(1)))

                return 0

            if len(values) == 1:
                url = values[0]

                config.config_source.add_property(
                    "repositories.{}.url".format(m.group(1)), url)

                return 0

            raise ValueError(
                "You must pass the url. "
                "Example: poetry config repositories.foo https://bar.com")

        # handle auth
        m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key"))
        if m:
            if self.option("unset"):
                keyring_repository_password_del(config, m.group(2))
                config.auth_config_source.remove_property("{}.{}".format(
                    m.group(1), m.group(2)))

                return 0

            if m.group(1) == "http-basic":
                if len(values) == 1:
                    username = values[0]
                    # Only username, so we prompt for password
                    password = self.secret("Password:"******"Expected one or two arguments "
                                     "(username, password), got {}".format(
                                         len(values)))
                else:
                    username = values[0]
                    password = values[1]

                property_value = dict(username=username)
                try:
                    keyring_repository_password_set(m.group(2), username,
                                                    password)
                except RuntimeError:
                    property_value.update(password=password)

                config.auth_config_source.add_property(
                    "{}.{}".format(m.group(1), m.group(2)), property_value)
            elif m.group(1) == "pypi-token":
                if len(values) != 1:
                    raise ValueError(
                        "Expected only one argument (token), got {}".format(
                            len(values)))

                token = values[0]

                config.auth_config_source.add_property(
                    "{}.{}".format(m.group(1), m.group(2)), token)

            return 0

        # handle certs
        m = re.match(r"(?:certificates)\.([^.]+)\.(cert|client-cert)",
                     self.argument("key"))
        if m:
            if self.option("unset"):
                config.auth_config_source.remove_property(
                    "certificates.{}.{}".format(m.group(1), m.group(2)))

                return 0

            if len(values) == 1:
                config.auth_config_source.add_property(
                    "certificates.{}.{}".format(m.group(1), m.group(2)),
                    values[0])
            else:
                raise ValueError("You must pass exactly 1 value")

            return 0

        raise ValueError("Setting {} does not exist".format(
            self.argument("key")))

    def _handle_single_value(self, source, key, callbacks, values):
        validator, normalizer, _ = callbacks

        if len(values) > 1:
            raise RuntimeError("You can only pass one value.")

        value = values[0]
        if not validator(value):
            raise RuntimeError('"{}" is an invalid value for {}'.format(
                value, key))

        source.add_property(key, normalizer(value))

        return 0

    def _list_configuration(self, config, raw, k=""):
        from poetry.utils._compat import basestring

        orig_k = k
        for key, value in sorted(config.items()):
            if k + key in self.LIST_PROHIBITED_SETTINGS:
                continue

            raw_val = raw.get(key)

            if isinstance(value, dict):
                k += "{}.".format(key)
                self._list_configuration(value, raw_val, k=k)
                k = orig_k

                continue
            elif isinstance(value, list):
                value = [
                    json.dumps(val) if isinstance(val, list) else val
                    for val in value
                ]

                value = "[{}]".format(", ".join(value))

            if k.startswith("repositories."):
                message = "<c1>{}</c1> = <c2>{}</c2>".format(
                    k + key, json.dumps(raw_val))
            elif isinstance(raw_val, basestring) and raw_val != value:
                message = "<c1>{}</c1> = <c2>{}</c2>  # {}".format(
                    k + key, json.dumps(raw_val), value)
            else:
                message = "<c1>{}</c1> = <c2>{}</c2>".format(
                    k + key, json.dumps(value))

            self.line(message)

    def _list_setting(self, contents, setting=None, k=None, default=None):
        values = self._get_setting(contents, setting, k, default)

        for value in values:
            self.line("<comment>{}</comment> = <info>{}</info>".format(
                value[0], value[1]))

    def _get_setting(self, contents, setting=None, k=None, default=None):
        orig_k = k

        if setting and setting.split(".")[0] not in contents:
            value = json.dumps(default)

            return [((k or "") + setting, value)]
        else:
            values = []
            for key, value in contents.items():
                if setting and key != setting.split(".")[0]:
                    continue

                if isinstance(value,
                              dict) or key == "repositories" and k is None:
                    if k is None:
                        k = ""

                    k += re.sub(r"^config\.", "", key + ".")
                    if setting and len(setting) > 1:
                        setting = ".".join(setting.split(".")[1:])

                    values += self._get_setting(value,
                                                k=k,
                                                setting=setting,
                                                default=default)
                    k = orig_k

                    continue

                if isinstance(value, list):
                    value = [
                        json.dumps(val) if isinstance(val, list) else val
                        for val in value
                    ]

                    value = "[{}]".format(", ".join(value))

                value = json.dumps(value)

                values.append(((k or "") + key, value))

            return values

    def _get_formatted_value(self, value):
        if isinstance(value, list):
            value = [
                json.dumps(val) if isinstance(val, list) else val
                for val in value
            ]

            value = "[{}]".format(", ".join(value))

        return json.dumps(value)
Esempio n. 26
0
class VersionCommand(Command):

    name = "version"
    description = ("Shows the version of the project or bumps it when a valid "
                   "bump rule is provided.")

    arguments = [
        argument(
            "version",
            "The version number or the rule to update the version.",
            optional=True,
        )
    ]
    options = [option("short", "s", "Output the version number only")]

    help = """\
The version command shows the current version of the project or bumps the version of
the project and writes the new version back to <comment>pyproject.toml</> if a valid
bump rule is provided.

The new version should ideally be a valid semver string or a valid bump rule:
patch, minor, major, prepatch, preminor, premajor, prerelease.
"""

    RESERVED = {
        "major",
        "minor",
        "patch",
        "premajor",
        "preminor",
        "prepatch",
        "prerelease",
    }

    def handle(self):
        version = self.argument("version")

        if version:
            version = self.increment_version(
                self.poetry.package.pretty_version, version)

            if self.option("short"):
                self.line("{}".format(version))
            else:
                self.line(
                    "Bumping version from <b>{}</> to <fg=green>{}</>".format(
                        self.poetry.package.pretty_version, version))

            content = self.poetry.file.read()
            poetry_content = content["tool"]["poetry"]
            poetry_content["version"] = version.text

            self.poetry.file.write(content)
        else:
            if self.option("short"):
                self.line("{}".format(self.poetry.package.pretty_version))
            else:
                self.line("<comment>{}</> <info>{}</>".format(
                    self.poetry.package.name,
                    self.poetry.package.pretty_version))

    def increment_version(self, version, rule):
        from poetry.core.semver import Version

        try:
            version = Version.parse(version)
        except ValueError:
            raise ValueError(
                "The project's version doesn't seem to follow semver")

        if rule in {"major", "premajor"}:
            new = version.next_major
            if rule == "premajor":
                new = new.first_prerelease
        elif rule in {"minor", "preminor"}:
            new = version.next_minor
            if rule == "preminor":
                new = new.first_prerelease
        elif rule in {"patch", "prepatch"}:
            new = version.next_patch
            if rule == "prepatch":
                new = new.first_prerelease
        elif rule == "prerelease":
            if version.is_prerelease():
                pre = version.prerelease
                new_prerelease = int(pre[1]) + 1
                new = Version.parse("{}.{}.{}-{}".format(
                    version.major,
                    version.minor,
                    version.patch,
                    ".".join([pre[0], str(new_prerelease)]),
                ))
            else:
                new = version.next_patch.first_prerelease
        else:
            new = Version.parse(rule)

        return new
Esempio n. 27
0
class RemoveCommand(EnvCommand):

    name = "remove"
    description = "Removes a package from the project dependencies."

    arguments = [
        argument("packages", "Packages that should be removed", multiple=True)
    ]
    options = [
        option("dev", "D",
               "Removes a package from the development dependencies."),
        option(
            "dry-run",
            None,
            "Outputs the operations but will not execute anything "
            "(implicitly enables --verbose).",
        ),
    ]

    help = """The <info>remove</info> command removes a package from the current
list of installed packages

<info>poetry remove</info>"""

    loggers = ["poetry.repositories.pypi_repository"]

    def handle(self):
        from poetry.installation import Installer

        packages = self.argument("packages")
        is_dev = self.option("dev")

        original_content = self.poetry.file.read()
        content = self.poetry.file.read()
        poetry_content = content["tool"]["poetry"]
        section = "dependencies"
        if is_dev:
            section = "dev-dependencies"

        # Deleting entries
        requirements = {}
        for name in packages:
            found = False
            for key in poetry_content[section]:
                if key.lower() == name.lower():
                    found = True
                    requirements[key] = poetry_content[section][key]
                    break

            if not found:
                raise ValueError("Package {} not found".format(name))

        for key in requirements:
            del poetry_content[section][key]

        # Write the new content back
        self.poetry.file.write(content)

        # Update packages
        self.reset_poetry()

        installer = Installer(self.io, self.env, self.poetry.package,
                              self.poetry.locker, self.poetry.pool)

        installer.dry_run(self.option("dry-run"))
        installer.update(True)
        installer.whitelist(requirements)

        try:
            status = installer.run()
        except Exception:
            self.poetry.file.write(original_content)

            raise

        if status != 0 or self.option("dry-run"):
            # Revert changes
            if not self.option("dry-run"):
                self.error("\n"
                           "Removal failed, reverting pyproject.toml "
                           "to its original content.")

            self.poetry.file.write(original_content)

        return status
Esempio n. 28
0
class ShowCommand(EnvCommand):

    name = "show"
    description = "Shows information about packages."

    arguments = [argument("package", "The package to inspect", optional=True)]
    options = [
        option("no-dev", None, "Do not list the development dependencies."),
        option("tree", "t", "List the dependencies as a tree."),
        option("latest", "l", "Show the latest version."),
        option(
            "outdated",
            "o",
            "Show the latest version but only for packages that are outdated.",
        ),
        option(
            "all",
            "a",
            "Show all packages (even those not compatible with current system).",
        ),
    ]

    help = """The show command displays detailed information about a package, or
lists all packages available."""

    colors = ["cyan", "yellow", "green", "magenta", "blue"]

    def handle(self):
        from clikit.utils.terminal import Terminal

        from poetry.io.null_io import NullIO
        from poetry.puzzle.solver import Solver
        from poetry.repositories.installed_repository import InstalledRepository
        from poetry.repositories.pool import Pool
        from poetry.repositories.repository import Repository
        from poetry.utils.helpers import get_package_version_display_string

        package = self.argument("package")

        if self.option("tree"):
            self.init_styles(self.io)

        if self.option("outdated"):
            self._args.set_option("latest", True)

        include_dev = not self.option("no-dev")
        locked_repo = self.poetry.locker.locked_repository(True)

        # Show tree view if requested
        if self.option("tree") and not package:
            requires = self.poetry.package.requires + self.poetry.package.dev_requires
            packages = locked_repo.packages
            for package in packages:
                for require in requires:
                    if package.name == require.name:
                        self.display_package_tree(self._io, package,
                                                  locked_repo)
                        break

            return 0

        table = self.table(style="compact")
        # table.style.line_vc_char = ""
        locked_packages = locked_repo.packages
        pool = Pool(ignore_repository_names=True)
        pool.add_repository(locked_repo)
        solver = Solver(
            self.poetry.package,
            pool=pool,
            installed=Repository(),
            locked=locked_repo,
            io=NullIO(),
        )
        solver.provider.load_deferred(False)
        with solver.use_environment(self.env):
            ops = solver.solve()

        required_locked_packages = set(
            [op.package for op in ops if not op.skipped])

        if self.option("no-dev"):
            required_locked_packages = [
                p for p in locked_packages if p.category == "main"
            ]

        if package:
            pkg = None
            for locked in locked_packages:
                if package.lower() == locked.name:
                    pkg = locked
                    break

            if not pkg:
                raise ValueError("Package {} not found".format(package))

            if self.option("tree"):
                self.display_package_tree(self.io, pkg, locked_repo)

                return 0

            rows = [
                ["<info>name</>", " : <c1>{}</>".format(pkg.pretty_name)],
                [
                    "<info>version</>",
                    " : <b>{}</b>".format(pkg.pretty_version)
                ],
                ["<info>description</>", " : {}".format(pkg.description)],
            ]

            table.add_rows(rows)
            table.render(self.io)

            if pkg.requires:
                self.line("")
                self.line("<info>dependencies</info>")
                for dependency in pkg.requires:
                    self.line(" - <c1>{}</c1> <b>{}</b>".format(
                        dependency.pretty_name, dependency.pretty_constraint))

            return 0

        show_latest = self.option("latest")
        show_all = self.option("all")
        terminal = Terminal()
        width = terminal.width
        name_length = version_length = latest_length = 0
        latest_packages = {}
        latest_statuses = {}
        installed_repo = InstalledRepository.load(self.env)

        # Computing widths
        for locked in locked_packages:
            if locked not in required_locked_packages and not show_all:
                continue

            current_length = len(locked.pretty_name)
            if not self._io.output.supports_ansi():
                installed_status = self.get_installed_status(
                    locked, installed_repo)

                if installed_status == "not-installed":
                    current_length += 4

            if show_latest:
                latest = self.find_latest_package(locked, include_dev)
                if not latest:
                    latest = locked

                latest_packages[locked.pretty_name] = latest
                update_status = latest_statuses[
                    locked.pretty_name] = self.get_update_status(
                        latest, locked)

                if not self.option(
                        "outdated") or update_status != "up-to-date":
                    name_length = max(name_length, current_length)
                    version_length = max(
                        version_length,
                        len(
                            get_package_version_display_string(
                                locked, root=self.poetry.file.parent)),
                    )
                    latest_length = max(
                        latest_length,
                        len(
                            get_package_version_display_string(
                                latest, root=self.poetry.file.parent)),
                    )
            else:
                name_length = max(name_length, current_length)
                version_length = max(
                    version_length,
                    len(
                        get_package_version_display_string(
                            locked, root=self.poetry.file.parent)),
                )

        write_version = name_length + version_length + 3 <= width
        write_latest = name_length + version_length + latest_length + 3 <= width
        write_description = name_length + version_length + latest_length + 24 <= width

        for locked in locked_packages:
            color = "cyan"
            name = locked.pretty_name
            install_marker = ""
            if locked not in required_locked_packages:
                if not show_all:
                    continue

                color = "black;options=bold"
            else:
                installed_status = self.get_installed_status(
                    locked, installed_repo)
                if installed_status == "not-installed":
                    color = "red"

                    if not self._io.output.supports_ansi():
                        # Non installed in non decorated mode
                        install_marker = " (!)"

            if (show_latest and self.option("outdated")
                    and latest_statuses[locked.pretty_name] == "up-to-date"):
                continue

            line = "<fg={}>{:{}}{}</>".format(
                color, name, name_length - len(install_marker), install_marker)
            if write_version:
                line += " <b>{:{}}</b>".format(
                    get_package_version_display_string(
                        locked, root=self.poetry.file.parent),
                    version_length,
                )
            if show_latest:
                latest = latest_packages[locked.pretty_name]
                update_status = latest_statuses[locked.pretty_name]

                if write_latest:
                    color = "green"
                    if update_status == "semver-safe-update":
                        color = "red"
                    elif update_status == "update-possible":
                        color = "yellow"

                    line += " <fg={}>{:{}}</>".format(
                        color,
                        get_package_version_display_string(
                            latest, root=self.poetry.file.parent),
                        latest_length,
                    )

            if write_description:
                description = locked.description
                remaining = width - name_length - version_length - 4
                if show_latest:
                    remaining -= latest_length

                if len(locked.description) > remaining:
                    description = description[:remaining - 3] + "..."

                line += " " + description

            self.line(line)

    def display_package_tree(self, io, package, installed_repo):
        io.write("<c1>{}</c1>".format(package.pretty_name))
        description = ""
        if package.description:
            description = " " + package.description

        io.write_line(" <b>{}</b>{}".format(package.pretty_version,
                                            description))

        dependencies = package.requires
        dependencies = sorted(dependencies, key=lambda x: x.name)
        tree_bar = "├"
        j = 0
        total = len(dependencies)
        for dependency in dependencies:
            j += 1
            if j == total:
                tree_bar = "└"

            level = 1
            color = self.colors[level]
            info = "{tree_bar}── <{color}>{name}</{color}> {constraint}".format(
                tree_bar=tree_bar,
                color=color,
                name=dependency.name,
                constraint=dependency.pretty_constraint,
            )
            self._write_tree_line(io, info)

            tree_bar = tree_bar.replace("└", " ")
            packages_in_tree = [package.name, dependency.name]

            self._display_tree(io, dependency, installed_repo,
                               packages_in_tree, tree_bar, level + 1)

    def _display_tree(
        self,
        io,
        dependency,
        installed_repo,
        packages_in_tree,
        previous_tree_bar="├",
        level=1,
    ):
        previous_tree_bar = previous_tree_bar.replace("├", "│")

        dependencies = []
        for package in installed_repo.packages:
            if package.name == dependency.name:
                dependencies = package.requires

                break

        dependencies = sorted(dependencies, key=lambda x: x.name)
        tree_bar = previous_tree_bar + "   ├"
        i = 0
        total = len(dependencies)
        for dependency in dependencies:
            i += 1
            current_tree = packages_in_tree
            if i == total:
                tree_bar = previous_tree_bar + "   └"

            color_ident = level % len(self.colors)
            color = self.colors[color_ident]

            circular_warn = ""
            if dependency.name in current_tree:
                circular_warn = "(circular dependency aborted here)"

            info = "{tree_bar}── <{color}>{name}</{color}> {constraint} {warn}".format(
                tree_bar=tree_bar,
                color=color,
                name=dependency.name,
                constraint=dependency.pretty_constraint,
                warn=circular_warn,
            )
            self._write_tree_line(io, info)

            tree_bar = tree_bar.replace("└", " ")

            if dependency.name not in current_tree:
                current_tree.append(dependency.name)

                self._display_tree(io, dependency, installed_repo,
                                   current_tree, tree_bar, level + 1)

    def _write_tree_line(self, io, line):
        if not io.output.supports_ansi():
            line = line.replace("└", "`-")
            line = line.replace("├", "|-")
            line = line.replace("──", "-")
            line = line.replace("│", "|")

        io.write_line(line)

    def init_styles(self, io):
        from clikit.api.formatter import Style

        for color in self.colors:
            style = Style(color).fg(color)
            io.output.formatter.add_style(style)
            io.error_output.formatter.add_style(style)

    def find_latest_package(self, package, include_dev):
        from clikit.io import NullIO

        from poetry.puzzle.provider import Provider
        from poetry.version.version_selector import VersionSelector

        # find the latest version allowed in this pool
        if package.source_type in ("git", "file", "directory"):
            requires = self.poetry.package.requires
            if include_dev:
                requires = requires + self.poetry.package.dev_requires

            for dep in requires:
                if dep.name == package.name:
                    provider = Provider(self.poetry.package, self.poetry.pool,
                                        NullIO())

                    if dep.is_vcs():
                        return provider.search_for_vcs(dep)[0]
                    if dep.is_file():
                        return provider.search_for_file(dep)[0]
                    if dep.is_directory():
                        return provider.search_for_directory(dep)[0]

        name = package.name
        selector = VersionSelector(self.poetry.pool)

        return selector.find_best_candidate(
            name, ">={}".format(package.pretty_version))

    def get_update_status(self, latest, package):
        from poetry.core.semver import parse_constraint

        if latest.full_pretty_version == package.full_pretty_version:
            return "up-to-date"

        constraint = parse_constraint("^" + package.pretty_version)

        if latest.version and constraint.allows(latest.version):
            # It needs an immediate semver-compliant upgrade
            return "semver-safe-update"

        # it needs an upgrade but has potential BC breaks so is not urgent
        return "update-possible"

    def get_installed_status(self, locked, installed_repo):
        for package in installed_repo.packages:
            if locked.name == package.name:
                return "installed"

        return "not-installed"
Esempio n. 29
0
class DebugResolveCommand(Command):

    name = "resolve"
    description = "Debugs dependency resolution."

    arguments = [
        argument("package",
                 "The packages to resolve.",
                 optional=True,
                 multiple=True)
    ]
    options = [
        option(
            "extras",
            "E",
            "Extras to activate for the dependency.",
            flag=False,
            multiple=True,
        ),
        option("python",
               None,
               "Python version(s) to use for resolution.",
               flag=False),
        option("tree", None, "Display the dependency tree."),
        option("install", None,
               "Show what would be installed for the current system."),
    ]

    loggers = ["poetry.repositories.pypi_repository"]

    def handle(self):
        from poetry.packages import ProjectPackage
        from poetry.puzzle import Solver
        from poetry.repositories.repository import Repository
        from poetry.semver import parse_constraint
        from poetry.utils.env import EnvManager

        packages = self.argument("package")

        if not packages:
            package = self.poetry.package
        else:
            package = ProjectPackage(self.poetry.package.name,
                                     self.poetry.package.version)
            requirements = self._format_requirements(packages)

            for name, constraint in requirements.items():
                dep = package.add_dependency(name, constraint)
                extras = []
                for extra in self.option("extras"):
                    if " " in extra:
                        extras += [e.strip() for e in extra.split(" ")]
                    else:
                        extras.append(extra)

                for ex in extras:
                    dep.extras.append(ex)

        package.python_versions = self.option("python") or (
            self.poetry.package.python_versions)

        pool = self.poetry.pool

        solver = Solver(package, pool, Repository(), Repository(), self._io)

        ops = solver.solve()

        self.line("")
        self.line("Resolution results:")
        self.line("")

        if self.option("tree"):
            show_command = self.application.find("show")
            show_command.init_styles(self.io)

            packages = [op.package for op in ops]
            repo = Repository(packages)

            requires = package.requires + package.dev_requires
            for pkg in repo.packages:
                for require in requires:
                    if pkg.name == require.name:
                        show_command.display_package_tree(self.io, pkg, repo)
                        break

            return 0

        env = EnvManager(self.poetry.config).get(self.poetry.file.parent)
        current_python_version = parse_constraint(".".join(
            str(v) for v in env.version_info))
        table = self.table([], style="borderless")
        rows = []
        for op in ops:
            pkg = op.package
            if self.option("install"):
                if not pkg.python_constraint.allows(
                        current_python_version) or not env.is_valid_for_marker(
                            pkg.marker):
                    continue
            row = [
                "<info>{}</info>".format(pkg.name),
                "<b>{}</b>".format(pkg.version),
                "",
            ]

            if not pkg.marker.is_any():
                row[2] = str(pkg.marker)

            rows.append(row)

        table.set_rows(rows)
        table.render(self.io)

    def _determine_requirements(self,
                                requires):  # type: (List[str]) -> List[str]
        from poetry.semver import parse_constraint

        if not requires:
            return []

        requires = self._parse_name_version_pairs(requires)
        for requirement in requires:
            if "version" in requirement:
                parse_constraint(requirement["version"])

        return requires

    def _parse_name_version_pairs(self, pairs):  # type: (list) -> list
        result = []

        for i in range(len(pairs)):
            if pairs[i].startswith("git+https://"):
                url = pairs[i].lstrip("git+")
                rev = None
                if "@" in url:
                    url, rev = url.split("@")

                pair = {"name": url.split("/")[-1].rstrip(".git"), "git": url}
                if rev:
                    pair["rev"] = rev

                result.append(pair)

                continue

            pair = re.sub("^([^=: ]+)[=: ](.*)$", "\\1 \\2", pairs[i].strip())
            pair = pair.strip()

            if " " in pair:
                name, version = pair.split(" ", 2)
                result.append({"name": name, "version": version})
            else:
                result.append({"name": pair, "version": "*"})

        return result

    def _format_requirements(self, requirements):  # type: (List[str]) -> dict
        requires = {}
        requirements = self._determine_requirements(requirements)

        for requirement in requirements:
            name = requirement.pop("name")
            requires[name] = requirement

        return requires
Esempio n. 30
0
class SelfUpdateCommand(Command):

    name = "update"
    description = "Updates Poetry to the latest version."

    arguments = [
        argument("version", "The version to update to.", optional=True)
    ]
    options = [option("preview", None, "Install prereleases.")]

    REPOSITORY_URL = "https://github.com/python-poetry/poetry"
    BASE_URL = REPOSITORY_URL + "/releases/download"

    @property
    def home(self):  # type: () -> Path
        from pathlib import Path

        return Path(os.environ.get("POETRY_HOME", "~/.poetry")).expanduser()

    @property
    def bin(self):  # type: () -> Path
        return self.home / "bin"

    @property
    def lib(self):  # type: () -> Path
        return self.home / "lib"

    @property
    def lib_backup(self):  # type: () -> Path
        return self.home / "lib-backup"

    def handle(self):  # type: () -> None
        from poetry.__version__ import __version__
        from poetry.core.semver import Version
        from poetry.repositories.pypi_repository import PyPiRepository

        self._check_recommended_installation()

        version = self.argument("version")
        if not version:
            version = ">=" + __version__

        repo = PyPiRepository(fallback=False)
        packages = repo.find_packages(
            Dependency("poetry",
                       version,
                       allows_prereleases=self.option("preview")))
        if not packages:
            self.line("No release found for the specified version")
            return

        packages.sort(key=cmp_to_key(lambda x, y: 0 if x.version == y.version
                                     else int(x.version < y.version or -1)))

        release = None
        for package in packages:
            if package.is_prerelease():
                if self.option("preview"):
                    release = package

                    break

                continue

            release = package

            break

        if release is None:
            self.line("No new release found")
            return

        if release.version == Version.parse(__version__):
            self.line("You are using the latest version")
            return

        self.update(release)

    def update(self, release):  # type: ("Package") -> None
        version = release.version
        self.line("Updating to <info>{}</info>".format(version))

        if self.lib_backup.exists():
            shutil.rmtree(str(self.lib_backup))

        # Backup the current installation
        if self.lib.exists():
            shutil.copytree(str(self.lib), str(self.lib_backup))
            shutil.rmtree(str(self.lib))

        try:
            self._update(version)
        except Exception:
            if not self.lib_backup.exists():
                raise

            shutil.copytree(str(self.lib_backup), str(self.lib))
            shutil.rmtree(str(self.lib_backup))

            raise
        finally:
            if self.lib_backup.exists():
                shutil.rmtree(str(self.lib_backup))

        self.make_bin()

        self.line("")
        self.line("")
        self.line(
            "<info>Poetry</info> (<comment>{}</comment>) is installed now. Great!"
            .format(version))

    def _update(self, version):  # type: ("Version") -> None
        from poetry.utils.helpers import temporary_directory

        release_name = self._get_release_name(version)

        checksum = "{}.sha256sum".format(release_name)

        base_url = self.BASE_URL

        try:
            r = urlopen(base_url + "/{}/{}".format(version, checksum))
        except HTTPError as e:
            if e.code == 404:
                raise RuntimeError("Could not find {} file".format(checksum))

            raise

        checksum = r.read().decode().strip()

        # We get the payload from the remote host
        name = "{}.tar.gz".format(release_name)
        try:
            r = urlopen(base_url + "/{}/{}".format(version, name))
        except HTTPError as e:
            if e.code == 404:
                raise RuntimeError("Could not find {} file".format(name))

            raise

        meta = r.info()
        size = int(meta["Content-Length"])
        current = 0
        block_size = 8192

        bar = self.progress_bar(max=size)
        bar.set_format(
            " - Downloading <info>{}</> <comment>%percent%%</>".format(name))
        bar.start()

        sha = hashlib.sha256()
        with temporary_directory(prefix="poetry-updater-") as dir_:
            tar = os.path.join(dir_, name)
            with open(tar, "wb") as f:
                while True:
                    buffer = r.read(block_size)
                    if not buffer:
                        break

                    current += len(buffer)
                    f.write(buffer)
                    sha.update(buffer)

                    bar.set_progress(current)

            bar.finish()

            # Checking hashes
            if checksum != sha.hexdigest():
                raise RuntimeError(
                    "Hashes for {} do not match: {} != {}".format(
                        name, checksum, sha.hexdigest()))

            gz = GzipFile(tar, mode="rb")
            try:
                with tarfile.TarFile(tar,
                                     fileobj=gz,
                                     format=tarfile.PAX_FORMAT) as f:
                    f.extractall(str(self.lib))
            finally:
                gz.close()

    def process(self, *args):  # type: (*Any) -> str
        return subprocess.check_output(list(args), stderr=subprocess.STDOUT)

    def _check_recommended_installation(self):  # type: () -> None
        from pathlib import Path

        current = Path(__file__)
        try:
            current.relative_to(self.home)
        except ValueError:
            raise RuntimeError(
                "Poetry was not installed with the recommended installer. "
                "Cannot update automatically.")

    def _get_release_name(self, version):  # type: ("Version") -> str
        platform = sys.platform
        if platform == "linux2":
            platform = "linux"

        return "poetry-{}-{}".format(version, platform)

    def make_bin(self):  # type: () -> None
        from poetry.utils._compat import WINDOWS

        self.bin.mkdir(0o755, parents=True, exist_ok=True)

        python_executable = self._which_python()

        if WINDOWS:
            with self.bin.joinpath("poetry.bat").open("w", newline="") as f:
                f.write(
                    BAT.format(
                        python_executable=python_executable,
                        poetry_bin=str(self.bin / "poetry").replace(
                            os.environ["USERPROFILE"], "%USERPROFILE%"),
                    ))

        bin_content = BIN
        if not WINDOWS:
            bin_content = "#!/usr/bin/env {}\n".format(
                python_executable) + bin_content

        self.bin.joinpath("poetry").write_text(bin_content, encoding="utf-8")

        if not WINDOWS:
            # Making the file executable
            st = os.stat(str(self.bin.joinpath("poetry")))
            os.chmod(str(self.bin.joinpath("poetry")),
                     st.st_mode | stat.S_IEXEC)

    def _which_python(self):  # type: () -> str
        """
        Decides which python executable we'll embed in the launcher script.
        """
        from poetry.utils._compat import WINDOWS

        allowed_executables = ["python", "python3"]
        if WINDOWS:
            allowed_executables += ["py.exe -3", "py.exe -2"]

        # \d in regex ensures we can convert to int later
        version_matcher = re.compile(
            r"^Python (?P<major>\d+)\.(?P<minor>\d+)\..+$")
        fallback = None
        for executable in allowed_executables:
            try:
                raw_version = subprocess.check_output(
                    executable + " --version",
                    stderr=subprocess.STDOUT,
                    shell=True).decode("utf-8")
            except subprocess.CalledProcessError:
                continue

            match = version_matcher.match(raw_version.strip())
            if match and tuple(map(int, match.groups())) >= (3, 0):
                # favor the first py3 executable we can find.
                return executable

            if fallback is None:
                # keep this one as the fallback; it was the first valid executable we found.
                fallback = executable

        if fallback is None:
            # Avoid breaking existing scripts
            fallback = "python"

        return fallback