Exemplo n.º 1
0
def test_installer_with_pypi_repository(package, locker, installed):
    pool = Pool()
    pool.add_repository(MockRepository())

    installer = Installer(
        NullIO(), NullVenv(), package, locker, pool, installed=installed
    )

    package.add_dependency("pytest", "^3.5", category="dev")
    installer.run()

    expected = fixture("with-pypi-repository")

    assert locker.written_data == expected
Exemplo n.º 2
0
    def _get_pool(self):
        from poetry.repositories import Pool
        from poetry.repositories.pypi_repository import PyPiRepository

        if isinstance(self, VenvCommand):
            return self.poetry.pool

        if self._pool is None:
            self._pool = Pool()
            self._pool.add_repository(PyPiRepository())

        return self._pool
Exemplo n.º 3
0
    def _do_install(self, local_repo):
        locked_repository = Repository()
        if self._update:
            if self._locker.is_locked() and not self._lock:
                locked_repository = self._locker.locked_repository(True)

                # If no packages have been whitelisted (The ones we want to update),
                # we whitelist every package in the lock file.
                if not self._whitelist:
                    for pkg in locked_repository.packages:
                        self._whitelist.append(pkg.name)

            # Checking extras
            for extra in self._extras:
                if extra not in self._package.extras:
                    raise ValueError(
                        "Extra [{}] is not specified.".format(extra))

            self._io.write_line("<info>Updating dependencies</>")
            solver = Solver(
                self._package,
                self._pool,
                self._installed_repository,
                locked_repository,
                self._io,
            )

            ops = solver.solve(use_latest=self._whitelist)
        else:
            self._io.write_line(
                "<info>Installing dependencies from lock file</>")

            locked_repository = self._locker.locked_repository(True)

            if not self._locker.is_fresh():
                self._io.write_line(
                    "<warning>"
                    "Warning: The lock file is not up to date with "
                    "the latest changes in pyproject.toml. "
                    "You may be getting outdated dependencies. "
                    "Run update to update them."
                    "</warning>")

            for extra in self._extras:
                if extra not in self._locker.lock_data.get("extras", {}):
                    raise ValueError(
                        "Extra [{}] is not specified.".format(extra))

            # If we are installing from lock
            # Filter the operations by comparing it with what is
            # currently installed
            ops = self._get_operations_from_lock(locked_repository)

        self._populate_local_repo(local_repo, ops)

        if self._update:
            self._write_lock_file(local_repo)

            if self._lock:
                # If we are only in lock mode, no need to go any further
                return 0

        root = self._package
        if not self.is_dev_mode():
            root = root.clone()
            del root.dev_requires[:]

        with root.with_python_versions(".".join(
            [str(i) for i in self._env.version_info[:3]])):
            # We resolve again by only using the lock file
            pool = Pool(ignore_repository_names=True)

            # Making a new repo containing the packages
            # newly resolved and the ones from the current lock file
            repo = Repository()
            for package in local_repo.packages + locked_repository.packages:
                if not repo.has_package(package):
                    repo.add_package(package)

            pool.add_repository(repo)

            # We whitelist all packages to be sure
            # that the latest ones are picked up
            whitelist = []
            for pkg in locked_repository.packages:
                whitelist.append(pkg.name)

            solver = Solver(root, pool, self._installed_repository,
                            locked_repository, NullIO())

            ops = solver.solve(use_latest=whitelist)

        # We need to filter operations so that packages
        # not compatible with the current system,
        # or optional and not requested, are dropped
        self._filter_operations(ops, local_repo)

        self._io.write_line("")

        # Execute operations
        actual_ops = [op for op in ops if not op.skipped]
        if not actual_ops and (self._execute_operations or self._dry_run):
            self._io.write_line("Nothing to install or update")

        if actual_ops and (self._execute_operations or self._dry_run):
            installs = []
            updates = []
            uninstalls = []
            skipped = []
            for op in ops:
                if op.skipped:
                    skipped.append(op)
                    continue

                if op.job_type == "install":
                    installs.append("{}:{}".format(
                        op.package.pretty_name,
                        op.package.full_pretty_version))
                elif op.job_type == "update":
                    updates.append("{}:{}".format(
                        op.target_package.pretty_name,
                        op.target_package.full_pretty_version,
                    ))
                elif op.job_type == "uninstall":
                    uninstalls.append(op.package.pretty_name)

            self._io.write_line("")
            self._io.write_line(
                "Package operations: "
                "<info>{}</> install{}, "
                "<info>{}</> update{}, "
                "<info>{}</> removal{}"
                "{}".format(
                    len(installs),
                    "" if len(installs) == 1 else "s",
                    len(updates),
                    "" if len(updates) == 1 else "s",
                    len(uninstalls),
                    "" if len(uninstalls) == 1 else "s",
                    ", <info>{}</> skipped".format(len(skipped))
                    if skipped and self.is_verbose() else "",
                ))

        self._io.write_line("")
        for op in ops:
            self._execute(op)
Exemplo n.º 4
0
    def _do_install(self) -> int:
        from poetry.puzzle.solver import Solver

        locked_repository = Repository("poetry-locked")
        if self._update:
            if self._locker.is_locked() and not self._lock:
                locked_repository = self._locker.locked_repository()

                # If no packages have been whitelisted (The ones we want to update),
                # we whitelist every package in the lock file.
                if not self._whitelist:
                    for pkg in locked_repository.packages:
                        self._whitelist.append(pkg.name)

            # Checking extras
            for extra in self._extras:
                if extra not in self._package.extras:
                    raise ValueError(f"Extra [{extra}] is not specified.")

            self._io.write_line("<info>Updating dependencies</>")
            solver = Solver(
                self._package,
                self._pool,
                self._installed_repository.packages,
                locked_repository.packages,
                self._io,
            )

            with solver.provider.use_source_root(
                    source_root=self._env.path.joinpath("src")):
                ops = solver.solve(
                    use_latest=self._whitelist).calculate_operations()
        else:
            self._io.write_line(
                "<info>Installing dependencies from lock file</>")

            locked_repository = self._locker.locked_repository()

            if not self._locker.is_fresh():
                self._io.write_error_line(
                    "<warning>"
                    "Warning: poetry.lock is not consistent with pyproject.toml. "
                    "You may be getting improper dependencies. "
                    "Run `poetry lock [--no-update]` to fix it."
                    "</warning>")

            for extra in self._extras:
                if extra not in self._locker.lock_data.get("extras", {}):
                    raise ValueError(f"Extra [{extra}] is not specified.")

            # If we are installing from lock
            # Filter the operations by comparing it with what is
            # currently installed
            ops = self._get_operations_from_lock(locked_repository)

        lockfile_repo = LockfileRepository()
        self._populate_lockfile_repo(lockfile_repo, ops)

        if self._update:
            self._write_lock_file(lockfile_repo)

            if self._lock:
                # If we are only in lock mode, no need to go any further
                return 0

        if self._groups is not None:
            root = self._package.with_dependency_groups(list(self._groups),
                                                        only=True)
        else:
            root = self._package.without_optional_dependency_groups()

        if self._io.is_verbose():
            self._io.write_line("")
            self._io.write_line(
                "<info>Finding the necessary packages for the current system</>"
            )

        # We resolve again by only using the lock file
        pool = Pool(ignore_repository_names=True)

        # Making a new repo containing the packages
        # newly resolved and the ones from the current lock file
        repo = Repository("poetry-repo")
        for package in lockfile_repo.packages + locked_repository.packages:
            if not package.is_direct_origin() and not repo.has_package(
                    package):
                repo.add_package(package)

        pool.add_repository(repo)

        solver = Solver(
            root,
            pool,
            self._installed_repository.packages,
            locked_repository.packages,
            NullIO(),
        )
        # Everything is resolved at this point, so we no longer need
        # to load deferred dependencies (i.e. VCS, URL and path dependencies)
        solver.provider.load_deferred(False)

        with solver.use_environment(self._env):
            ops = solver.solve(
                use_latest=self._whitelist).calculate_operations(
                    with_uninstalls=self._requires_synchronization,
                    synchronize=self._requires_synchronization,
                )

        if not self._requires_synchronization:
            # If no packages synchronisation has been requested we need
            # to calculate the uninstall operations
            from poetry.puzzle.transaction import Transaction

            transaction = Transaction(
                locked_repository.packages,
                [(package, 0) for package in lockfile_repo.packages],
                installed_packages=self._installed_repository.packages,
                root_package=root,
            )

            ops = [
                op for op in transaction.calculate_operations(
                    with_uninstalls=True) if op.job_type == "uninstall"
            ] + ops

        # We need to filter operations so that packages
        # not compatible with the current system,
        # or optional and not requested, are dropped
        self._filter_operations(ops, lockfile_repo)

        # Execute operations
        return self._execute(ops)
Exemplo n.º 5
0
def test_repository_from_normal_pool():
    repo = LegacyRepository("foo", "https://foo.bar")
    pool = Pool()
    pool.add_repository(repo)

    assert pool.repository("foo") is repo
Exemplo n.º 6
0
def pool(repo):
    pool = Pool()
    pool.add_repository(repo)

    return pool
Exemplo n.º 7
0
def test_pool_with_initial_repositories():
    repo = Repository()
    pool = Pool([repo])

    assert 1 == len(pool.repositories)
    assert not pool.has_default()
Exemplo n.º 8
0
class InitCommand(Command):
    """
    Creates a basic <comment>pyproject.toml</> file in the current directory.

    init
        {--name= : Name of the package}
        {--description= : Description of the package}
        {--author= : Author name of the package}
        {--dependency=* : Package to require with an optional version constraint,
                          e.g. requests:^2.10.0 or requests=2.11.1}
        {--dev-dependency=* : Package to require for development with an optional version constraint,
                              e.g. requests:^2.10.0 or requests=2.11.1}
        {--l|license= : License of the package}
    """

    help = """\
The <info>init</info> command creates a basic <comment>pyproject.toml</> file in the current directory.
"""

    def __init__(self):
        super(InitCommand, self).__init__()

        self._pool = None

    def handle(self):
        from poetry.layouts import layout
        from poetry.utils._compat import Path
        from poetry.vcs.git import GitConfig

        if (Path.cwd() / "pyproject.toml").exists():
            self.error("A pyproject.toml file already exists.")
            return 1

        vcs_config = GitConfig()

        self.line(
            [
                "",
                "This command will guide you through creating your <info>pyproject.toml</> config.",
                "",
            ]
        )

        name = self.option("name")
        if not name:
            name = Path.cwd().name.lower()

            question = self.create_question(
                "Package name [<comment>{}</comment>]: ".format(name), default=name
            )
            name = self.ask(question)

        version = "0.1.0"
        question = self.create_question(
            "Version [<comment>{}</comment>]: ".format(version), default=version
        )
        version = self.ask(question)

        description = self.option("description") or ""
        question = self.create_question(
            "Description [<comment>{}</comment>]: ".format(description),
            default=description,
        )
        description = self.ask(question)

        author = self.option("author")
        if not author and vcs_config and vcs_config.get("user.name"):
            author = vcs_config["user.name"]
            author_email = vcs_config.get("user.email")
            if author_email:
                author += " <{}>".format(author_email)

        question = self.create_question(
            "Author [<comment>{}</comment>, n to skip]: ".format(author), default=author
        )
        question.validator = lambda v: self._validate_author(v, author)
        author = self.ask(question)

        if not author:
            authors = []
        else:
            authors = [author]

        license = self.option("license") or ""

        question = self.create_question(
            "License [<comment>{}</comment>]: ".format(license), default=license
        )
        question.validator = self._validate_license
        license = self.ask(question)

        question = self.create_question("Compatible Python versions [*]: ", default="*")
        python = self.ask(question)

        self.line("")

        requirements = {}

        question = "Would you like to define your dependencies" " (require) interactively?"
        if self.confirm(question, True):
            requirements = self._format_requirements(
                self._determine_requirements(self.option("dependency"))
            )

        dev_requirements = {}

        question = "Would you like to define your dev dependencies" " (require-dev) interactively"
        if self.confirm(question, True):
            dev_requirements = self._format_requirements(
                self._determine_requirements(self.option("dev-dependency"))
            )

        layout_ = layout("standard")(
            name,
            version,
            description=description,
            author=authors[0] if authors else None,
            license=license,
            python=python,
            dependencies=requirements,
            dev_dependencies=dev_requirements,
        )

        content = layout_.generate_poetry_content()
        if self.input.is_interactive():
            self.line("<info>Generated file</info>")
            self.line(["", content, ""])

        if not self.confirm("Do you confirm generation?", True):
            self.line("<error>Command aborted</error>")

            return 1

        with (Path.cwd() / "pyproject.toml").open("w") as f:
            f.write(content)

    def _determine_requirements(
        self, requires, allow_prereleases=False  # type: List[str]  # type: bool
    ):  # type: (...) -> List[str]
        if not requires:
            requires = []

            package = self.ask("Search for package:")
            while package is not None:
                matches = self._get_pool().search(package)

                if not matches:
                    self.line("<error>Unable to find package</error>")
                    package = False
                else:
                    choices = []

                    for found_package in matches:
                        choices.append(found_package.pretty_name)

                    self.line(
                        "Found <info>{}</info> packages matching <info>{}</info>".format(
                            len(matches), package
                        )
                    )

                    package = self.choice(
                        "\nEnter package # to add, or the complete package name if it is not listed",
                        choices,
                        attempts=3,
                    )

                # no constraint yet, determine the best version automatically
                if package is not False and " " not in package:
                    question = self.create_question(
                        "Enter the version constraint to require "
                        "(or leave blank to use the latest version):"
                    )
                    question.attempts = 3
                    question.validator = lambda x: (x or "").strip() or False

                    constraint = self.ask(question)

                    if constraint is False:
                        _, constraint = self._find_best_version_for_package(package)

                        self.line(
                            "Using version <info>{}</info> for <info>{}</info>".format(
                                constraint, package
                            )
                        )

                    package += " {}".format(constraint)

                if package is not False:
                    requires.append(package)

                package = self.ask("\nSearch for a package:")

            return requires

        requires = self._parse_name_version_pairs(requires)
        result = []
        for requirement in requires:
            if "version" not in requirement:
                # determine the best version automatically
                name, version = self._find_best_version_for_package(
                    requirement["name"], allow_prereleases=allow_prereleases
                )
                requirement["version"] = version
                requirement["name"] = name

                self.line(
                    "Using version <info>{}</> for <info>{}</>".format(version, name)
                )
            else:
                # check that the specified version/constraint exists
                # before we proceed
                name, _ = self._find_best_version_for_package(
                    requirement["name"],
                    requirement["version"],
                    allow_prereleases=allow_prereleases,
                )

                requirement["name"] = name

            result.append("{} {}".format(requirement["name"], requirement["version"]))

        return result

    def _find_best_version_for_package(
        self, name, required_version=None, allow_prereleases=False
    ):  # type: (...) -> Tuple[str, str]
        from poetry.version.version_selector import VersionSelector

        selector = VersionSelector(self._get_pool())
        package = selector.find_best_candidate(
            name, required_version, allow_prereleases=allow_prereleases
        )

        if not package:
            # TODO: find similar
            raise ValueError(
                "Could not find a matching version of package {}".format(name)
            )

        return (package.pretty_name, selector.find_recommended_require_version(package))

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

        for i in range(len(pairs)):
            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})

        return result

    def _format_requirements(self, requirements):  # type: (List[str]) -> dict
        requires = {}
        requirements = self._parse_name_version_pairs(requirements)
        for requirement in requirements:
            requires[requirement["name"]] = requirement["version"]

        return requires

    def _validate_author(self, author, default):
        from poetry.packages.package import AUTHOR_REGEX

        author = author or default

        if author in ["n", "no"]:
            return

        m = AUTHOR_REGEX.match(author)
        if not m:
            raise ValueError(
                "Invalid author string. Must be in the format: "
                "John Smith <*****@*****.**>"
            )

        return author

    def _validate_license(self, license):
        from poetry.spdx import license_by_id

        if license:
            license_by_id(license)

        return license

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

        if isinstance(self, VenvCommand):
            return self.poetry.pool

        if self._pool is None:
            self._pool = Pool()
            self._pool.add_repository(PyPiRepository())

        return self._pool
Exemplo n.º 9
0
Arquivo: add.py Projeto: tony/poetry
    def handle(self) -> Optional[int]:
        from poetry.factory import Factory
        from poetry.repositories import Pool

        name = self.argument("name")
        url = self.argument("url")
        is_default = self.option("default")
        is_secondary = self.option("secondary")

        if is_default and is_secondary:
            self.line_error(
                "Cannot configure a source as both <c1>default</c1> and"
                " <c1>secondary</c1>.")
            return 1

        new_source: Optional[Source] = Source(name=name,
                                              url=url,
                                              default=is_default,
                                              secondary=is_secondary)
        existing_sources = self.poetry.get_sources()

        sources = AoT([])

        for source in existing_sources:
            if source == new_source:
                self.line(
                    f"Source with name <c1>{name}</c1> already exists. Skipping"
                    " addition.")
                return 0
            elif source.default and is_default:
                self.line_error(
                    f"<error>Source with name <c1>{source.name}</c1> is already set to"
                    " default. Only one default source can be configured at a"
                    " time.</error>")
                return 1

            if new_source and source.name == name:
                self.line(
                    f"Source with name <c1>{name}</c1> already exists. Updating."
                )
                source = new_source
                new_source = None

            sources.append(self.source_to_table(source))

        if new_source is not None:
            self.line(f"Adding source with name <c1>{name}</c1>.")
            sources.append(self.source_to_table(new_source))

        # ensure new source is valid. eg: invalid name etc.
        self.poetry._pool = Pool()
        try:
            Factory.configure_sources(self.poetry, sources, self.poetry.config,
                                      NullIO())
            self.poetry.pool.repository(name)
        except ValueError as e:
            self.line_error(
                f"<error>Failed to validate addition of <c1>{name}</c1>: {e}</error>"
            )
            return 1

        self.poetry.pyproject.poetry_config["source"] = sources
        self.poetry.pyproject.save()

        return 0
Exemplo n.º 10
0
    def _do_install(self, local_repo: Repository) -> int:
        from poetry.puzzle import Solver

        locked_repository = Repository()
        if self._update:
            if self._locker.is_locked() and not self._lock:
                locked_repository = self._locker.locked_repository(True)

                # If no packages have been whitelisted (The ones we want to update),
                # we whitelist every package in the lock file.
                if not self._whitelist:
                    for pkg in locked_repository.packages:
                        self._whitelist.append(pkg.name)

            # Checking extras
            for extra in self._extras:
                if extra not in self._package.extras:
                    raise ValueError(f"Extra [{extra}] is not specified.")

            self._io.write_line("<info>Updating dependencies</>")
            solver = Solver(
                self._package,
                self._pool,
                self._installed_repository,
                locked_repository,
                self._io,
                remove_untracked=self._remove_untracked,
            )

            ops = solver.solve(use_latest=self._whitelist)
        else:
            self._io.write_line(
                "<info>Installing dependencies from lock file</>")

            locked_repository = self._locker.locked_repository(True)

            if not self._locker.is_fresh():
                self._io.write_line(
                    "<warning>"
                    "Warning: The lock file is not up to date with "
                    "the latest changes in pyproject.toml. "
                    "You may be getting outdated dependencies. "
                    "Run update to update them."
                    "</warning>")

            for extra in self._extras:
                if extra not in self._locker.lock_data.get("extras", {}):
                    raise ValueError(f"Extra [{extra}] is not specified.")

            # If we are installing from lock
            # Filter the operations by comparing it with what is
            # currently installed
            ops = self._get_operations_from_lock(locked_repository)

        self._populate_local_repo(local_repo, ops)

        if self._update:
            self._write_lock_file(local_repo)

            if self._lock:
                # If we are only in lock mode, no need to go any further
                return 0

        if self._without_groups or self._with_groups or self._only_groups:
            if self._with_groups:
                # Default dependencies and opted-in optional dependencies
                root = self._package.with_dependency_groups(self._with_groups)
            elif self._without_groups:
                # Default dependencies without selected groups
                root = self._package.without_dependency_groups(
                    self._without_groups)
            else:
                # Only selected groups
                root = self._package.with_dependency_groups(self._only_groups,
                                                            only=True)
        else:
            root = self._package.without_optional_dependency_groups()

        if self._io.is_verbose():
            self._io.write_line("")
            self._io.write_line(
                "<info>Finding the necessary packages for the current system</>"
            )

        # We resolve again by only using the lock file
        pool = Pool(ignore_repository_names=True)

        # Making a new repo containing the packages
        # newly resolved and the ones from the current lock file
        repo = Repository()
        for package in local_repo.packages + locked_repository.packages:
            if not repo.has_package(package):
                repo.add_package(package)

        pool.add_repository(repo)

        solver = Solver(
            root,
            pool,
            self._installed_repository,
            locked_repository,
            NullIO(),
            remove_untracked=self._remove_untracked,
        )
        # Everything is resolved at this point, so we no longer need
        # to load deferred dependencies (i.e. VCS, URL and path dependencies)
        solver.provider.load_deferred(False)

        with solver.use_environment(self._env):
            ops = solver.solve(use_latest=self._whitelist)

        # We need to filter operations so that packages
        # not compatible with the current system,
        # or optional and not requested, are dropped
        self._filter_operations(ops, local_repo)

        # Execute operations
        return self._execute(ops)
Exemplo n.º 11
0
    def _do_install(self, local_repo):  # type: (Repository) -> int
        from poetry.puzzle import Solver

        locked_repository = Repository()
        if self._update:
            if self._locker.is_locked() and not self._lock:
                locked_repository = self._locker.locked_repository(True)

                # If no packages have been whitelisted (The ones we want to update),
                # we whitelist every package in the lock file.
                if not self._whitelist:
                    for pkg in locked_repository.packages:
                        self._whitelist.append(pkg.name)

            # Checking extras
            for extra in self._extras:
                if extra not in self._package.extras:
                    raise ValueError(
                        "Extra [{}] is not specified.".format(extra))

            self._io.write_line("<info>Updating dependencies</>")
            solver = Solver(
                self._package,
                self._pool,
                self._installed_repository,
                locked_repository,
                self._io,
                remove_untracked=self._remove_untracked,
            )

            ops = solver.solve(use_latest=self._whitelist)
        else:
            self._io.write_line(
                "<info>Installing dependencies from lock file</>")

            locked_repository = self._locker.locked_repository(True)

            if not self._locker.is_fresh():
                self._io.write_line(
                    "<warning>"
                    "Warning: The lock file is not up to date with "
                    "the latest changes in pyproject.toml. "
                    "You may be getting outdated dependencies. "
                    "Run update to update them."
                    "</warning>")

            for extra in self._extras:
                if extra not in self._locker.lock_data.get("extras", {}):
                    raise ValueError(
                        "Extra [{}] is not specified.".format(extra))

            # If we are installing from lock
            # Filter the operations by comparing it with what is
            # currently installed
            ops = self._get_operations_from_lock(locked_repository)

        self._populate_local_repo(local_repo, ops)

        if self._update:
            self._write_lock_file(local_repo)

            if self._lock:
                # If we are only in lock mode, no need to go any further
                return 0

        root = self._package
        if not self.is_dev_mode():
            root = root.clone()
            del root.dev_requires[:]
        elif self.is_dev_only():
            root = root.clone()
            del root.requires[:]

        if self._io.is_verbose():
            self._io.write_line("")
            self._io.write_line(
                "<info>Finding the necessary packages for the current system</>"
            )

        # We resolve again by only using the lock file
        pool = Pool(ignore_repository_names=True)

        # Making a new repo containing the packages
        # newly resolved and the ones from the current lock file
        repo = Repository()
        for package in local_repo.packages + locked_repository.packages:
            if not repo.has_package(package):
                repo.add_package(package)

        pool.add_repository(repo)

        # We whitelist all packages to be sure
        # that the latest ones are picked up
        whitelist = []
        for pkg in locked_repository.packages:
            whitelist.append(pkg.name)

        solver = Solver(
            root,
            pool,
            self._installed_repository,
            locked_repository,
            NullIO(),
            remove_untracked=self._remove_untracked,
        )

        with solver.use_environment(self._env):
            ops = solver.solve(use_latest=whitelist)

        # We need to filter operations so that packages
        # not compatible with the current system,
        # or optional and not requested, are dropped
        self._filter_operations(ops, local_repo)

        # Execute operations
        return self._execute(ops)
Exemplo n.º 12
0
class InitCommand(Command):

    name = "init"
    description = (
        "Creates a basic <comment>pyproject.toml</> file in the current directory."
    )

    options = [
        option("name", None, "Name of the package.", flag=False),
        option("description", None, "Description of the package.", flag=False),
        option("author", None, "Author name of the package.", flag=False),
        option(
            "dependency",
            None,
            "Package to require, with an optional version constraint, "
            "e.g. requests:^2.10.0 or requests=2.11.1.",
            flag=False,
            multiple=True,
        ),
        option(
            "dev-dependency",
            None,
            "Package to require for development, with an optional version constraint, "
            "e.g. requests:^2.10.0 or requests=2.11.1.",
            flag=False,
            multiple=True,
        ),
        option("license", "l", "License of the package.", flag=False),
    ]

    help = """\
The <info>init</info> command creates a basic <comment>pyproject.toml</> file in the current directory.
"""

    def __init__(self):
        super(InitCommand, self).__init__()

        self._pool = None

    def handle(self):
        from poetry.layouts import layout
        from poetry.utils._compat import Path
        from poetry.utils.env import EnvManager
        from poetry.vcs.git import GitConfig

        if (Path.cwd() / "pyproject.toml").exists():
            self.line("<error>A pyproject.toml file already exists.</error>")
            return 1

        vcs_config = GitConfig()

        self.line("")
        self.line(
            "This command will guide you through creating your <info>pyproject.toml</> config."
        )
        self.line("")

        name = self.option("name")
        if not name:
            name = Path.cwd().name.lower()

            question = self.create_question(
                "Package name [<comment>{}</comment>]: ".format(name),
                default=name)
            name = self.ask(question)

        version = "0.1.0"
        question = self.create_question(
            "Version [<comment>{}</comment>]: ".format(version),
            default=version)
        version = self.ask(question)

        description = self.option("description") or ""
        question = self.create_question(
            "Description [<comment>{}</comment>]: ".format(description),
            default=description,
        )
        description = self.ask(question)

        author = self.option("author")
        if not author and vcs_config and vcs_config.get("user.name"):
            author = vcs_config["user.name"]
            author_email = vcs_config.get("user.email")
            if author_email:
                author += " <{}>".format(author_email)

        question = self.create_question(
            "Author [<comment>{}</comment>, n to skip]: ".format(author),
            default=author)
        question.set_validator(lambda v: self._validate_author(v, author))
        author = self.ask(question)

        if not author:
            authors = []
        else:
            authors = [author]

        license = self.option("license") or ""

        question = self.create_question(
            "License [<comment>{}</comment>]: ".format(license),
            default=license)
        question.set_validator(self._validate_license)
        license = self.ask(question)

        current_env = EnvManager().get(Path.cwd())
        default_python = "^{}".format(".".join(
            str(v) for v in current_env.version_info[:2]))
        question = self.create_question(
            "Compatible Python versions [<comment>{}</comment>]: ".format(
                default_python),
            default=default_python,
        )
        python = self.ask(question)

        self.line("")

        requirements = {}

        question = "Would you like to define your main dependencies interactively?"
        help_message = (
            "You can specify a package in the following forms:\n"
            "  - A single name (<b>requests</b>)\n"
            "  - A name and a constraint (<b>requests ^2.23.0</b>)\n"
            "  - A git url (<b>https://github.com/sdispater/poetry.git</b>)\n"
            "  - A git url with a revision (<b>https://github.com/sdispater/poetry.git@develop</b>)\n"
            "  - A file path (<b>../my-package/my-package.whl</b>)\n"
            "  - A directory (<b>../my-package/</b>)\n"
            "  - An url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)\n"
        )
        help_displayed = False
        if self.confirm(question, True):
            self.line(help_message)
            help_displayed = True
            requirements = self._format_requirements(
                self._determine_requirements(self.option("dependency")))
            self.line("")

        dev_requirements = {}

        question = ("Would you like to define your dev dependencies"
                    " (require-dev) interactively")
        if self.confirm(question, True):
            if not help_displayed:
                self.line(help_message)

            dev_requirements = self._format_requirements(
                self._determine_requirements(self.option("dev-dependency")))
            self.line("")

        layout_ = layout("standard")(
            name,
            version,
            description=description,
            author=authors[0] if authors else None,
            license=license,
            python=python,
            dependencies=requirements,
            dev_dependencies=dev_requirements,
        )

        content = layout_.generate_poetry_content()
        if self.io.is_interactive():
            self.line("<info>Generated file</info>")
            self.line("")
            self.line(content)
            self.line("")

        if not self.confirm("Do you confirm generation?", True):
            self.line("<error>Command aborted</error>")

            return 1

        with (Path.cwd() / "pyproject.toml").open("w", encoding="utf-8") as f:
            f.write(content)

    def _determine_requirements(
        self,
        requires,
        allow_prereleases=False
    ):  # type: (List[str], bool) -> List[Dict[str, str]]
        if not requires:
            requires = []

            package = self.ask(
                "Search for package to add (or leave blank to continue):")
            while package is not None:
                constraint = self._parse_requirements([package])[0]
                if ("git" in constraint or "url" in constraint
                        or "path" in constraint or "version" in constraint):
                    self.line("Adding <info>{}</info>".format(package))
                    requires.append(constraint)
                    package = self.ask("\nAdd a package:")
                    continue

                matches = self._get_pool().search(constraint["name"])

                if not matches:
                    self.line("<error>Unable to find package</error>")
                    package = False
                else:
                    choices = []
                    matches_names = [p.name for p in matches]
                    exact_match = constraint["name"] in matches_names
                    if exact_match:
                        choices.append(matches[matches_names.index(
                            constraint["name"])].pretty_name)

                    for found_package in matches:
                        if len(choices) >= 10:
                            break

                        if found_package.name.lower(
                        ) == constraint["name"].lower():
                            continue

                        choices.append(found_package.pretty_name)

                    self.line(
                        "Found <info>{}</info> packages matching <info>{}</info>"
                        .format(len(matches), package))

                    package = self.choice(
                        "\nEnter package # to add, or the complete package name if it is not listed",
                        choices,
                        attempts=3,
                    )

                # no constraint yet, determine the best version automatically
                if package is not False and "version" not in constraint:
                    question = self.create_question(
                        "Enter the version constraint to require "
                        "(or leave blank to use the latest version):")
                    question.attempts = 3
                    question.validator = lambda x: (x or "").strip() or False

                    package_constraint = self.ask(question)

                    if package_constraint is None:
                        _, package_constraint = self._find_best_version_for_package(
                            package)

                        self.line(
                            "Using version <info>{}</info> for <info>{}</info>"
                            .format(package_constraint, package))

                    constraint["version"] = package_constraint

                if package is not False:
                    requires.append(constraint)

                package = self.ask("\nAdd a package:")

            return requires

        requires = self._parse_requirements(requires)
        result = []
        for requirement in requires:
            if "git" in requirement or "url" in requirement or "path" in requirement:
                result.append(requirement)
                continue
            elif "version" not in requirement:
                # determine the best version automatically
                name, version = self._find_best_version_for_package(
                    requirement["name"], allow_prereleases=allow_prereleases)
                requirement["version"] = version
                requirement["name"] = name

                self.line("Using version <info>{}</> for <info>{}</>".format(
                    version, name))
            else:
                # check that the specified version/constraint exists
                # before we proceed
                name, _ = self._find_best_version_for_package(
                    requirement["name"],
                    requirement["version"],
                    allow_prereleases=allow_prereleases,
                )

                requirement["name"] = name

            result.append(requirement)

        return result

    def _find_best_version_for_package(
            self,
            name,
            required_version=None,
            allow_prereleases=False):  # type: (...) -> Tuple[str, str]
        from poetry.version.version_selector import VersionSelector

        selector = VersionSelector(self._get_pool())
        package = selector.find_best_candidate(
            name, required_version, allow_prereleases=allow_prereleases)

        if not package:
            # TODO: find similar
            raise ValueError(
                "Could not find a matching version of package {}".format(name))

        return package.pretty_name, selector.find_recommended_require_version(
            package)

    def _parse_requirements(
            self, requirements):  # type: (List[str]) -> List[Dict[str, str]]
        from poetry.puzzle.provider import Provider

        result = []

        try:
            cwd = self.poetry.file.parent
        except RuntimeError:
            cwd = Path.cwd()

        for requirement in requirements:
            requirement = requirement.strip()
            extras = []
            extras_m = re.search(r"\[([\w\d,-_]+)\]$", requirement)
            if extras_m:
                extras = [e.strip() for e in extras_m.group(1).split(",")]
                requirement, _ = requirement.split("[")

            url_parsed = urlparse.urlparse(requirement)
            if url_parsed.scheme and url_parsed.netloc:
                # Url
                if url_parsed.scheme in ["git+https", "git+ssh"]:
                    url = requirement.lstrip("git+")
                    rev = None
                    if "@" in url:
                        url, rev = url.split("@")

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

                    if extras:
                        pair["extras"] = extras

                    package = Provider.get_package_from_vcs(
                        "git", url, reference=pair.get("rev"))
                    pair["name"] = package.name
                    result.append(pair)

                    continue
                elif url_parsed.scheme in ["http", "https"]:
                    package = Provider.get_package_from_url(requirement)

                    pair = OrderedDict([("name", package.name),
                                        ("url", package.source_url)])
                    if extras:
                        pair["extras"] = extras

                    result.append(pair)
                    continue
            elif (os.path.sep in requirement or "/"
                  in requirement) and cwd.joinpath(requirement).exists():
                path = cwd.joinpath(requirement)
                if path.is_file():
                    package = Provider.get_package_from_file(path.resolve())
                else:
                    package = Provider.get_package_from_directory(path)

                result.append(
                    OrderedDict([
                        ("name", package.name),
                        ("path", path.relative_to(cwd).as_posix()),
                    ] + ([("extras", extras)] if extras else [])))

                continue

            pair = re.sub("^([^@=: ]+)(?:@|==|(?<![<>~!])=|:| )(.*)$",
                          "\\1 \\2", requirement)
            pair = pair.strip()

            require = OrderedDict()
            if " " in pair:
                name, version = pair.split(" ", 2)
                require["name"] = name
                if version != "latest":
                    require["version"] = version
            else:
                m = re.match("^([^><=!: ]+)((?:>=|<=|>|<|!=|~=|~|\^).*)$",
                             requirement.strip())
                if m:
                    name, constraint = m.group(1), m.group(2)
                    extras_m = re.search(r"\[([\w\d,-_]+)\]$", name)
                    if extras_m:
                        extras = [
                            e.strip() for e in extras_m.group(1).split(",")
                        ]
                        name, _ = name.split("[")

                    require["name"] = name
                    require["version"] = constraint
                else:
                    extras_m = re.search(r"\[([\w\d,-_]+)\]$", pair)
                    if extras_m:
                        extras = [
                            e.strip() for e in extras_m.group(1).split(",")
                        ]
                        pair, _ = pair.split("[")

                    require["name"] = pair

            if extras:
                require["extras"] = extras

            result.append(require)

        return result

    def _format_requirements(
        self, requirements
    ):  # type: (List[Dict[str, str]]) -> Dict[str, Union[str, Dict[str, str]]]
        requires = {}
        for requirement in requirements:
            name = requirement.pop("name")
            if "version" in requirement and len(requirement) == 1:
                constraint = requirement["version"]
            else:
                constraint = inline_table()
                constraint.trivia.trail = "\n"
                constraint.update(requirement)

            requires[name] = constraint

        return requires

    def _validate_author(self, author, default):
        from poetry.packages.package import AUTHOR_REGEX

        author = author or default

        if author in ["n", "no"]:
            return

        m = AUTHOR_REGEX.match(author)
        if not m:
            raise ValueError("Invalid author string. Must be in the format: "
                             "John Smith <*****@*****.**>")

        return author

    def _validate_license(self, license):
        from poetry.spdx import license_by_id

        if license:
            license_by_id(license)

        return license

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

        if isinstance(self, EnvCommand):
            return self.poetry.pool

        if self._pool is None:
            self._pool = Pool()
            self._pool.add_repository(PyPiRepository())

        return self._pool
Exemplo n.º 13
0
def pool(repo: TestRepository) -> Pool:
    pool = Pool()
    pool.add_repository(repo)

    return pool
Exemplo n.º 14
0
def test_repository_with_normal_default_and_secondary_repositories():
    secondary = LegacyRepository("secondary", "https://secondary.com")
    default = LegacyRepository("default", "https://default.com")
    repo1 = LegacyRepository("foo", "https://foo.bar")
    repo2 = LegacyRepository("bar", "https://bar.baz")

    pool = Pool()
    pool.add_repository(repo1)
    pool.add_repository(secondary, secondary=True)
    pool.add_repository(repo2)
    pool.add_repository(default, default=True)

    assert pool.repository("secondary") is secondary
    assert pool.repository("default") is default
    assert pool.repository("foo") is repo1
    assert pool.repository("bar") is repo2
    assert pool.has_default()
Exemplo n.º 15
0
def test_repository_from_secondary_pool():
    repo = LegacyRepository("foo", "https://foo.bar")
    pool = Pool()
    pool.add_repository(repo, secondary=True)

    assert pool.repository("foo") is repo
Exemplo n.º 16
0
class InitCommand(Command):
    """
    Creates a basic <comment>pyproject.toml</> file in the current directory.

    init
        {--name= : Name of the package}
        {--description= : Description of the package}
        {--author= : Author name of the package}
        {--dependency=* : Package to require with an optional version constraint,
                          e.g. requests:^2.10.0 or requests=2.11.1}
        {--dev-dependency=* : Package to require for development with an optional version constraint,
                              e.g. requests:^2.10.0 or requests=2.11.1}
        {--l|license= : License of the package}
    """

    help = """\
The <info>init</info> command creates a basic <comment>pyproject.toml</> file in the current directory.
"""

    def __init__(self):
        super(InitCommand, self).__init__()

        self._pool = None

    def handle(self):
        from poetry.layouts import layout
        from poetry.utils._compat import Path
        from poetry.vcs.git import GitConfig

        if (Path.cwd() / "pyproject.toml").exists():
            self.error("A pyproject.toml file already exists.")
            return 1

        vcs_config = GitConfig()

        self.line([
            "",
            "This command will guide you through creating your <info>poetry.toml</> config.",
            "",
        ])

        name = self.option("name")
        if not name:
            name = Path.cwd().name.lower()

            question = self.create_question(
                "Package name [<comment>{}</comment>]: ".format(name),
                default=name)
            name = self.ask(question)

        version = "0.1.0"
        question = self.create_question(
            "Version [<comment>{}</comment>]: ".format(version),
            default=version)
        version = self.ask(question)

        description = self.option("description") or ""
        question = self.create_question(
            "Description [<comment>{}</comment>]: ".format(description),
            default=description,
        )
        description = self.ask(question)

        author = self.option("author")
        if not author and vcs_config and vcs_config.get("user.name"):
            author = vcs_config["user.name"]
            author_email = vcs_config.get("user.email")
            if author_email:
                author += " <{}>".format(author_email)

        question = self.create_question(
            "Author [<comment>{}</comment>, n to skip]: ".format(author),
            default=author)
        question.validator = lambda v: self._validate_author(v, author)
        author = self.ask(question)

        if not author:
            authors = []
        else:
            authors = [author]

        license = self.option("license") or ""

        question = self.create_question(
            "License [<comment>{}</comment>]: ".format(license),
            default=license)
        question.validator = self._validate_license
        license = self.ask(question)

        question = self.create_question("Compatible Python versions [*]: ",
                                        default="*")
        python = self.ask(question)

        self.line("")

        requirements = {}

        question = "Would you like to define your dependencies" " (require) interactively?"
        if self.confirm(question, True):
            requirements = self._format_requirements(
                self._determine_requirements(self.option("dependency")))

        dev_requirements = {}

        question = "Would you like to define your dev dependencies" " (require-dev) interactively"
        if self.confirm(question, True):
            dev_requirements = self._format_requirements(
                self._determine_requirements(self.option("dev-dependency")))

        layout_ = layout("standard")(
            name,
            version,
            description=description,
            author=authors[0] if authors else None,
            license=license,
            python=python,
            dependencies=requirements,
            dev_dependencies=dev_requirements,
        )

        content = layout_.generate_poetry_content()
        if self.input.is_interactive():
            self.line("<info>Generated file</info>")
            self.line(["", content, ""])

        if not self.confirm("Do you confirm generation?", True):
            self.line("<error>Command aborted</error>")

            return 1

        with (Path.cwd() / "pyproject.toml").open("w") as f:
            f.write(content)

    def _determine_requirements(
        self,
        requires,
        allow_prereleases=False  # type: List[str]  # type: bool
    ):  # type: (...) -> List[str]
        if not requires:
            requires = []

            package = self.ask("Search for package:")
            while package is not None:
                matches = self._get_pool().search(package)

                if not matches:
                    self.line("<error>Unable to find package</error>")
                    package = False
                else:
                    choices = []

                    for found_package in matches:
                        choices.append(found_package.pretty_name)

                    self.line(
                        "Found <info>{}</info> packages matching <info>{}</info>"
                        .format(len(matches), package))

                    package = self.choice(
                        "\nEnter package # to add, or the complete package name if it is not listed",
                        choices,
                        attempts=3,
                    )

                # no constraint yet, determine the best version automatically
                if package is not False and " " not in package:
                    question = self.create_question(
                        "Enter the version constraint to require "
                        "(or leave blank to use the latest version):")
                    question.attempts = 3
                    question.validator = lambda x: (x or "").strip() or False

                    constraint = self.ask(question)

                    if constraint is False:
                        _, constraint = self._find_best_version_for_package(
                            package)

                        self.line(
                            "Using version <info>{}</info> for <info>{}</info>"
                            .format(constraint, package))

                    package += " {}".format(constraint)

                if package is not False:
                    requires.append(package)

                package = self.ask("\nSearch for a package:")

            return requires

        requires = self._parse_name_version_pairs(requires)
        result = []
        for requirement in requires:
            if "version" not in requirement:
                # determine the best version automatically
                name, version = self._find_best_version_for_package(
                    requirement["name"], allow_prereleases=allow_prereleases)
                requirement["version"] = version
                requirement["name"] = name

                self.line("Using version <info>{}</> for <info>{}</>".format(
                    version, name))
            else:
                # check that the specified version/constraint exists
                # before we proceed
                name, _ = self._find_best_version_for_package(
                    requirement["name"],
                    requirement["version"],
                    allow_prereleases=allow_prereleases,
                )

                requirement["name"] = name

            result.append("{} {}".format(requirement["name"],
                                         requirement["version"]))

        return result

    def _find_best_version_for_package(
            self,
            name,
            required_version=None,
            allow_prereleases=False):  # type: (...) -> Tuple[str, str]
        from poetry.version.version_selector import VersionSelector

        selector = VersionSelector(self._get_pool())
        package = selector.find_best_candidate(
            name, required_version, allow_prereleases=allow_prereleases)

        if not package:
            # TODO: find similar
            raise ValueError(
                "Could not find a matching version of package {}".format(name))

        return (package.pretty_name,
                selector.find_recommended_require_version(package))

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

        for i in range(len(pairs)):
            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})

        return result

    def _format_requirements(self, requirements):  # type: (List[str]) -> dict
        requires = {}
        requirements = self._parse_name_version_pairs(requirements)
        for requirement in requirements:
            requires[requirement["name"]] = requirement["version"]

        return requires

    def _validate_author(self, author, default):
        from poetry.packages.package import AUTHOR_REGEX

        author = author or default

        if author in ["n", "no"]:
            return

        m = AUTHOR_REGEX.match(author)
        if not m:
            raise ValueError("Invalid author string. Must be in the format: "
                             "John Smith <*****@*****.**>")

        return author

    def _validate_license(self, license):
        from poetry.spdx import license_by_id

        license_by_id(license)

        return license

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

        if isinstance(self, VenvCommand):
            return self.poetry.pool

        if self._pool is None:
            self._pool = Pool()
            self._pool.add_repository(PyPiRepository())

        return self._pool
Exemplo n.º 17
0
    def _factory(
        name: str | None = None,
        dependencies: dict[str, str] | None = None,
        dev_dependencies: dict[str, str] | None = None,
        pyproject_content: str | None = None,
        poetry_lock_content: str | None = None,
        install_deps: bool = True,
        source: Path | None = None,
        locker_config: dict[str, Any] | None = None,
    ) -> Poetry:
        project_dir = workspace / f"poetry-fixture-{name}"
        dependencies = dependencies or {}
        dev_dependencies = dev_dependencies or {}

        if pyproject_content or source:
            if source:
                project_dir.parent.mkdir(parents=True, exist_ok=True)
                shutil.copytree(source, project_dir)
            else:
                project_dir.mkdir(parents=True, exist_ok=True)

            if pyproject_content:
                with project_dir.joinpath("pyproject.toml").open(
                        "w", encoding="utf-8") as f:
                    f.write(pyproject_content)
        else:
            layout("src")(
                name,
                "0.1.0",
                author="PyTest Tester <*****@*****.**>",
                readme_format="md",
                python=default_python,
                dependencies=dependencies,
                dev_dependencies=dev_dependencies,
            ).create(project_dir, with_tests=False)

        if poetry_lock_content:
            lock_file = project_dir / "poetry.lock"
            lock_file.write_text(data=poetry_lock_content, encoding="utf-8")

        poetry = Factory().create_poetry(project_dir)

        locker = TestLocker(poetry.locker.lock.path, locker_config
                            or poetry.locker._local_config)
        locker.write()

        poetry.set_locker(locker)
        poetry.set_config(config)

        pool = Pool()
        pool.add_repository(repo)

        poetry.set_pool(pool)

        if install_deps:
            for deps in [dependencies, dev_dependencies]:
                for name, version in deps.items():
                    pkg = get_package(name, version)
                    repo.add_package(pkg)
                    installed.add_package(pkg)

        return poetry
Exemplo n.º 18
0
Arquivo: init.py Projeto: tony/poetry
class InitCommand(Command):
    name = "init"
    description = (
        "Creates a basic <comment>pyproject.toml</> file in the current directory."
    )

    options = [
        option("name", None, "Name of the package.", flag=False),
        option("description", None, "Description of the package.", flag=False),
        option("author", None, "Author name of the package.", flag=False),
        option("python", None, "Compatible Python versions.", flag=False),
        option(
            "dependency",
            None,
            "Package to require, with an optional version constraint, "
            "e.g. requests:^2.10.0 or requests=2.11.1.",
            flag=False,
            multiple=True,
        ),
        option(
            "dev-dependency",
            None,
            "Package to require for development, with an optional version constraint, "
            "e.g. requests:^2.10.0 or requests=2.11.1.",
            flag=False,
            multiple=True,
        ),
        option("license", "l", "License of the package.", flag=False),
    ]

    help = """\
The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the\
 current directory.
"""

    def __init__(self) -> None:
        super().__init__()

        self._pool: Optional["Pool"] = None

    def handle(self) -> int:
        from pathlib import Path

        from poetry.core.pyproject.toml import PyProjectTOML
        from poetry.core.vcs.git import GitConfig

        from poetry.layouts import layout
        from poetry.utils.env import SystemEnv

        pyproject = PyProjectTOML(Path.cwd() / "pyproject.toml")

        if pyproject.file.exists():
            if pyproject.is_poetry_project():
                self.line(
                    "<error>A pyproject.toml file with a poetry section already"
                    " exists.</error>")
                return 1

            if pyproject.data.get("build-system"):
                self.line(
                    "<error>A pyproject.toml file with a defined build-system already"
                    " exists.</error>")
                return 1

        vcs_config = GitConfig()

        if self.io.is_interactive():
            self.line("")
            self.line("This command will guide you through creating your"
                      " <info>pyproject.toml</> config.")
            self.line("")

        name = self.option("name")
        if not name:
            name = Path.cwd().name.lower()

            question = self.create_question(
                f"Package name [<comment>{name}</comment>]: ", default=name)
            name = self.ask(question)

        version = "0.1.0"
        question = self.create_question(
            f"Version [<comment>{version}</comment>]: ", default=version)
        version = self.ask(question)

        description = self.option("description") or ""
        question = self.create_question(
            f"Description [<comment>{description}</comment>]: ",
            default=description,
        )
        description = self.ask(question)

        author = self.option("author")
        if not author and vcs_config and vcs_config.get("user.name"):
            author = vcs_config["user.name"]
            author_email = vcs_config.get("user.email")
            if author_email:
                author += f" <{author_email}>"

        question = self.create_question(
            f"Author [<comment>{author}</comment>, n to skip]: ",
            default=author)
        question.set_validator(lambda v: self._validate_author(v, author))
        author = self.ask(question)

        if not author:
            authors = []
        else:
            authors = [author]

        license = self.option("license") or ""

        question = self.create_question(
            f"License [<comment>{license}</comment>]: ", default=license)
        question.set_validator(self._validate_license)
        license = self.ask(question)

        python = self.option("python")
        if not python:
            current_env = SystemEnv(Path(sys.executable))
            default_python = "^" + ".".join(
                str(v) for v in current_env.version_info[:2])
            question = self.create_question(
                f"Compatible Python versions [<comment>{default_python}</comment>]: ",
                default=default_python,
            )
            python = self.ask(question)

        if self.io.is_interactive():
            self.line("")

        requirements = {}
        if self.option("dependency"):
            requirements = self._format_requirements(
                self._determine_requirements(self.option("dependency")))

        question = "Would you like to define your main dependencies interactively?"
        help_message = """\
You can specify a package in the following forms:
  - A single name (<b>requests</b>)
  - A name and a constraint (<b>requests@^2.23.0</b>)
  - A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)
  - A git url with a revision\
 (<b>git+https://github.com/python-poetry/poetry.git#develop</b>)
  - A file path (<b>../my-package/my-package.whl</b>)
  - A directory (<b>../my-package/</b>)
  - A url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)
"""

        help_displayed = False
        if self.confirm(question, True):
            if self.io.is_interactive():
                self.line(help_message)
                help_displayed = True
            requirements.update(
                self._format_requirements(self._determine_requirements([])))
            if self.io.is_interactive():
                self.line("")

        dev_requirements: Dict[str, str] = {}
        if self.option("dev-dependency"):
            dev_requirements = self._format_requirements(
                self._determine_requirements(self.option("dev-dependency")))

        question = (
            "Would you like to define your development dependencies interactively?"
        )
        if self.confirm(question, True):
            if self.io.is_interactive() and not help_displayed:
                self.line(help_message)

            dev_requirements.update(
                self._format_requirements(self._determine_requirements([])))
            if self.io.is_interactive():
                self.line("")

        layout_ = layout("standard")(
            name,
            version,
            description=description,
            author=authors[0] if authors else None,
            license=license,
            python=python,
            dependencies=requirements,
            dev_dependencies=dev_requirements,
        )

        content = layout_.generate_poetry_content(original=pyproject)
        if self.io.is_interactive():
            self.line("<info>Generated file</info>")
            self.line("")
            self.line(content)
            self.line("")

        if not self.confirm("Do you confirm generation?", True):
            self.line("<error>Command aborted</error>")

            return 1

        with (Path.cwd() / "pyproject.toml").open("w", encoding="utf-8") as f:
            f.write(content)

        return 0

    def _generate_choice_list(self, matches: List["Package"],
                              canonicalized_name: str) -> List[str]:
        choices = []
        matches_names = [p.name for p in matches]
        exact_match = canonicalized_name in matches_names
        if exact_match:
            choices.append(
                matches[matches_names.index(canonicalized_name)].pretty_name)

        for found_package in matches:
            if len(choices) >= 10:
                break

            if found_package.name == canonicalized_name:
                continue

            choices.append(found_package.pretty_name)

        return choices

    def _determine_requirements(
        self,
        requires: List[str],
        allow_prereleases: bool = False,
        source: Optional[str] = None,
    ) -> List[Dict[str, Union[str, List[str]]]]:
        if not requires:
            requires = []

            package = self.ask(
                "Search for package to add (or leave blank to continue):")
            while package:
                constraint = self._parse_requirements([package])[0]
                if ("git" in constraint or "url" in constraint
                        or "path" in constraint or "version" in constraint):
                    self.line(f"Adding <info>{package}</info>")
                    requires.append(constraint)
                    package = self.ask("\nAdd a package:")
                    continue

                canonicalized_name = canonicalize_name(constraint["name"])
                matches = self._get_pool().search(canonicalized_name)
                if not matches:
                    self.line("<error>Unable to find package</error>")
                    package = False
                else:
                    choices = self._generate_choice_list(
                        matches, canonicalized_name)

                    info_string = (
                        f"Found <info>{len(matches)}</info> packages matching"
                        f" <c1>{package}</c1>")

                    if len(matches) > 10:
                        info_string += "\nShowing the first 10 matches"

                    self.line(info_string)

                    package = self.choice(
                        "\nEnter package # to add, or the complete package name if it"
                        " is not listed",
                        choices,
                        attempts=3,
                    )

                    # package selected by user, set constraint name to package name
                    if package is not False:
                        constraint["name"] = package

                # no constraint yet, determine the best version automatically
                if package is not False and "version" not in constraint:
                    question = self.create_question(
                        "Enter the version constraint to require "
                        "(or leave blank to use the latest version):")
                    question.attempts = 3
                    question.validator = lambda x: (x or "").strip() or False

                    package_constraint = self.ask(question)

                    if package_constraint is None:
                        _, package_constraint = self._find_best_version_for_package(
                            package)

                        self.line(
                            f"Using version <b>{package_constraint}</b> for"
                            f" <c1>{package}</c1>")

                    constraint["version"] = package_constraint

                if package is not False:
                    requires.append(constraint)

                if self.io.is_interactive():
                    package = self.ask("\nAdd a package:")

            return requires

        requires = self._parse_requirements(requires)
        result = []
        for requirement in requires:
            if "git" in requirement or "url" in requirement or "path" in requirement:
                result.append(requirement)
                continue
            elif "version" not in requirement:
                # determine the best version automatically
                name, version = self._find_best_version_for_package(
                    requirement["name"],
                    allow_prereleases=allow_prereleases,
                    source=source,
                )
                requirement["version"] = version
                requirement["name"] = name

                self.line(
                    f"Using version <b>{version}</b> for <c1>{name}</c1>")
            else:
                # check that the specified version/constraint exists
                # before we proceed
                name, _ = self._find_best_version_for_package(
                    requirement["name"],
                    requirement["version"],
                    allow_prereleases=allow_prereleases,
                    source=source,
                )

                requirement["name"] = name

            result.append(requirement)

        return result

    def _find_best_version_for_package(
        self,
        name: str,
        required_version: Optional[str] = None,
        allow_prereleases: bool = False,
        source: Optional[str] = None,
    ) -> Tuple[str, str]:
        from poetry.version.version_selector import VersionSelector

        selector = VersionSelector(self._get_pool())
        package = selector.find_best_candidate(
            name,
            required_version,
            allow_prereleases=allow_prereleases,
            source=source)

        if not package:
            # TODO: find similar
            raise ValueError(
                f"Could not find a matching version of package {name}")

        return package.pretty_name, selector.find_recommended_require_version(
            package)

    def _parse_requirements(self,
                            requirements: List[str]) -> List[Dict[str, Any]]:
        from poetry.core.pyproject.exceptions import PyProjectException

        from poetry.puzzle.provider import Provider

        result = []

        try:
            cwd = self.poetry.file.parent
        except (PyProjectException, RuntimeError):
            cwd = Path.cwd()

        for requirement in requirements:
            requirement = requirement.strip()
            extras = []
            extras_m = re.search(r"\[([\w\d,-_ ]+)\]$", requirement)
            if extras_m:
                extras = [e.strip() for e in extras_m.group(1).split(",")]
                requirement, _ = requirement.split("[")

            url_parsed = urllib.parse.urlparse(requirement)
            if url_parsed.scheme and url_parsed.netloc:
                # Url
                if url_parsed.scheme in ["git+https", "git+ssh"]:
                    from poetry.core.vcs.git import Git
                    from poetry.core.vcs.git import ParsedUrl

                    parsed = ParsedUrl.parse(requirement)
                    url = Git.normalize_url(requirement)

                    pair = {"name": parsed.name, "git": url.url}
                    if parsed.rev:
                        pair["rev"] = url.revision

                    if extras:
                        pair["extras"] = extras

                    package = Provider.get_package_from_vcs(
                        "git", url.url, rev=pair.get("rev"))
                    pair["name"] = package.name
                    result.append(pair)

                    continue
                elif url_parsed.scheme in ["http", "https"]:
                    package = Provider.get_package_from_url(requirement)

                    pair = {"name": package.name, "url": package.source_url}
                    if extras:
                        pair["extras"] = extras

                    result.append(pair)
                    continue
            elif (os.path.sep in requirement or "/" in requirement) and (
                    cwd.joinpath(requirement).exists()
                    or Path(requirement).expanduser().exists()
                    and Path(requirement).expanduser().is_absolute()):
                path = Path(requirement).expanduser()
                is_absolute = path.is_absolute()

                if not path.is_absolute():
                    path = cwd.joinpath(requirement)

                if path.is_file():
                    package = Provider.get_package_from_file(path.resolve())
                else:
                    package = Provider.get_package_from_directory(
                        path.resolve())

                result.append(
                    dict([
                        ("name", package.name),
                        (
                            "path",
                            path.relative_to(cwd).as_posix(
                            ) if not is_absolute else path.as_posix(),
                        ),
                    ] + ([("extras", extras)] if extras else [])))

                continue

            pair = re.sub("^([^@=: ]+)(?:@|==|(?<![<>~!])=|:| )(.*)$",
                          "\\1 \\2", requirement)
            pair = pair.strip()

            require: Dict[str, str] = {}
            if " " in pair:
                name, version = pair.split(" ", 2)
                extras_m = re.search(r"\[([\w\d,-_]+)\]$", name)
                if extras_m:
                    extras = [e.strip() for e in extras_m.group(1).split(",")]
                    name, _ = name.split("[")

                require["name"] = name
                if version != "latest":
                    require["version"] = version
            else:
                m = re.match(r"^([^><=!: ]+)((?:>=|<=|>|<|!=|~=|~|\^).*)$",
                             requirement.strip())
                if m:
                    name, constraint = m.group(1), m.group(2)
                    extras_m = re.search(r"\[([\w\d,-_]+)\]$", name)
                    if extras_m:
                        extras = [
                            e.strip() for e in extras_m.group(1).split(",")
                        ]
                        name, _ = name.split("[")

                    require["name"] = name
                    require["version"] = constraint
                else:
                    extras_m = re.search(r"\[([\w\d,-_]+)\]$", pair)
                    if extras_m:
                        extras = [
                            e.strip() for e in extras_m.group(1).split(",")
                        ]
                        pair, _ = pair.split("[")

                    require["name"] = pair

            if extras:
                require["extras"] = extras

            result.append(require)

        return result

    def _format_requirements(
        self, requirements: List[Dict[str, str]]
    ) -> Mapping[str, Union[str, Mapping[str, str]]]:
        requires = {}
        for requirement in requirements:
            name = requirement.pop("name")
            constraint: Union[str, "InlineTable"]
            if "version" in requirement and len(requirement) == 1:
                constraint = requirement["version"]
            else:
                constraint = inline_table()
                constraint.trivia.trail = "\n"
                constraint.update(requirement)

            requires[name] = constraint

        return requires

    def _validate_author(self, author: str, default: str) -> Optional[str]:
        from poetry.core.packages.package import AUTHOR_REGEX

        author = author or default

        if author in ["n", "no"]:
            return None

        m = AUTHOR_REGEX.match(author)
        if not m:
            raise ValueError("Invalid author string. Must be in the format: "
                             "John Smith <*****@*****.**>")

        return author

    def _validate_license(self, license: str) -> str:
        from poetry.core.spdx.helpers import license_by_id

        if license:
            license_by_id(license)

        return license

    def _get_pool(self) -> "Pool":
        from poetry.repositories import Pool
        from poetry.repositories.pypi_repository import PyPiRepository

        if isinstance(self, EnvCommand):
            return self.poetry.pool

        if self._pool is None:
            self._pool = Pool()
            self._pool.add_repository(PyPiRepository())

        return self._pool
Exemplo n.º 19
0
class InitCommand(Command):
    name = "init"
    description = (
        "Creates a basic <comment>pyproject.toml</> file in the current directory."
    )

    options = [
        option("name", None, "Name of the package.", flag=False),
        option("description", None, "Description of the package.", flag=False),
        option("author", None, "Author name of the package.", flag=False),
        option("python", None, "Compatible Python versions.", flag=False),
        option(
            "dependency",
            None,
            "Package to require, with an optional version constraint, "
            "e.g. requests:^2.10.0 or requests=2.11.1.",
            flag=False,
            multiple=True,
        ),
        option(
            "dev-dependency",
            None,
            "Package to require for development, with an optional version constraint, "
            "e.g. requests:^2.10.0 or requests=2.11.1.",
            flag=False,
            multiple=True,
        ),
        option("license", "l", "License of the package.", flag=False),
    ]

    help = """\
The <c1>init</c1> command creates a basic <comment>pyproject.toml</> file in the\
 current directory.
"""

    def __init__(self) -> None:
        super().__init__()

        self._pool: Pool | None = None

    def handle(self) -> int:
        from pathlib import Path

        from poetry.core.pyproject.toml import PyProjectTOML
        from poetry.core.vcs.git import GitConfig

        from poetry.layouts import layout
        from poetry.utils.env import SystemEnv

        pyproject = PyProjectTOML(Path.cwd() / "pyproject.toml")

        if pyproject.file.exists():
            if pyproject.is_poetry_project():
                self.line_error(
                    "<error>A pyproject.toml file with a poetry section already"
                    " exists.</error>")
                return 1

            if pyproject.data.get("build-system"):
                self.line_error(
                    "<error>A pyproject.toml file with a defined build-system already"
                    " exists.</error>")
                return 1

        vcs_config = GitConfig()

        if self.io.is_interactive():
            self.line("")
            self.line("This command will guide you through creating your"
                      " <info>pyproject.toml</> config.")
            self.line("")

        name = self.option("name")
        if not name:
            name = Path.cwd().name.lower()

            question = self.create_question(
                f"Package name [<comment>{name}</comment>]: ", default=name)
            name = self.ask(question)

        version = "0.1.0"
        question = self.create_question(
            f"Version [<comment>{version}</comment>]: ", default=version)
        version = self.ask(question)

        description = self.option("description")
        if not description:
            description = self.ask(
                self.create_question("Description []: ", default=""))

        author = self.option("author")
        if not author and vcs_config.get("user.name"):
            author = vcs_config["user.name"]
            author_email = vcs_config.get("user.email")
            if author_email:
                author += f" <{author_email}>"

        question = self.create_question(
            f"Author [<comment>{author}</comment>, n to skip]: ",
            default=author)
        question.set_validator(lambda v: self._validate_author(v, author))
        author = self.ask(question)

        if not author:
            authors = []
        else:
            authors = [author]

        license = self.option("license")
        if not license:
            license = self.ask(self.create_question("License []: ",
                                                    default=""))

        python = self.option("python")
        if not python:
            current_env = SystemEnv(Path(sys.executable))
            default_python = "^" + ".".join(
                str(v) for v in current_env.version_info[:2])
            question = self.create_question(
                f"Compatible Python versions [<comment>{default_python}</comment>]: ",
                default=default_python,
            )
            python = self.ask(question)

        if self.io.is_interactive():
            self.line("")

        requirements: Requirements = {}
        if self.option("dependency"):
            requirements = self._format_requirements(
                self._determine_requirements(self.option("dependency")))

        question = "Would you like to define your main dependencies interactively?"
        help_message = """\
You can specify a package in the following forms:
  - A single name (<b>requests</b>): this will search for matches on PyPI
  - A name and a constraint (<b>requests@^2.23.0</b>)
  - A git url (<b>git+https://github.com/python-poetry/poetry.git</b>)
  - A git url with a revision\
 (<b>git+https://github.com/python-poetry/poetry.git#develop</b>)
  - A file path (<b>../my-package/my-package.whl</b>)
  - A directory (<b>../my-package/</b>)
  - A url (<b>https://example.com/packages/my-package-0.1.0.tar.gz</b>)
"""

        help_displayed = False
        if self.confirm(question, True):
            if self.io.is_interactive():
                self.line(help_message)
                help_displayed = True
            requirements.update(
                self._format_requirements(self._determine_requirements([])))
            if self.io.is_interactive():
                self.line("")

        dev_requirements: Requirements = {}
        if self.option("dev-dependency"):
            dev_requirements = self._format_requirements(
                self._determine_requirements(self.option("dev-dependency")))

        question = (
            "Would you like to define your development dependencies interactively?"
        )
        if self.confirm(question, True):
            if self.io.is_interactive() and not help_displayed:
                self.line(help_message)

            dev_requirements.update(
                self._format_requirements(self._determine_requirements([])))
            if self.io.is_interactive():
                self.line("")

        layout_ = layout("standard")(
            name,
            version,
            description=description,
            author=authors[0] if authors else None,
            license=license,
            python=python,
            dependencies=requirements,
            dev_dependencies=dev_requirements,
        )

        content = layout_.generate_poetry_content()
        for section in content:
            pyproject.data.append(section, content[section])
        if self.io.is_interactive():
            self.line("<info>Generated file</info>")
            self.line("")
            self.line(pyproject.data.as_string().replace("\r\n", "\n"))
            self.line("")

        if not self.confirm("Do you confirm generation?", True):
            self.line_error("<error>Command aborted</error>")

            return 1

        pyproject.save()

        return 0

    def _generate_choice_list(self, matches: list[Package],
                              canonicalized_name: str) -> list[str]:
        choices = []
        matches_names = [p.name for p in matches]
        exact_match = canonicalized_name in matches_names
        if exact_match:
            choices.append(
                matches[matches_names.index(canonicalized_name)].pretty_name)

        for found_package in matches:
            if len(choices) >= 10:
                break

            if found_package.name == canonicalized_name:
                continue

            choices.append(found_package.pretty_name)

        return choices

    def _determine_requirements(
        self,
        requires: list[str],
        allow_prereleases: bool = False,
        source: str | None = None,
    ) -> list[dict[str, Any]]:
        if not requires:
            result = []

            question = self.create_question(
                "Package to add or search for (leave blank to skip):")
            question.set_validator(self._validate_package)

            package = self.ask(question)
            while package:
                constraint = self._parse_requirements([package])[0]
                if ("git" in constraint or "url" in constraint
                        or "path" in constraint or "version" in constraint):
                    self.line(f"Adding <info>{package}</info>")
                    result.append(constraint)
                    package = self.ask(
                        "\nAdd a package (leave blank to skip):")
                    continue

                canonicalized_name = canonicalize_name(constraint["name"])
                matches = self._get_pool().search(canonicalized_name)
                if not matches:
                    self.line_error("<error>Unable to find package</error>")
                    package = False
                else:
                    choices = self._generate_choice_list(
                        matches, canonicalized_name)

                    info_string = (
                        f"Found <info>{len(matches)}</info> packages matching"
                        f" <c1>{package}</c1>")

                    if len(matches) > 10:
                        info_string += "\nShowing the first 10 matches"

                    self.line(info_string)

                    # Default to an empty value to signal no package was selected
                    choices.append("")

                    package = self.choice(
                        "\nEnter package # to add, or the complete package name if it"
                        " is not listed",
                        choices,
                        attempts=3,
                        default=len(choices) - 1,
                    )

                    if not package:
                        self.line("<warning>No package selected</warning>")

                    # package selected by user, set constraint name to package name
                    if package:
                        constraint["name"] = package

                # no constraint yet, determine the best version automatically
                if package and "version" not in constraint:
                    question = self.create_question(
                        "Enter the version constraint to require "
                        "(or leave blank to use the latest version):")
                    question.attempts = 3
                    question.validator = lambda x: (x or "").strip() or False

                    package_constraint = self.ask(question)

                    if package_constraint is None:
                        _, package_constraint = self._find_best_version_for_package(
                            package)

                        self.line(
                            f"Using version <b>{package_constraint}</b> for"
                            f" <c1>{package}</c1>")

                    constraint["version"] = package_constraint

                if package:
                    result.append(constraint)

                if self.io.is_interactive():
                    package = self.ask(
                        "\nAdd a package (leave blank to skip):")

            return result

        result = []
        for requirement in self._parse_requirements(requires):
            if "git" in requirement or "url" in requirement or "path" in requirement:
                result.append(requirement)
                continue
            elif "version" not in requirement:
                # determine the best version automatically
                name, version = self._find_best_version_for_package(
                    requirement["name"],
                    allow_prereleases=allow_prereleases,
                    source=source,
                )
                requirement["version"] = version
                requirement["name"] = name

                self.line(
                    f"Using version <b>{version}</b> for <c1>{name}</c1>")
            else:
                # check that the specified version/constraint exists
                # before we proceed
                name, _ = self._find_best_version_for_package(
                    requirement["name"],
                    requirement["version"],
                    allow_prereleases=allow_prereleases,
                    source=source,
                )

                requirement["name"] = name

            result.append(requirement)

        return result

    def _find_best_version_for_package(
        self,
        name: str,
        required_version: str | None = None,
        allow_prereleases: bool = False,
        source: str | None = None,
    ) -> tuple[str, str]:
        from poetry.version.version_selector import VersionSelector

        selector = VersionSelector(self._get_pool())
        package = selector.find_best_candidate(
            name,
            required_version,
            allow_prereleases=allow_prereleases,
            source=source)

        if not package:
            # TODO: find similar
            raise ValueError(
                f"Could not find a matching version of package {name}")

        return package.pretty_name, selector.find_recommended_require_version(
            package)

    def _parse_requirements(self,
                            requirements: list[str]) -> list[dict[str, Any]]:
        from poetry.core.pyproject.exceptions import PyProjectException

        try:
            cwd = self.poetry.file.parent
        except (PyProjectException, RuntimeError):
            cwd = Path.cwd()

        return [
            parse_dependency_specification(
                requirement=requirement,
                env=self.env if isinstance(self, EnvCommand) else None,
                cwd=cwd,
            ) for requirement in requirements
        ]

    def _format_requirements(
            self, requirements: list[dict[str, str]]) -> Requirements:
        requires: Requirements = {}
        for requirement in requirements:
            name = requirement.pop("name")
            constraint: str | InlineTable
            if "version" in requirement and len(requirement) == 1:
                constraint = requirement["version"]
            else:
                constraint = inline_table()
                constraint.trivia.trail = "\n"
                constraint.update(requirement)

            requires[name] = constraint

        return requires

    def _validate_author(self, author: str, default: str) -> str | None:
        from poetry.core.packages.package import AUTHOR_REGEX

        author = author or default

        if author in ["n", "no"]:
            return None

        m = AUTHOR_REGEX.match(author)
        if not m:
            raise ValueError("Invalid author string. Must be in the format: "
                             "John Smith <*****@*****.**>")

        return author

    @staticmethod
    def _validate_package(package: str | None) -> str | None:
        if package and len(package.split()) > 2:
            raise ValueError("Invalid package definition.")

        return package

    def _get_pool(self) -> Pool:
        from poetry.repositories import Pool
        from poetry.repositories.pypi_repository import PyPiRepository

        if isinstance(self, EnvCommand):
            return self.poetry.pool

        if self._pool is None:
            self._pool = Pool()
            self._pool.add_repository(PyPiRepository())

        return self._pool
Exemplo n.º 20
0
def test_pool():
    pool = Pool()

    assert 0 == len(pool.repositories)
    assert not pool.has_default()
Exemplo n.º 21
0
def pool(repo):
    pool = Pool()
    pool.add_repository(repo)

    return pool
Exemplo n.º 22
0
def test_repository_no_repository():
    pool = Pool()

    with pytest.raises(ValueError):
        pool.repository("foo")
Exemplo n.º 23
0
    def _do_install(self, local_repo):
        locked_repository = Repository()
        if self._update:
            if self._locker.is_locked():
                locked_repository = self._locker.locked_repository(True)

                # If no packages have been whitelisted (The ones we want to update),
                # we whitelist every package in the lock file.
                if not self._whitelist:
                    for pkg in locked_repository.packages:
                        self._whitelist.append(pkg.name)

            # Checking extras
            for extra in self._extras:
                if extra not in self._package.extras:
                    raise ValueError("Extra [{}] is not specified.".format(extra))

            self._io.writeln("<info>Updating dependencies</>")
            solver = Solver(
                self._package,
                self._pool,
                self._installed_repository,
                locked_repository,
                self._io,
            )

            ops = solver.solve(use_latest=self._whitelist)
        else:
            self._io.writeln("<info>Installing dependencies from lock file</>")

            locked_repository = self._locker.locked_repository(True)

            if not self._locker.is_fresh():
                self._io.writeln(
                    "<warning>"
                    "Warning: The lock file is not up to date with "
                    "the latest changes in pyproject.toml. "
                    "You may be getting outdated dependencies. "
                    "Run update to update them."
                    "</warning>"
                )

            for extra in self._extras:
                if extra not in self._locker.lock_data.get("extras", {}):
                    raise ValueError("Extra [{}] is not specified.".format(extra))

            # If we are installing from lock
            # Filter the operations by comparing it with what is
            # currently installed
            ops = self._get_operations_from_lock(locked_repository)

        self._populate_local_repo(local_repo, ops, locked_repository)

        with self._package.with_python_versions(
            ".".join([str(i) for i in self._venv.version_info[:3]])
        ):
            # We resolve again by only using the lock file
            pool = Pool()

            # Making a new repo containing the packages
            # newly resolved and the ones from the current lock file
            locked_repository = self._locker.locked_repository(True)
            repo = Repository()
            for package in local_repo.packages + locked_repository.packages:
                if not repo.has_package(package):
                    repo.add_package(package)

            pool.add_repository(repo)

            # We whitelist all packages to be sure
            # that the latest ones are picked up
            whitelist = []
            for pkg in locked_repository.packages:
                whitelist.append(pkg.name)

            solver = Solver(
                self._package,
                pool,
                self._installed_repository,
                locked_repository,
                NullIO(),
            )

            ops = solver.solve(use_latest=whitelist)

        # We need to filter operations so that packages
        # not compatible with the current system,
        # or optional and not requested, are dropped
        self._filter_operations(ops, local_repo)

        self._io.new_line()

        # Execute operations
        actual_ops = [op for op in ops if not op.skipped]
        if not actual_ops and (self._execute_operations or self._dry_run):
            self._io.writeln("Nothing to install or update")

        if actual_ops and (self._execute_operations or self._dry_run):
            installs = []
            updates = []
            uninstalls = []
            skipped = []
            for op in ops:
                if op.skipped:
                    skipped.append(op)
                    continue

                if op.job_type == "install":
                    installs.append(
                        "{}:{}".format(
                            op.package.pretty_name, op.package.full_pretty_version
                        )
                    )
                elif op.job_type == "update":
                    updates.append(
                        "{}:{}".format(
                            op.target_package.pretty_name,
                            op.target_package.full_pretty_version,
                        )
                    )
                elif op.job_type == "uninstall":
                    uninstalls.append(op.package.pretty_name)

            self._io.new_line()
            self._io.writeln(
                "Package operations: "
                "<info>{}</> install{}, "
                "<info>{}</> update{}, "
                "<info>{}</> removal{}"
                "{}".format(
                    len(installs),
                    "" if len(installs) == 1 else "s",
                    len(updates),
                    "" if len(updates) == 1 else "s",
                    len(uninstalls),
                    "" if len(uninstalls) == 1 else "s",
                    ", <info>{}</> skipped".format(len(skipped))
                    if skipped and self.is_verbose()
                    else "",
                )
            )

        # Writing lock before installing
        if self._update and self._write_lock:
            updated_lock = self._locker.set_lock_data(
                self._package, local_repo.packages
            )

            if updated_lock:
                self._io.writeln("")
                self._io.writeln("<info>Writing lock file</>")

        self._io.writeln("")
        for op in ops:
            self._execute(op)
Exemplo n.º 24
0
def patches(mocker: "MockerFixture", source_dir: Path,
            repo: "TestRepository") -> None:
    mocker.patch("pathlib.Path.cwd", return_value=source_dir)
    mocker.patch("poetry.console.commands.init.InitCommand._get_pool",
                 return_value=Pool([repo]))
Exemplo n.º 25
0
def test_pool_raises_package_not_found_when_no_package_is_found():
    pool = Pool()
    pool.add_repository(Repository("repo"))

    with pytest.raises(PackageNotFound):
        pool.package("foo", "1.0.0")
Exemplo n.º 26
0
def patches(mocker, source_dir, repo):
    mocker.patch("pathlib.Path.cwd", return_value=source_dir)
    mocker.patch("poetry.console.commands.init.InitCommand._get_pool",
                 return_value=Pool([repo]))
Exemplo n.º 27
0
class InitCommand(Command):
    """
    Creates a basic <comment>pyproject.toml</> file in the current directory.

    init
        {--name= : Name of the package}
        {--description= : Description of the package}
        {--author= : Author name of the package}
        {--dependency=* : Package to require with an optional version constraint,
                          e.g. requests:^2.10.0 or requests=2.11.1}
        {--dev-dependency=* : Package to require for development with an optional version constraint,
                              e.g. requests:^2.10.0 or requests=2.11.1}
        {--l|license= : License of the package}
    """

    help = """\
The <info>init</info> command creates a basic <comment>pyproject.toml</> file in the current directory.  
"""

    def __init__(self):
        super(InitCommand, self).__init__()

        self._pool = None

    def handle(self):
        from poetry.layouts import layout
        from poetry.utils._compat import Path
        from poetry.vcs.git import GitConfig

        if (Path.cwd() / 'pyproject.toml').exists():
            self.error('A pyproject.toml file already exists.')
            return 1

        vcs_config = GitConfig()

        self.line([
            '',
            'This command will guide you through creating your <info>poetry.toml</> config.',
            ''
        ])

        name = self.option('name')
        if not name:
            name = Path.cwd().name.lower()

            question = self.create_question(
                'Package name [<comment>{}</comment>]: '.format(name),
                default=name
            )
            name = self.ask(question)

        version = '0.1.0'
        question = self.create_question(
            'Version [<comment>{}</comment>]: '.format(version),
            default=version
        )
        version = self.ask(question)

        description = self.option('description') or ''
        question = self.create_question(
            'Description [<comment>{}</comment>]: '.format(description),
            default=description
        )
        description = self.ask(question)

        author = self.option('author')
        if not author and vcs_config and vcs_config.get('user.name'):
            author = vcs_config['user.name']
            author_email = vcs_config.get('user.email')
            if author_email:
                author += ' <{}>'.format(author_email)

        question = self.create_question(
            'Author [<comment>{}</comment>, n to skip]: '.format(author),
            default=author
        )
        question.validator = lambda v: self._validate_author(v, author)
        author = self.ask(question)

        if not author:
            authors = []
        else:
            authors = [author]

        license = self.option('license') or ''

        question = self.create_question(
            'License [<comment>{}</comment>]: '.format(license),
            default=license
        )

        license = self.ask(question)

        question = self.create_question(
            'Compatible Python versions [*]: ',
            default='*'
        )
        python = self.ask(question)

        self.line('')

        requirements = []

        question = 'Would you like to define your dependencies' \
                   ' (require) interactively?'
        if self.confirm(question, True):
            requirements = self._format_requirements(
                self._determine_requirements(self.option('dependency'))
            )

        dev_requirements = []

        question = 'Would you like to define your dev dependencies' \
                   ' (require-dev) interactively'
        if self.confirm(question, True):
            dev_requirements = self._format_requirements(
                self._determine_requirements(self.option('dev-dependency'))
            )

        layout_ = layout('standard')(
            name,
            version,
            description=description,
            author=authors[0] if authors else None,
            license=license,
            python=python,
            dependencies=requirements,
            dev_dependencies=dev_requirements
        )

        content = layout_.generate_poetry_content()
        if self.input.is_interactive():
            self.line('<info>Generated file</info>')
            self.line(['', content, ''])

        if not self.confirm('Do you confirm generation?', True):
            self.line('<error>Command aborted</error>')

            return 1

        with (Path.cwd() / 'pyproject.toml').open('w') as f:
            f.write(content)

    def _determine_requirements(self,
                                requires,  # type: List[str]
                                allow_prereleases=False,  # type: bool
                                ):  # type: (...) -> List[str]
        if not requires:
            requires = []

            package = self.ask('Search for package:')
            while package is not None:
                matches = self._get_pool().search(package)

                if not matches:
                    self.line('<error>Unable to find package</error>')
                    package = False
                else:
                    choices = []

                    for found_package in matches:
                        choices.append(found_package.pretty_name)

                    self.line(
                        'Found <info>{}</info> packages matching <info>{}</info>'
                        .format(
                            len(matches),
                            package
                        )
                    )

                    package = self.choice(
                        '\nEnter package # to add, or the complete package name if it is not listed',
                        choices,
                        attempts=3
                    )

                # no constraint yet, determine the best version automatically
                if package is not False and ' ' not in package:
                    question = self.create_question(
                        'Enter the version constraint to require '
                        '(or leave blank to use the latest version):'
                    )
                    question.attempts = 3
                    question.validator = lambda x: (x or '').strip() or False

                    constraint = self.ask(question)

                    if constraint is False:
                        _, constraint = self._find_best_version_for_package(package)

                        self.line(
                            'Using version <info>{}</info> for <info>{}</info>'
                            .format(constraint, package)
                        )

                    package += ' {}'.format(constraint)

                if package is not False:
                    requires.append(package)

                package = self.ask('\nSearch for a package:')

            return requires

        requires = self._parse_name_version_pairs(requires)
        result = []
        for requirement in requires:
            if 'version' not in requirement:
                # determine the best version automatically
                name, version = self._find_best_version_for_package(
                    requirement['name'],
                    allow_prereleases=allow_prereleases
                )
                requirement['version'] = version
                requirement['name'] = name

                self.line(
                    'Using version <info>{}</> for <info>{}</>'
                    .format(version, name)
                )
            else:
                # check that the specified version/constraint exists
                # before we proceed
                name, _ = self._find_best_version_for_package(
                    requirement['name'], requirement['version'],
                    allow_prereleases=allow_prereleases
                )

                requirement['name'] = name

            result.append(
                '{} {}'.format(requirement['name'], requirement['version'])
            )

        return result

    def _find_best_version_for_package(self,
                                       name,
                                       required_version=None,
                                       allow_prereleases=False
                                       ):  # type: (...) -> Tuple[str, str]
        from poetry.version.version_selector import VersionSelector

        selector = VersionSelector(self._get_pool())
        package = selector.find_best_candidate(
            name, required_version,
            allow_prereleases=allow_prereleases
        )

        if not package:
            # TODO: find similar
            raise ValueError(
                'Could not find a matching version of package {}'.format(name)
            )

        return (
            package.pretty_name,
            selector.find_recommended_require_version(package)
        )

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

        for i in range(len(pairs)):
            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
                })

        return result

    def _format_requirements(self, requirements):  # type: (List[str]) -> dict
        requires = {}
        requirements = self._parse_name_version_pairs(requirements)
        for requirement in requirements:
            requires[requirement['name']] = requirement['version']

        return requires

    def _validate_author(self, author, default):
        from poetry.packages.package import AUTHOR_REGEX

        author = author or default

        if author in ['n', 'no']:
            return

        m = AUTHOR_REGEX.match(author)
        if not m:
            raise ValueError(
                'Invalid author string. Must be in the format: '
                'John Smith <*****@*****.**>'
            )

        return author

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

        if isinstance(self, VenvCommand):
            return self.poetry.pool

        if self._pool is None:
            self._pool = Pool()
            self._pool.add_repository(PyPiRepository())

        return self._pool