From 9b286303c04857b56db90ae457de5d86862ea3be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Fri, 13 Sep 2019 21:05:13 +0200 Subject: [PATCH] Add a Factory class to help create objects needed by Poetry (#1349) --- poetry/config/config.py | 25 +- poetry/config/config_source.py | 77 +---- poetry/config/dict_config_source.py | 42 +++ poetry/config/file_config_source.py | 80 +++++ poetry/console/application.py | 5 +- poetry/console/commands/check.py | 6 +- poetry/console/commands/config.py | 47 +-- poetry/factory.py | 321 +++++++++++++++++++++ poetry/masonry/api.py | 10 +- poetry/masonry/builders/complete.py | 8 +- poetry/poetry.py | 256 +--------------- poetry/puzzle/provider.py | 5 +- tests/config/test_config.py | 2 +- tests/conftest.py | 69 +++-- tests/console/commands/test_check.py | 3 +- tests/console/commands/test_config.py | 28 +- tests/console/commands/test_export.py | 16 +- tests/console/conftest.py | 21 +- tests/masonry/builders/test_builder.py | 22 +- tests/masonry/builders/test_complete.py | 16 +- tests/masonry/builders/test_editable.py | 6 +- tests/masonry/builders/test_sdist.py | 36 +-- tests/masonry/builders/test_wheel.py | 16 +- tests/masonry/publishing/test_publisher.py | 10 +- tests/masonry/publishing/test_uploader.py | 8 +- tests/{test_poetry.py => test_factory.py} | 39 +-- tests/utils/test_exporter.py | 4 +- 27 files changed, 695 insertions(+), 483 deletions(-) create mode 100644 poetry/config/dict_config_source.py create mode 100644 poetry/config/file_config_source.py create mode 100644 poetry/factory.py rename tests/{test_poetry.py => test_factory.py} (83%) diff --git a/poetry/config/config.py b/poetry/config/config.py index 54c73b0e170..583f906abd6 100644 --- a/poetry/config/config.py +++ b/poetry/config/config.py @@ -11,13 +11,16 @@ from poetry.utils._compat import Path from poetry.utils._compat import basestring +from .config_source import ConfigSource +from .dict_config_source import DictConfigSource + _NOT_SET = object() boolean_validator = lambda val: val in {"true", "false", "1", "0"} boolean_normalizer = lambda val: val in ["true", "1"] -class Config: +class Config(object): default_config = { "cache-dir": str(CACHE_DIR), @@ -34,6 +37,8 @@ def __init__( self._config = deepcopy(self.default_config) self._use_environment = use_environment self._base_dir = base_dir + self._config_source = DictConfigSource() + self._auth_config_source = DictConfigSource() @property def name(self): @@ -43,6 +48,24 @@ def name(self): def config(self): return self._config + @property + def config_source(self): # type: () -> ConfigSource + return self._config_source + + @property + def auth_config_source(self): # type: () -> ConfigSource + return self._auth_config_source + + def set_config_source(self, config_source): # type: (ConfigSource) -> Config + self._config_source = config_source + + return self + + def set_auth_config_source(self, config_source): # type: (ConfigSource) -> Config + self._auth_config_source = config_source + + return self + def merge(self, config): # type: (Dict[str, Any]) -> None from poetry.utils.helpers import merge_dicts diff --git a/poetry/config/config_source.py b/poetry/config/config_source.py index d2c2f02d309..63a4ad6b628 100644 --- a/poetry/config/config_source.py +++ b/poetry/config/config_source.py @@ -1,80 +1,9 @@ -import io -import os -from contextlib import contextmanager from typing import Any -from tomlkit import document -from tomlkit import table - -from poetry.utils.toml_file import TomlFile - - -class ConfigSource: - def __init__(self, file, auth_config=False): # type: (TomlFile, bool) -> None - self._file = file - self._auth_config = auth_config - - @property - def name(self): # type: () -> str - return str(self._file.path) - - @property - def file(self): # type: () -> TomlFile - return self._file +class ConfigSource(object): def add_property(self, key, value): # type: (str, Any) -> None - with self.secure() as config: - keys = key.split(".") - - for i, key in enumerate(keys): - if key not in config and i < len(keys) - 1: - config[key] = table() - - if i == len(keys) - 1: - config[key] = value - break - - config = config[key] + raise NotImplementedError() def remove_property(self, key): # type: (str) -> None - with self.secure() as config: - keys = key.split(".") - - current_config = config - for i, key in enumerate(keys): - if key not in current_config: - return - - if i == len(keys) - 1: - del current_config[key] - - break - - current_config = current_config[key] - - @contextmanager - def secure(self): - if self.file.exists(): - initial_config = self.file.read() - config = self.file.read() - else: - initial_config = document() - config = document() - - new_file = not self.file.exists() - - yield config - - try: - # Ensuring the file is only readable and writable - # by the current user - mode = 0o600 - - if new_file: - self.file.touch(mode=mode) - - self.file.write(config) - except Exception: - self.file.write(initial_config) - - raise + raise NotImplementedError() diff --git a/poetry/config/dict_config_source.py b/poetry/config/dict_config_source.py new file mode 100644 index 00000000000..aaa6ee3b9d1 --- /dev/null +++ b/poetry/config/dict_config_source.py @@ -0,0 +1,42 @@ +from typing import Any +from typing import Dict + +from .config_source import ConfigSource + + +class DictConfigSource(ConfigSource): + def __init__(self): # type: () -> None + self._config = {} + + @property + def config(self): # type: () -> Dict[str, Any] + return self._config + + def add_property(self, key, value): # type: (str, Any) -> None + keys = key.split(".") + config = self._config + + for i, key in enumerate(keys): + if key not in config and i < len(keys) - 1: + config[key] = {} + + if i == len(keys) - 1: + config[key] = value + break + + config = config[key] + + def remove_property(self, key): # type: (str) -> None + keys = key.split(".") + + config = self._config + for i, key in enumerate(keys): + if key not in config: + return + + if i == len(keys) - 1: + del config[key] + + break + + config = config[key] diff --git a/poetry/config/file_config_source.py b/poetry/config/file_config_source.py new file mode 100644 index 00000000000..dda85839cd9 --- /dev/null +++ b/poetry/config/file_config_source.py @@ -0,0 +1,80 @@ +from contextlib import contextmanager +from typing import Any + +from tomlkit import document +from tomlkit import table + +from poetry.utils.toml_file import TomlFile + +from .config_source import ConfigSource + + +class FileConfigSource(ConfigSource): + def __init__(self, file, auth_config=False): # type: (TomlFile, bool) -> None + self._file = file + self._auth_config = auth_config + + @property + def name(self): # type: () -> str + return str(self._file.path) + + @property + def file(self): # type: () -> TomlFile + return self._file + + def add_property(self, key, value): # type: (str, Any) -> None + with self.secure() as config: + keys = key.split(".") + + for i, key in enumerate(keys): + if key not in config and i < len(keys) - 1: + config[key] = table() + + if i == len(keys) - 1: + config[key] = value + break + + config = config[key] + + def remove_property(self, key): # type: (str) -> None + with self.secure() as config: + keys = key.split(".") + + current_config = config + for i, key in enumerate(keys): + if key not in current_config: + return + + if i == len(keys) - 1: + del current_config[key] + + break + + current_config = current_config[key] + + @contextmanager + def secure(self): + if self.file.exists(): + initial_config = self.file.read() + config = self.file.read() + else: + initial_config = document() + config = document() + + new_file = not self.file.exists() + + yield config + + try: + # Ensuring the file is only readable and writable + # by the current user + mode = 0o600 + + if new_file: + self.file.touch(mode=mode) + + self.file.write(config) + except Exception: + self.file.write(initial_config) + + raise diff --git a/poetry/console/application.py b/poetry/console/application.py index aec9e26fee9..a67e459d500 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -47,12 +47,13 @@ def __init__(self): @property def poetry(self): - from poetry.poetry import Poetry + from poetry.factory import Factory + from poetry.utils._compat import Path if self._poetry is not None: return self._poetry - self._poetry = Poetry.create(os.getcwd()) + self._poetry = Factory().create_poetry(Path.cwd()) return self._poetry diff --git a/poetry/console/commands/check.py b/poetry/console/commands/check.py index 9505b8e4521..872e14fc519 100644 --- a/poetry/console/commands/check.py +++ b/poetry/console/commands/check.py @@ -1,4 +1,4 @@ -from poetry.poetry import Poetry +from poetry.factory import Factory from poetry.utils._compat import Path from poetry.utils.toml_file import TomlFile @@ -12,9 +12,9 @@ class CheckCommand(Command): def handle(self): # Load poetry config and display errors, if any - poetry_file = Poetry.locate(Path.cwd()) + poetry_file = Factory.locate(Path.cwd()) config = TomlFile(str(poetry_file)).read()["tool"]["poetry"] - check_result = Poetry.check(config, strict=True) + check_result = Factory.validate(config, strict=True) if not check_result["errors"] and not check_result["warnings"]: self.info("All set!") diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index f45645de832..797a69cb7aa 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -4,6 +4,7 @@ from cleo import argument from cleo import option +from poetry.factory import Factory from poetry.utils.helpers import ( keyring_repository_password_del, keyring_repository_password_set, @@ -37,6 +38,8 @@ class ConfigCommand(Command): poetry config --unset repo.foo""" + LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"} + @property def unique_config_values(self): from poetry.locations import CACHE_DIR @@ -63,28 +66,24 @@ def unique_config_values(self): return unique_config_values def handle(self): - from poetry.config.config import Config - from poetry.config.config_source import ConfigSource + from poetry.config.file_config_source import FileConfigSource from poetry.locations import CONFIG_DIR from poetry.utils._compat import Path from poetry.utils._compat import basestring from poetry.utils.toml_file import TomlFile - config = Config() + config = Factory.create_config(self.io) config_file = TomlFile(Path(CONFIG_DIR) / "config.toml") - config_source = ConfigSource(config_file) - if config_source.file.exists(): - config.merge(config_source.file.read()) - - auth_config_file = TomlFile(Path(CONFIG_DIR) / "auth.toml") - auth_config_source = ConfigSource(auth_config_file, auth_config=True) - local_config_file = TomlFile(self.poetry.file.parent / "poetry.toml") - if local_config_file.exists(): - config.merge(local_config_file.read()) + try: + local_config_file = TomlFile(self.poetry.file.parent / "poetry.toml") + if local_config_file.exists(): + config.merge(local_config_file.read()) + except RuntimeError: + local_config_file = TomlFile(Path.cwd() / "poetry.toml") if self.option("local"): - config_source = ConfigSource(local_config_file) + config.set_config_source(FileConfigSource(local_config_file)) if not config_file.exists(): config_file.path.parent.mkdir(parents=True, exist_ok=True) @@ -139,10 +138,13 @@ def handle(self): unique_config_values = self.unique_config_values if setting_key in unique_config_values: if self.option("unset"): - return config_source.remove_property(setting_key) + return config.config_source.remove_property(setting_key) return self._handle_single_value( - config_source, setting_key, unique_config_values[setting_key], values + config.config_source, + setting_key, + unique_config_values[setting_key], + values, ) # handle repositories @@ -158,14 +160,16 @@ def handle(self): "There is no {} repository defined".format(m.group(1)) ) - config_source.remove_property("repositories.{}".format(m.group(1))) + config.config_source.remove_property( + "repositories.{}".format(m.group(1)) + ) return 0 if len(values) == 1: url = values[0] - config_source.add_property( + config.config_source.add_property( "repositories.{}.url".format(m.group(1)), url ) @@ -181,7 +185,7 @@ def handle(self): if m: if self.option("unset"): keyring_repository_password_del(config, m.group(2)) - auth_config_source.remove_property( + config.auth_config_source.remove_property( "{}.{}".format(m.group(1), m.group(2)) ) @@ -207,7 +211,7 @@ def handle(self): except RuntimeError: property_value.update(password=password) - auth_config_source.add_property( + config.auth_config_source.add_property( "{}.{}".format(m.group(1), m.group(2)), property_value ) elif m.group(1) == "pypi-token": @@ -218,7 +222,7 @@ def handle(self): token = values[0] - auth_config_source.add_property( + config.auth_config_source.add_property( "{}.{}".format(m.group(1), m.group(2)), token ) @@ -245,6 +249,9 @@ def _list_configuration(self, config, raw, k=""): orig_k = k for key, value in sorted(config.items()): + if k + key in self.LIST_PROHIBITED_SETTINGS: + continue + raw_val = raw.get(key) if isinstance(value, dict): diff --git a/poetry/factory.py b/poetry/factory.py new file mode 100644 index 00000000000..faf4de860e9 --- /dev/null +++ b/poetry/factory.py @@ -0,0 +1,321 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +import shutil + +from typing import Dict +from typing import List +from typing import Optional + +from clikit.api.io.io import IO + +from .config.config import Config +from .config.file_config_source import FileConfigSource +from .io.null_io import NullIO +from .json import validate_object +from .locations import CONFIG_DIR +from .packages.dependency import Dependency +from .packages.locker import Locker +from .packages.project_package import ProjectPackage +from .poetry import Poetry +from .repositories.pypi_repository import PyPiRepository +from .spdx import license_by_id +from .utils._compat import Path +from .utils.toml_file import TomlFile + + +class Factory: + """ + Factory class to create various elements needed by Poetry. + """ + + def create_poetry( + self, cwd=None, io=None + ): # type: (Optional[Path], Optional[IO]) -> Poetry + if io is None: + io = NullIO() + + poetry_file = self.locate(cwd) + + local_config = TomlFile(poetry_file.as_posix()).read() + if "tool" not in local_config or "poetry" not in local_config["tool"]: + raise RuntimeError( + "[tool.poetry] section not found in {}".format(poetry_file.name) + ) + local_config = local_config["tool"]["poetry"] + + # Checking validity + check_result = self.validate(local_config) + if check_result["errors"]: + message = "" + for error in check_result["errors"]: + message += " - {}\n".format(error) + + raise RuntimeError("The Poetry configuration is invalid:\n" + message) + + # Load package + name = local_config["name"] + version = local_config["version"] + package = ProjectPackage(name, version, version) + package.root_dir = poetry_file.parent + + for author in local_config["authors"]: + package.authors.append(author) + + for maintainer in local_config.get("maintainers", []): + package.maintainers.append(maintainer) + + package.description = local_config.get("description", "") + package.homepage = local_config.get("homepage") + package.repository_url = local_config.get("repository") + package.documentation_url = local_config.get("documentation") + try: + license_ = license_by_id(local_config.get("license", "")) + except ValueError: + license_ = None + + package.license = license_ + package.keywords = local_config.get("keywords", []) + package.classifiers = local_config.get("classifiers", []) + + if "readme" in local_config: + package.readme = Path(poetry_file.parent) / local_config["readme"] + + if "platform" in local_config: + package.platform = local_config["platform"] + + if "dependencies" in local_config: + for name, constraint in local_config["dependencies"].items(): + if name.lower() == "python": + package.python_versions = constraint + continue + + if isinstance(constraint, list): + for _constraint in constraint: + package.add_dependency(name, _constraint) + + continue + + package.add_dependency(name, constraint) + + if "dev-dependencies" in local_config: + for name, constraint in local_config["dev-dependencies"].items(): + if isinstance(constraint, list): + for _constraint in constraint: + package.add_dependency(name, _constraint, category="dev") + + continue + + package.add_dependency(name, constraint, category="dev") + + extras = local_config.get("extras", {}) + for extra_name, requirements in extras.items(): + package.extras[extra_name] = [] + + # Checking for dependency + for req in requirements: + req = Dependency(req, "*") + + for dep in package.requires: + if dep.name == req.name: + dep.in_extras.append(extra_name) + package.extras[extra_name].append(dep) + + break + + if "build" in local_config: + package.build = local_config["build"] + + if "include" in local_config: + package.include = local_config["include"] + + if "exclude" in local_config: + package.exclude = local_config["exclude"] + + if "packages" in local_config: + package.packages = local_config["packages"] + + # Custom urls + if "urls" in local_config: + package.custom_urls = local_config["urls"] + + # Moving lock if necessary (pyproject.lock -> poetry.lock) + lock = poetry_file.parent / "poetry.lock" + if not lock.exists(): + # Checking for pyproject.lock + old_lock = poetry_file.with_suffix(".lock") + if old_lock.exists(): + shutil.move(str(old_lock), str(lock)) + + locker = Locker(poetry_file.parent / "poetry.lock", local_config) + + # Loading global configuration + config = self.create_config(io) + + # Loading local configuration + local_config_file = TomlFile(poetry_file.parent / "poetry.toml") + if local_config_file.exists(): + if io.is_debug(): + io.write_line( + "Loading configuration file {}".format(local_config_file.path) + ) + + config.merge(local_config_file.read()) + + poetry = Poetry(poetry_file, local_config, package, locker, config) + + # Configuring sources + for source in local_config.get("source", []): + repository = self.create_legacy_repository(source, config) + is_default = source.get("default", False) + is_secondary = source.get("secondary", False) + if io.is_debug(): + message = "Adding repository {} ({})".format( + repository.name, repository.url + ) + if is_default: + message += " and setting it as the default one" + elif is_secondary: + message += " and setting it as secondary" + + io.write_line(message) + + poetry.pool.add_repository(repository, is_default, secondary=is_secondary) + + # Always put PyPI last to prefer private repositories + # but only if we have no other default source + if not poetry.pool.has_default(): + poetry.pool.add_repository(PyPiRepository(), True) + else: + if io.is_debug(): + io.write_line("Deactivating the PyPI repository") + + return poetry + + @classmethod + def create_config(cls, io=None): # type: (Optional[IO]) -> Config + if io is None: + io = NullIO() + + config = Config() + # Load global config + config_file = TomlFile(Path(CONFIG_DIR) / "config.toml") + if config_file.exists(): + if io.is_debug(): + io.write_line( + "Loading configuration file {}".format( + config_file.path + ) + ) + + config.merge(config_file.read()) + + config.set_config_source(FileConfigSource(config_file)) + + # Load global auth config + auth_config_file = TomlFile(Path(CONFIG_DIR) / "auth.toml") + if auth_config_file.exists(): + if io.is_debug(): + io.write_line( + "Loading configuration file {}".format( + auth_config_file.path + ) + ) + + config.merge(auth_config_file.read()) + + config.set_auth_config_source(FileConfigSource(auth_config_file)) + + return config + + def create_legacy_repository( + self, source, auth_config + ): # type: (Dict[str, str], Config) -> LegacyRepository + from .repositories.auth import Auth + from .repositories.legacy_repository import LegacyRepository + from .utils.helpers import get_http_basic_auth + + if "url" in source: + # PyPI-like repository + if "name" not in source: + raise RuntimeError("Missing [name] in source.") + else: + raise RuntimeError("Unsupported source specified") + + name = source["name"] + url = source["url"] + credentials = get_http_basic_auth(auth_config, name) + if not credentials: + return LegacyRepository(name, url) + + auth = Auth(url, credentials[0], credentials[1]) + + return LegacyRepository(name, url, auth=auth) + + @classmethod + def validate( + cls, config, strict=False + ): # type: (dict, bool) -> Dict[str, List[str]] + """ + Checks the validity of a configuration + """ + result = {"errors": [], "warnings": []} + # Schema validation errors + validation_errors = validate_object(config, "poetry-schema") + + result["errors"] += validation_errors + + if strict: + # If strict, check the file more thoroughly + + # Checking license + license = config.get("license") + if license: + try: + license_by_id(license) + except ValueError: + result["errors"].append("{} is not a valid license".format(license)) + + if "dependencies" in config: + python_versions = config["dependencies"]["python"] + if python_versions == "*": + result["warnings"].append( + "A wildcard Python dependency is ambiguous. " + "Consider specifying a more explicit one." + ) + + # Checking for scripts with extras + if "scripts" in config: + scripts = config["scripts"] + for name, script in scripts.items(): + if not isinstance(script, dict): + continue + + extras = script["extras"] + for extra in extras: + if extra not in config["extras"]: + result["errors"].append( + 'Script "{}" requires extra "{}" which is not defined.'.format( + name, extra + ) + ) + + return result + + @classmethod + def locate(cls, cwd): # type: (Path) -> Path + candidates = [Path(cwd)] + candidates.extend(Path(cwd).parents) + + for path in candidates: + poetry_file = path / "pyproject.toml" + + if poetry_file.exists(): + return poetry_file + + else: + raise RuntimeError( + "Poetry could not find a pyproject.toml file in {} or its parents".format( + cwd + ) + ) diff --git a/poetry/masonry/api.py b/poetry/masonry/api.py index ab6ef13c2d6..49b1a884514 100644 --- a/poetry/masonry/api.py +++ b/poetry/masonry/api.py @@ -6,7 +6,7 @@ from clikit.io import NullIO -from poetry.poetry import Poetry +from poetry.factory import Factory from poetry.utils._compat import Path from poetry.utils._compat import unicode from poetry.utils.env import SystemEnv @@ -21,7 +21,7 @@ def get_requires_for_build_wheel(config_settings=None): """ Returns a list of requirements for building, as strings """ - poetry = Poetry.create(".") + poetry = Factory().create_poetry(Path(".")) main, _ = SdistBuilder.convert_dependencies(poetry.package, poetry.package.requires) @@ -33,7 +33,7 @@ def get_requires_for_build_wheel(config_settings=None): def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): - poetry = Poetry.create(".") + poetry = Factory().create_poetry(Path(".")) builder = WheelBuilder(poetry, SystemEnv(Path(sys.prefix)), NullIO()) dist_info = Path(metadata_directory, builder.dist_info) @@ -54,7 +54,7 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): """Builds a wheel, places it in wheel_directory""" - poetry = Poetry.create(".") + poetry = Factory().create_poetry(Path(".")) return unicode( WheelBuilder.make_in( @@ -65,7 +65,7 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): def build_sdist(sdist_directory, config_settings=None): """Builds an sdist, places it in sdist_directory""" - poetry = Poetry.create(".") + poetry = Factory().create_poetry(Path(".")) path = SdistBuilder(poetry, SystemEnv(Path(sys.prefix)), NullIO()).build( Path(sdist_directory) diff --git a/poetry/masonry/builders/complete.py b/poetry/masonry/builders/complete.py index d998d713d23..ec40014c42e 100644 --- a/poetry/masonry/builders/complete.py +++ b/poetry/masonry/builders/complete.py @@ -1,7 +1,7 @@ import os import tarfile -import poetry.poetry +from poetry.factory import Factory from poetry.io.null_io import NullIO from poetry.utils._compat import Path from poetry.utils.helpers import temporary_directory @@ -43,7 +43,7 @@ def build(self): with self.unpacked_tarball(sdist_file) as tmpdir: WheelBuilder.make_in( - poetry.poetry.Poetry.create(tmpdir), + Factory().create_poetry(tmpdir), self._env, self._io, dist_dir, @@ -52,7 +52,7 @@ def build(self): else: with self.unpacked_tarball(sdist_file) as tmpdir: WheelBuilder.make_in( - poetry.poetry.Poetry.create(tmpdir), + Factory().create_poetry(tmpdir), self._env, self._io, dist_dir, @@ -70,4 +70,4 @@ def unpacked_tarball(cls, path): assert len(files) == 1, files - yield os.path.join(tmpdir, files[0]) + yield Path(tmpdir) / files[0] diff --git a/poetry/poetry.py b/poetry/poetry.py index 3c53244830b..4b28a5478ae 100644 --- a/poetry/poetry.py +++ b/poetry/poetry.py @@ -1,26 +1,14 @@ from __future__ import absolute_import from __future__ import unicode_literals -import shutil - -from typing import Dict -from typing import List +from typing import Optional from .__version__ import __version__ from .config.config import Config -from .json import validate_object -from .locations import CONFIG_DIR -from .packages import Dependency from .packages import Locker -from .packages import Package from .packages import ProjectPackage -from .repositories import Pool -from .repositories.auth import Auth -from .repositories.legacy_repository import LegacyRepository -from .repositories.pypi_repository import PyPiRepository -from .spdx import license_by_id +from .repositories.pool import Pool from .utils._compat import Path -from .utils.helpers import get_http_basic_auth from .utils.toml_file import TomlFile @@ -32,7 +20,7 @@ def __init__( self, file, # type: Path local_config, # type: dict - package, # type: Package + package, # type: ProjectPackage locker, # type: Locker config, # type: Config ): @@ -41,28 +29,14 @@ def __init__( self._local_config = local_config self._locker = locker self._config = config - - # Configure sources self._pool = Pool() - for source in self._local_config.get("source", []): - repository = self.create_legacy_repository(source) - self._pool.add_repository( - repository, - source.get("default", False), - secondary=source.get("secondary", False), - ) - - # Always put PyPI last to prefer private repositories - # but only if we have no other default source - if not self._pool.has_default(): - self._pool.add_repository(PyPiRepository(), True) @property def file(self): return self._file @property - def package(self): # type: () -> Package + def package(self): # type: () -> ProjectPackage return self._package @property @@ -81,221 +55,17 @@ def pool(self): # type: () -> Pool def config(self): # type: () -> Config return self._config - @classmethod - def create(cls, cwd): # type: (Path) -> Poetry - poetry_file = cls.locate(cwd) - - local_config = TomlFile(poetry_file.as_posix()).read() - if "tool" not in local_config or "poetry" not in local_config["tool"]: - raise RuntimeError( - "[tool.poetry] section not found in {}".format(poetry_file.name) - ) - local_config = local_config["tool"]["poetry"] - - # Checking validity - check_result = cls.check(local_config) - if check_result["errors"]: - message = "" - for error in check_result["errors"]: - message += " - {}\n".format(error) - - raise RuntimeError("The Poetry configuration is invalid:\n" + message) - - # Load package - name = local_config["name"] - version = local_config["version"] - package = ProjectPackage(name, version, version) - package.root_dir = poetry_file.parent - - for author in local_config["authors"]: - package.authors.append(author) - - for maintainer in local_config.get("maintainers", []): - package.maintainers.append(maintainer) - - package.description = local_config.get("description", "") - package.homepage = local_config.get("homepage") - package.repository_url = local_config.get("repository") - package.documentation_url = local_config.get("documentation") - try: - license_ = license_by_id(local_config.get("license", "")) - except ValueError: - license_ = None - - package.license = license_ - package.keywords = local_config.get("keywords", []) - package.classifiers = local_config.get("classifiers", []) - - if "readme" in local_config: - package.readme = Path(poetry_file.parent) / local_config["readme"] - - if "platform" in local_config: - package.platform = local_config["platform"] - - if "dependencies" in local_config: - for name, constraint in local_config["dependencies"].items(): - if name.lower() == "python": - package.python_versions = constraint - continue - - if isinstance(constraint, list): - for _constraint in constraint: - package.add_dependency(name, _constraint) - - continue - - package.add_dependency(name, constraint) - - if "dev-dependencies" in local_config: - for name, constraint in local_config["dev-dependencies"].items(): - if isinstance(constraint, list): - for _constraint in constraint: - package.add_dependency(name, _constraint, category="dev") - - continue - - package.add_dependency(name, constraint, category="dev") - - extras = local_config.get("extras", {}) - for extra_name, requirements in extras.items(): - package.extras[extra_name] = [] - - # Checking for dependency - for req in requirements: - req = Dependency(req, "*") - - for dep in package.requires: - if dep.name == req.name: - dep.in_extras.append(extra_name) - package.extras[extra_name].append(dep) - - break - - if "build" in local_config: - package.build = local_config["build"] - - if "include" in local_config: - package.include = local_config["include"] - - if "exclude" in local_config: - package.exclude = local_config["exclude"] - - if "packages" in local_config: - package.packages = local_config["packages"] - - # Custom urls - if "urls" in local_config: - package.custom_urls = local_config["urls"] - - # Moving lock if necessary (pyproject.lock -> poetry.lock) - lock = poetry_file.parent / "poetry.lock" - if not lock.exists(): - # Checking for pyproject.lock - old_lock = poetry_file.with_suffix(".lock") - if old_lock.exists(): - shutil.move(str(old_lock), str(lock)) - - locker = Locker(poetry_file.parent / "poetry.lock", local_config) - - config = Config() - # Load global config - config_file = TomlFile(Path(CONFIG_DIR) / "config.toml") - if config_file.exists(): - config.merge(config_file.read()) - - local_config_file = TomlFile(poetry_file.parent / "poetry.toml") - if local_config_file.exists(): - config.merge(local_config_file.read()) - - # Load global auth config - auth_config_file = TomlFile(Path(CONFIG_DIR) / "auth.toml") - if auth_config_file.exists(): - config.merge(auth_config_file.read()) - - return cls(poetry_file, local_config, package, locker, config) - - def create_legacy_repository( - self, source - ): # type: (Dict[str, str]) -> LegacyRepository - if "url" in source: - # PyPI-like repository - if "name" not in source: - raise RuntimeError("Missing [name] in source.") - else: - raise RuntimeError("Unsupported source specified") - - name = source["name"] - url = source["url"] - credentials = get_http_basic_auth(self._config, name) - if not credentials: - return LegacyRepository(name, url) - - auth = Auth(url, credentials[0], credentials[1]) - - return LegacyRepository(name, url, auth=auth) - - @classmethod - def locate(cls, cwd): # type: (Path) -> Poetry - candidates = [Path(cwd)] - candidates.extend(Path(cwd).parents) - - for path in candidates: - poetry_file = path / "pyproject.toml" - - if poetry_file.exists(): - return poetry_file - - else: - raise RuntimeError( - "Poetry could not find a pyproject.toml file in {} or its parents".format( - cwd - ) - ) - - @classmethod - def check(cls, config, strict=False): # type: (dict, bool) -> Dict[str, List[str]] - """ - Checks the validity of a configuration - """ - result = {"errors": [], "warnings": []} - # Schema validation errors - validation_errors = validate_object(config, "poetry-schema") - - result["errors"] += validation_errors - - if strict: - # If strict, check the file more thoroughly + def set_locker(self, locker): # type: (Locker) -> Poetry + self._locker = locker - # Checking license - license = config.get("license") - if license: - try: - license_by_id(license) - except ValueError: - result["errors"].append("{} is not a valid license".format(license)) + return self - if "dependencies" in config: - python_versions = config["dependencies"]["python"] - if python_versions == "*": - result["warnings"].append( - "A wildcard Python dependency is ambiguous. " - "Consider specifying a more explicit one." - ) + def set_pool(self, pool): # type: (Pool) -> Poetry + self._pool = pool - # Checking for scripts with extras - if "scripts" in config: - scripts = config["scripts"] - for name, script in scripts.items(): - if not isinstance(script, dict): - continue + return self - extras = script["extras"] - for extra in extras: - if extra not in config["extras"]: - result["errors"].append( - 'Script "{}" requires extra "{}" which is not defined.'.format( - name, extra - ) - ) + def set_config(self, config): # type: (Config) -> Poetry + self._config = config - return result + return self diff --git a/poetry/puzzle/provider.py b/poetry/puzzle/provider.py index 75c30195b96..3e4bc2d71e8 100644 --- a/poetry/puzzle/provider.py +++ b/poetry/puzzle/provider.py @@ -11,6 +11,7 @@ from typing import List from typing import Optional +from poetry.factory import Factory from poetry.packages import Dependency from poetry.packages import DependencyPackage from poetry.packages import DirectoryDependency @@ -300,9 +301,7 @@ def get_package_from_directory( ) if supports_poetry: - from poetry.poetry import Poetry - - poetry = Poetry.create(directory) + poetry = Factory().create_poetry(directory) pkg = poetry.package package = Package(pkg.name, pkg.version) diff --git a/tests/config/test_config.py b/tests/config/test_config.py index c1f6b81c029..8dda26eca37 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -8,7 +8,7 @@ def test_config_get_default_value(config): def test_config_get_processes_depended_on_values(config): - assert os.path.join(str(CACHE_DIR), "virtualenvs") == config.get("virtualenvs.path") + assert os.path.join("/foo", "virtualenvs") == config.get("virtualenvs.path") def test_config_get_from_environment_variable(config, environ): diff --git a/tests/conftest.py b/tests/conftest.py index de7cf31ea31..a4f58b0acc8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,43 +10,68 @@ except ImportError: import urlparse -from tomlkit import parse +from typing import Any +from typing import Dict -from poetry.config.config import Config +from poetry.config.config import Config as BaseConfig +from poetry.config.dict_config_source import DictConfigSource from poetry.utils._compat import PY2 from poetry.utils._compat import WINDOWS from poetry.utils._compat import Path -from poetry.utils.helpers import merge_dicts -from poetry.utils.toml_file import TomlFile + + +class Config(BaseConfig): + def get(self, setting_name, default=None): # type: (str, Any) -> Any + self.merge(self._config_source.config) + self.merge(self._auth_config_source.config) + + return super(Config, self).get(setting_name, default=default) + + def raw(self): # type: () -> Dict[str, Any] + self.merge(self._config_source.config) + self.merge(self._auth_config_source.config) + + return super(Config, self).raw() + + def all(self): # type: () -> Dict[str, Any] + self.merge(self._config_source.config) + self.merge(self._auth_config_source.config) + + return super(Config, self).all() + + +def tmp_dir(): + dir_ = tempfile.mkdtemp(prefix="poetry_") + + yield dir_ + + shutil.rmtree(dir_) @pytest.fixture -def config_document(): - content = """cache-dir = "/foo" -""" - doc = parse(content) +def config_source(): + source = DictConfigSource() + source.add_property("cache-dir", "/foo") - return doc + return source @pytest.fixture -def config_source(config_document, mocker): - file = TomlFile(Path(tempfile.mktemp())) - mocker.patch.object(file, "exists", return_value=True) - mocker.patch.object(file, "read", return_value=config_document) - mocker.patch.object( - file, "write", return_value=lambda new: merge_dicts(config_document, new) - ) - mocker.patch( - "poetry.config.config_source.ConfigSource.file", - new_callable=mocker.PropertyMock, - return_value=file, - ) +def auth_config_source(): + source = DictConfigSource() + + return source @pytest.fixture -def config(config_source): +def config(config_source, auth_config_source, mocker): c = Config() + c.merge(config_source.config) + c.set_config_source(config_source) + c.set_auth_config_source(auth_config_source) + + mocker.patch("poetry.factory.Factory.create_config", return_value=c) + mocker.patch("poetry.config.config.Config.set_config_source") return c diff --git a/tests/console/commands/test_check.py b/tests/console/commands/test_check.py index 7a97d9c7e2b..bfc4ce9d0f2 100644 --- a/tests/console/commands/test_check.py +++ b/tests/console/commands/test_check.py @@ -2,7 +2,6 @@ from poetry.utils._compat import PY2 from poetry.utils._compat import Path -from poetry.poetry import Poetry def test_check_valid(app): @@ -20,7 +19,7 @@ def test_check_valid(app): def test_check_invalid(app, mocker): mocker.patch( - "poetry.poetry.Poetry.locate", + "poetry.factory.Factory.locate", return_value=Path(__file__).parent.parent.parent / "fixtures" / "invalid_pyproject" diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 94e4000c06a..b690b2a2f4d 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -4,13 +4,12 @@ from cleo.testers import CommandTester from poetry.config.config_source import ConfigSource -from poetry.poetry import Poetry +from poetry.factory import Factory -def test_list_displays_default_value_if_not_set(app, config_source): +def test_list_displays_default_value_if_not_set(app, config): command = app.find("config") tester = CommandTester(command) - tester.execute("--list") expected = """cache-dir = "/foo" @@ -24,7 +23,7 @@ def test_list_displays_default_value_if_not_set(app, config_source): assert expected == tester.io.fetch_output() -def test_list_displays_set_get_setting(app, config_source, config_document): +def test_list_displays_set_get_setting(app, config): command = app.find("config") tester = CommandTester(command) @@ -40,10 +39,11 @@ def test_list_displays_set_get_setting(app, config_source, config_document): path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep ) + assert 0 == config.set_config_source.call_count assert expected == tester.io.fetch_output() -def test_display_single_setting(app, config_source): +def test_display_single_setting(app, config): command = app.find("config") tester = CommandTester(command) @@ -55,8 +55,8 @@ def test_display_single_setting(app, config_source): assert expected == tester.io.fetch_output() -def test_display_single_local_setting(app, config_source, fixture_dir): - poetry = Poetry.create(fixture_dir("with_local_config")) +def test_display_single_local_setting(app, config, fixture_dir): + poetry = Factory().create_poetry(fixture_dir("with_local_config")) app._poetry = poetry command = app.find("config") @@ -70,10 +70,7 @@ def test_display_single_local_setting(app, config_source, fixture_dir): assert expected == tester.io.fetch_output() -def test_list_displays_set_get_local_setting( - app, config_source, config_document, mocker -): - init = mocker.spy(ConfigSource, "__init__") +def test_list_displays_set_get_local_setting(app, config): command = app.find("config") tester = CommandTester(command) @@ -89,14 +86,11 @@ def test_list_displays_set_get_local_setting( path=json.dumps(os.path.join("{cache-dir}", "virtualenvs")), sep=os.path.sep ) - assert expected == tester.io.fetch_output() - - assert "poetry.toml" == init.call_args_list[2][0][1].path.name + assert 1 == config.set_config_source.call_count assert expected == tester.io.fetch_output() -def test_set_pypi_token(app, config_source, config_document, mocker): - init = mocker.spy(ConfigSource, "__init__") +def test_set_pypi_token(app, config, config_source, auth_config_source): command = app.find("config") tester = CommandTester(command) @@ -104,4 +98,4 @@ def test_set_pypi_token(app, config_source, config_document, mocker): tester.execute("--list") - assert "mytoken" == config_document["pypi-token"]["pypi"] + assert "mytoken" == auth_config_source.config["pypi-token"]["pypi"] diff --git a/tests/console/commands/test_export.py b/tests/console/commands/test_export.py index 529acb9023f..45de47c6eb1 100644 --- a/tests/console/commands/test_export.py +++ b/tests/console/commands/test_export.py @@ -5,11 +5,13 @@ from cleo.testers import CommandTester +from poetry.factory import Factory +from poetry.repositories.pool import Pool from tests.helpers import get_package from ..conftest import Application +from ..conftest import Locker from ..conftest import Path -from ..conftest import Poetry PYPROJECT_CONTENT = """\ @@ -51,11 +53,15 @@ def poetry(repo, tmp_dir): with (Path(tmp_dir) / "pyproject.toml").open("w", encoding="utf-8") as f: f.write(PYPROJECT_CONTENT) - p = Poetry.create(Path(tmp_dir)) + p = Factory().create_poetry(Path(tmp_dir)) - p.pool.remove_repository("pypi") - p.pool.add_repository(repo) - p._locker.write() + locker = Locker(p.locker.lock.path, p.locker._local_config) + locker.write() + p.set_locker(locker) + + pool = Pool() + pool.add_repository(repo) + p.set_pool(pool) yield p diff --git a/tests/console/conftest.py b/tests/console/conftest.py index 85efd4bfea0..5f2a4d4923b 100644 --- a/tests/console/conftest.py +++ b/tests/console/conftest.py @@ -10,6 +10,7 @@ from cleo import ApplicationTester from poetry.console import Application as BaseApplication +from poetry.factory import Factory from poetry.installation.noop_installer import NoopInstaller from poetry.poetry import Poetry as BasePoetry from poetry.packages import Locker as BaseLocker @@ -112,8 +113,10 @@ def __init__(self, poetry): def reset_poetry(self): poetry = self._poetry - self._poetry = Poetry.create(self._poetry.file.path.parent) - self._poetry._pool = poetry.pool + self._poetry = Factory().create_poetry(self._poetry.file.path.parent) + self._poetry.set_pool(poetry.pool) + self._poetry.set_config(poetry.config) + self._poetry.set_locker(poetry.locker) class Locker(BaseLocker): @@ -189,14 +192,20 @@ def project_directory(): @pytest.fixture -def poetry(repo, project_directory, config_source): - p = Poetry.create(Path(__file__).parent.parent / "fixtures" / project_directory) +def poetry(repo, project_directory, config): + p = Factory().create_poetry( + Path(__file__).parent.parent / "fixtures" / project_directory + ) + p.set_locker(Locker(p.locker.lock.path, p.locker._local_config)) with p.file.path.open(encoding="utf-8") as f: content = f.read() - p.pool.remove_repository("pypi") - p.pool.add_repository(repo) + p.set_config(config) + + pool = Pool() + pool.add_repository(repo) + p.set_pool(pool) yield p diff --git a/tests/masonry/builders/test_builder.py b/tests/masonry/builders/test_builder.py index 6b43e5280b8..b393022c3ab 100644 --- a/tests/masonry/builders/test_builder.py +++ b/tests/masonry/builders/test_builder.py @@ -2,8 +2,8 @@ from clikit.io import NullIO from email.parser import Parser +from poetry.factory import Factory from poetry.masonry.builders.builder import Builder -from poetry.poetry import Poetry from poetry.utils._compat import Path from poetry.utils.env import NullEnv @@ -13,7 +13,7 @@ def test_builder_find_excluded_files(mocker): p.return_value = [] builder = Builder( - Poetry.create(Path(__file__).parent / "fixtures" / "complete"), + Factory().create_poetry(Path(__file__).parent / "fixtures" / "complete"), NullEnv(), NullIO(), ) @@ -26,7 +26,9 @@ def test_builder_find_case_sensitive_excluded_files(mocker): p.return_value = [] builder = Builder( - Poetry.create(Path(__file__).parent / "fixtures" / "case_sensitive_exclusions"), + Factory().create_poetry( + Path(__file__).parent / "fixtures" / "case_sensitive_exclusions" + ), NullEnv(), NullIO(), ) @@ -47,7 +49,7 @@ def test_builder_find_invalid_case_sensitive_excluded_files(mocker): p.return_value = [] builder = Builder( - Poetry.create( + Factory().create_poetry( Path(__file__).parent / "fixtures" / "invalid_case_sensitive_exclusions" ), NullEnv(), @@ -59,7 +61,7 @@ def test_builder_find_invalid_case_sensitive_excluded_files(mocker): def test_get_metadata_content(): builder = Builder( - Poetry.create(Path(__file__).parent / "fixtures" / "complete"), + Factory().create_poetry(Path(__file__).parent / "fixtures" / "complete"), NullEnv(), NullIO(), ) @@ -110,7 +112,7 @@ def test_get_metadata_content(): def test_metadata_homepage_default(): builder = Builder( - Poetry.create(Path(__file__).parent / "fixtures" / "simple_version"), + Factory().create_poetry(Path(__file__).parent / "fixtures" / "simple_version"), NullEnv(), NullIO(), ) @@ -122,7 +124,9 @@ def test_metadata_homepage_default(): def test_metadata_with_vcs_dependencies(): builder = Builder( - Poetry.create(Path(__file__).parent / "fixtures" / "with_vcs_dependency"), + Factory().create_poetry( + Path(__file__).parent / "fixtures" / "with_vcs_dependency" + ), NullEnv(), NullIO(), ) @@ -136,7 +140,9 @@ def test_metadata_with_vcs_dependencies(): def test_metadata_with_url_dependencies(): builder = Builder( - Poetry.create(Path(__file__).parent / "fixtures" / "with_url_dependency"), + Factory().create_poetry( + Path(__file__).parent / "fixtures" / "with_url_dependency" + ), NullEnv(), NullIO(), ) diff --git a/tests/masonry/builders/test_complete.py b/tests/masonry/builders/test_complete.py index d12aec2add3..09771125b94 100644 --- a/tests/masonry/builders/test_complete.py +++ b/tests/masonry/builders/test_complete.py @@ -14,8 +14,8 @@ from clikit.io import NullIO from poetry import __version__ +from poetry.factory import Factory from poetry.masonry.builders import CompleteBuilder -from poetry.poetry import Poetry from poetry.utils._compat import Path from poetry.utils._compat import decode from poetry.utils.env import NullEnv @@ -45,7 +45,7 @@ def clear_samples_dist(): def test_wheel_c_extension(): module_path = fixtures_dir / "extended" builder = CompleteBuilder( - Poetry.create(module_path), NullEnv(execute=True), NullIO() + Factory().create_poetry(module_path), NullEnv(execute=True), NullIO() ) builder.build() @@ -102,7 +102,7 @@ def test_wheel_c_extension(): def test_wheel_c_extension_src_layout(): module_path = fixtures_dir / "src_extended" builder = CompleteBuilder( - Poetry.create(module_path), NullEnv(execute=True), NullIO() + Factory().create_poetry(module_path), NullEnv(execute=True), NullIO() ) builder.build() @@ -155,7 +155,7 @@ def test_wheel_c_extension_src_layout(): def test_complete(): module_path = fixtures_dir / "complete" builder = CompleteBuilder( - Poetry.create(module_path), NullEnv(execute=True), NullIO() + Factory().create_poetry(module_path), NullEnv(execute=True), NullIO() ) builder.build() @@ -244,7 +244,7 @@ def test_complete_no_vcs(): shutil.copytree(module_path.as_posix(), temporary_dir.as_posix()) builder = CompleteBuilder( - Poetry.create(temporary_dir), NullEnv(execute=True), NullIO() + Factory().create_poetry(temporary_dir), NullEnv(execute=True), NullIO() ) builder.build() @@ -341,7 +341,7 @@ def test_complete_no_vcs(): def test_module_src(): module_path = fixtures_dir / "source_file" builder = CompleteBuilder( - Poetry.create(module_path), NullEnv(execute=True), NullIO() + Factory().create_poetry(module_path), NullEnv(execute=True), NullIO() ) builder.build() @@ -367,7 +367,7 @@ def test_module_src(): def test_package_src(): module_path = fixtures_dir / "source_package" builder = CompleteBuilder( - Poetry.create(module_path), NullEnv(execute=True), NullIO() + Factory().create_poetry(module_path), NullEnv(execute=True), NullIO() ) builder.build() @@ -413,7 +413,7 @@ def test_package_with_include(mocker): / "vcs_excluded.txt" ), ] - builder = CompleteBuilder(Poetry.create(module_path), NullEnv(), NullIO()) + builder = CompleteBuilder(Factory().create_poetry(module_path), NullEnv(), NullIO()) builder.build() sdist = fixtures_dir / "with-include" / "dist" / "with-include-1.2.3.tar.gz" diff --git a/tests/masonry/builders/test_editable.py b/tests/masonry/builders/test_editable.py index 7d35397ecfe..a909b3e2504 100644 --- a/tests/masonry/builders/test_editable.py +++ b/tests/masonry/builders/test_editable.py @@ -3,8 +3,8 @@ from clikit.io import NullIO +from poetry.factory import Factory from poetry.masonry.builders import EditableBuilder -from poetry.poetry import Poetry from poetry.utils._compat import Path from poetry.utils.env import MockEnv @@ -18,7 +18,7 @@ def test_build_should_delegate_to_pip_for_non_pure_python_packages(tmp_dir, mock env.site_packages.mkdir(parents=True) module_path = fixtures_dir / "extended" - builder = EditableBuilder(Poetry.create(module_path), env, NullIO()) + builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO()) builder.build() expected = [["python", "-m", "pip", "install", "-e", str(module_path)]] @@ -34,7 +34,7 @@ def test_build_should_temporarily_remove_the_pyproject_file(tmp_dir, mocker): env.site_packages.mkdir(parents=True) module_path = fixtures_dir / "extended" - builder = EditableBuilder(Poetry.create(module_path), env, NullIO()) + builder = EditableBuilder(Factory().create_poetry(module_path), env, NullIO()) builder.build() expected = [["python", "-m", "pip", "install", "-e", str(module_path)]] diff --git a/tests/masonry/builders/test_sdist.py b/tests/masonry/builders/test_sdist.py index 4fcda1e4266..281016a3653 100644 --- a/tests/masonry/builders/test_sdist.py +++ b/tests/masonry/builders/test_sdist.py @@ -8,11 +8,11 @@ from clikit.io import NullIO +from poetry.factory import Factory from poetry.masonry.builders.sdist import SdistBuilder from poetry.masonry.utils.package_include import PackageInclude from poetry.packages import Package from poetry.packages.vcs_dependency import VCSDependency -from poetry.poetry import Poetry from poetry.utils._compat import Path from poetry.utils._compat import to_str from poetry.utils.env import NullEnv @@ -111,7 +111,7 @@ def test_convert_dependencies(): def test_make_setup(): - poetry = Poetry.create(project("complete")) + poetry = Factory().create_poetry(project("complete")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) setup = builder.build_setup() @@ -144,7 +144,7 @@ def test_make_pkg_info(mocker): get_metadata_content = mocker.patch( "poetry.masonry.builders.builder.Builder.get_metadata_content" ) - poetry = Poetry.create(project("complete")) + poetry = Factory().create_poetry(project("complete")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) builder.build_pkg_info() @@ -153,7 +153,7 @@ def test_make_pkg_info(mocker): def test_make_pkg_info_any_python(): - poetry = Poetry.create(project("module1")) + poetry = Factory().create_poetry(project("module1")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) pkg_info = builder.build_pkg_info() @@ -164,7 +164,7 @@ def test_make_pkg_info_any_python(): def test_find_files_to_add(): - poetry = Poetry.create(project("complete")) + poetry = Factory().create_poetry(project("complete")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) result = builder.find_files_to_add() @@ -184,7 +184,7 @@ def test_find_files_to_add(): def test_make_pkg_info_multi_constraints_dependency(): - poetry = Poetry.create( + poetry = Factory().create_poetry( Path(__file__).parent.parent.parent / "fixtures" / "project_with_multi_constraints_dependency" @@ -203,7 +203,7 @@ def test_make_pkg_info_multi_constraints_dependency(): def test_find_packages(): - poetry = Poetry.create(project("complete")) + poetry = Factory().create_poetry(project("complete")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) @@ -220,7 +220,7 @@ def test_find_packages(): "my_package.sub_pkg2": ["data2/*"], } - poetry = Poetry.create(project("source_package")) + poetry = Factory().create_poetry(project("source_package")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) @@ -235,7 +235,7 @@ def test_find_packages(): def test_package(): - poetry = Poetry.create(project("complete")) + poetry = Factory().create_poetry(project("complete")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) builder.build() @@ -249,7 +249,7 @@ def test_package(): def test_module(): - poetry = Poetry.create(project("module1")) + poetry = Factory().create_poetry(project("module1")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) builder.build() @@ -263,7 +263,7 @@ def test_module(): def test_prelease(): - poetry = Poetry.create(project("prerelease")) + poetry = Factory().create_poetry(project("prerelease")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) builder.build() @@ -274,7 +274,7 @@ def test_prelease(): def test_with_c_extensions(): - poetry = Poetry.create(project("extended")) + poetry = Factory().create_poetry(project("extended")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) builder.build() @@ -289,7 +289,7 @@ def test_with_c_extensions(): def test_with_c_extensions_src_layout(): - poetry = Poetry.create(project("src_extended")) + poetry = Factory().create_poetry(project("src_extended")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) builder.build() @@ -304,7 +304,7 @@ def test_with_c_extensions_src_layout(): def test_with_src_module_file(): - poetry = Poetry.create(project("source_file")) + poetry = Factory().create_poetry(project("source_file")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) @@ -329,7 +329,7 @@ def test_with_src_module_file(): def test_with_src_module_dir(): - poetry = Poetry.create(project("source_package")) + poetry = Factory().create_poetry(project("source_package")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) @@ -372,7 +372,7 @@ def test_default_with_excluded_data(mocker): .as_posix() ) ] - poetry = Poetry.create(project("default_with_excluded_data")) + poetry = Factory().create_poetry(project("default_with_excluded_data")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) @@ -411,7 +411,7 @@ def test_default_with_excluded_data(mocker): def test_proper_python_requires_if_two_digits_precision_version_specified(): - poetry = Poetry.create(project("simple_version")) + poetry = Factory().create_poetry(project("simple_version")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) pkg_info = builder.build_pkg_info() @@ -422,7 +422,7 @@ def test_proper_python_requires_if_two_digits_precision_version_specified(): def test_proper_python_requires_if_three_digits_precision_version_specified(): - poetry = Poetry.create(project("single_python")) + poetry = Factory().create_poetry(project("single_python")) builder = SdistBuilder(poetry, NullEnv(), NullIO()) pkg_info = builder.build_pkg_info() diff --git a/tests/masonry/builders/test_wheel.py b/tests/masonry/builders/test_wheel.py index 0f41c898102..6344a7f1435 100644 --- a/tests/masonry/builders/test_wheel.py +++ b/tests/masonry/builders/test_wheel.py @@ -5,8 +5,8 @@ from clikit.io import NullIO +from poetry.factory import Factory from poetry.masonry.builders import WheelBuilder -from poetry.poetry import Poetry from poetry.utils._compat import Path from poetry.utils.env import NullEnv @@ -31,7 +31,7 @@ def clear_samples_dist(): def test_wheel_module(): module_path = fixtures_dir / "module1" - WheelBuilder.make(Poetry.create(str(module_path)), NullEnv(), NullIO()) + WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) whl = module_path / "dist" / "module1-0.1-py2.py3-none-any.whl" @@ -43,7 +43,7 @@ def test_wheel_module(): def test_wheel_package(): module_path = fixtures_dir / "complete" - WheelBuilder.make(Poetry.create(str(module_path)), NullEnv(), NullIO()) + WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl" @@ -55,7 +55,7 @@ def test_wheel_package(): def test_wheel_prerelease(): module_path = fixtures_dir / "prerelease" - WheelBuilder.make(Poetry.create(str(module_path)), NullEnv(), NullIO()) + WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) whl = module_path / "dist" / "prerelease-0.1b1-py2.py3-none-any.whl" @@ -64,7 +64,7 @@ def test_wheel_prerelease(): def test_wheel_localversionlabel(): module_path = fixtures_dir / "localversionlabel" - WheelBuilder.make(Poetry.create(str(module_path)), NullEnv(), NullIO()) + WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) local_version_string = "localversionlabel-0.1b1+gitbranch.buildno.1" whl = module_path / "dist" / (local_version_string + "-py2.py3-none-any.whl") @@ -76,7 +76,7 @@ def test_wheel_localversionlabel(): def test_wheel_package_src(): module_path = fixtures_dir / "source_package" - WheelBuilder.make(Poetry.create(str(module_path)), NullEnv(), NullIO()) + WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) whl = module_path / "dist" / "package_src-0.1-py2.py3-none-any.whl" @@ -89,7 +89,7 @@ def test_wheel_package_src(): def test_wheel_module_src(): module_path = fixtures_dir / "source_file" - WheelBuilder.make(Poetry.create(str(module_path)), NullEnv(), NullIO()) + WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) whl = module_path / "dist" / "module_src-0.1-py2.py3-none-any.whl" @@ -101,7 +101,7 @@ def test_wheel_module_src(): def test_dist_info_file_permissions(): module_path = fixtures_dir / "complete" - WheelBuilder.make(Poetry.create(str(module_path)), NullEnv(), NullIO()) + WheelBuilder.make(Factory().create_poetry(module_path), NullEnv(), NullIO()) whl = module_path / "dist" / "my_package-1.2.3-py3-none-any.whl" diff --git a/tests/masonry/publishing/test_publisher.py b/tests/masonry/publishing/test_publisher.py index 01399aeace3..e98e5e1312b 100644 --- a/tests/masonry/publishing/test_publisher.py +++ b/tests/masonry/publishing/test_publisher.py @@ -1,14 +1,14 @@ import pytest +from poetry.factory import Factory from poetry.io.null_io import NullIO from poetry.masonry.publishing.publisher import Publisher -from poetry.poetry import Poetry def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") - poetry = Poetry.create(fixture_dir("sample_project")) + poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge( {"http-basic": {"pypi": {"username": "foo", "password": "bar"}}} @@ -24,7 +24,7 @@ def test_publish_publishes_to_pypi_by_default(fixture_dir, mocker, config): def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config): uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") - poetry = Poetry.create(fixture_dir("sample_project")) + poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge( { @@ -41,7 +41,7 @@ def test_publish_can_publish_to_given_repository(fixture_dir, mocker, config): def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, config): - poetry = Poetry.create(fixture_dir("sample_project")) + poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge( {"http-basic": {"my-repo": {"username": "foo", "password": "bar"}}} @@ -55,7 +55,7 @@ def test_publish_raises_error_for_undefined_repository(fixture_dir, mocker, conf def test_publish_uses_token_if_it_exists(fixture_dir, mocker, config): uploader_auth = mocker.patch("poetry.masonry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.masonry.publishing.uploader.Uploader.upload") - poetry = Poetry.create(fixture_dir("sample_project")) + poetry = Factory().create_poetry(fixture_dir("sample_project")) poetry._config = config poetry.config.merge({"pypi-token": {"pypi": "my-token"}}) publisher = Publisher(poetry, NullIO()) diff --git a/tests/masonry/publishing/test_uploader.py b/tests/masonry/publishing/test_uploader.py index 4c875e3c0ce..e9c5cc3c9f0 100644 --- a/tests/masonry/publishing/test_uploader.py +++ b/tests/masonry/publishing/test_uploader.py @@ -1,9 +1,9 @@ import pytest +from poetry.factory import Factory from poetry.io.null_io import NullIO from poetry.masonry.publishing.uploader import UploadError from poetry.masonry.publishing.uploader import Uploader -from poetry.poetry import Poetry from poetry.utils._compat import Path @@ -16,7 +16,7 @@ def project(name): def test_uploader_properly_handles_400_errors(http): http.register_uri(http.POST, "https://foo.com", status=400, body="Bad request") - uploader = Uploader(Poetry.create(project("simple_project")), NullIO()) + uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") @@ -26,7 +26,7 @@ def test_uploader_properly_handles_400_errors(http): def test_uploader_properly_handles_403_errors(http): http.register_uri(http.POST, "https://foo.com", status=403, body="Unauthorized") - uploader = Uploader(Poetry.create(project("simple_project")), NullIO()) + uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError) as e: uploader.upload("https://foo.com") @@ -39,7 +39,7 @@ def test_uploader_registers_for_appropriate_400_errors(mocker, http): http.register_uri( http.POST, "https://foo.com", status=400, body="No package was ever registered" ) - uploader = Uploader(Poetry.create(project("simple_project")), NullIO()) + uploader = Uploader(Factory().create_poetry(project("simple_project")), NullIO()) with pytest.raises(UploadError): uploader.upload("https://foo.com") diff --git a/tests/test_poetry.py b/tests/test_factory.py similarity index 83% rename from tests/test_poetry.py rename to tests/test_factory.py index 1d1e298bc06..ae63054b08e 100644 --- a/tests/test_poetry.py +++ b/tests/test_factory.py @@ -4,7 +4,8 @@ import pytest -from poetry.poetry import Poetry +from poetry.io.null_io import NullIO +from poetry.factory import Factory from poetry.utils._compat import PY2 from poetry.utils._compat import Path from poetry.utils.toml_file import TomlFile @@ -13,8 +14,8 @@ fixtures_dir = Path(__file__).parent / "fixtures" -def test_poetry(): - poetry = Poetry.create(str(fixtures_dir / "sample_project")) +def test_create_poetry(): + poetry = Factory().create_poetry(fixtures_dir / "sample_project") package = poetry.package @@ -111,9 +112,9 @@ def test_poetry(): ] -def test_poetry_with_packages_and_includes(): - poetry = Poetry.create( - str(fixtures_dir.parent / "masonry" / "builders" / "fixtures" / "with-include") +def test_create_poetry_with_packages_and_includes(): + poetry = Factory().create_poetry( + fixtures_dir.parent / "masonry" / "builders" / "fixtures" / "with-include" ) package = poetry.package @@ -131,9 +132,9 @@ def test_poetry_with_packages_and_includes(): assert package.include == ["extra_dir/vcs_excluded.txt", "notes.txt"] -def test_poetry_with_multi_constraints_dependency(): - poetry = Poetry.create( - str(fixtures_dir / "project_with_multi_constraints_dependency") +def test_create_poetry_with_multi_constraints_dependency(): + poetry = Factory().create_poetry( + fixtures_dir / "project_with_multi_constraints_dependency" ) package = poetry.package @@ -142,26 +143,26 @@ def test_poetry_with_multi_constraints_dependency(): def test_poetry_with_default_source(): - poetry = Poetry.create(fixtures_dir / "with_default_source") + poetry = Factory().create_poetry(fixtures_dir / "with_default_source") assert 1 == len(poetry.pool.repositories) def test_poetry_with_two_default_sources(): with pytest.raises(ValueError) as e: - Poetry.create(fixtures_dir / "with_two_default_sources") + Factory().create_poetry(fixtures_dir / "with_two_default_sources") assert "Only one repository can be the default" == str(e.value) -def test_check(): +def test_validate(): complete = TomlFile(fixtures_dir / "complete.toml") content = complete.read()["tool"]["poetry"] - assert Poetry.check(content) == {"errors": [], "warnings": []} + assert Factory.validate(content) == {"errors": [], "warnings": []} -def test_check_fails(): +def test_validate_fails(): complete = TomlFile(fixtures_dir / "complete.toml") content = complete.read()["tool"]["poetry"] content["this key is not in the schema"] = "" @@ -177,12 +178,12 @@ def test_check_fails(): "('this key is not in the schema' was unexpected)" ) - assert Poetry.check(content) == {"errors": [expected], "warnings": []} + assert Factory.validate(content) == {"errors": [expected], "warnings": []} -def test_create_fails_on_invalid_configuration(): +def test_create_poetry_fails_on_invalid_configuration(): with pytest.raises(RuntimeError) as e: - Poetry.create( + Factory().create_poetry( Path(__file__).parent / "fixtures" / "invalid_pyproject" / "pyproject.toml" ) @@ -199,8 +200,8 @@ def test_create_fails_on_invalid_configuration(): assert expected == str(e.value) -def test_poetry_with_local_config(fixture_dir): - poetry = Poetry.create(fixture_dir("with_local_config")) +def test_create_poetry_with_local_config(fixture_dir): + poetry = Factory().create_poetry(fixture_dir("with_local_config")) assert not poetry.config.get("virtualenvs.in-project") assert not poetry.config.get("virtualenvs.create") diff --git a/tests/utils/test_exporter.py b/tests/utils/test_exporter.py index 4c67820c346..ad5be296bf4 100644 --- a/tests/utils/test_exporter.py +++ b/tests/utils/test_exporter.py @@ -2,8 +2,8 @@ import pytest +from poetry.factory import Factory from poetry.packages import Locker as BaseLocker -from poetry.poetry import Poetry from poetry.repositories.auth import Auth from poetry.repositories.legacy_repository import LegacyRepository from poetry.utils._compat import Path @@ -40,7 +40,7 @@ def locker(): @pytest.fixture def poetry(fixture_dir, locker): - p = Poetry.create(fixture_dir("sample_project")) + p = Factory().create_poetry(fixture_dir("sample_project")) p._locker = locker return p