def __init__( self, path, # type: Path source_root=None, # type: Optional[Path] ): """ :param path: a full path to the file to be included :param source_root: the root path to resolve to """ self.path = Path(path) self.source_root = None if not source_root else Path( source_root).resolve() if not self.path.is_absolute() and self.source_root: self.path = (self.source_root / self.path).resolve() else: self.path = self.path.resolve()
def test_metadata_homepage_default(): builder = Builder(Factory().create_poetry( Path(__file__).parent / "fixtures" / "simple_version")) metadata = Parser().parsestr(builder.get_metadata_content()) assert metadata["Home-page"] is None
def get_vcs(directory): # type: (Path) -> Git working_dir = Path.cwd() os.chdir(str(directory.resolve())) try: git_dir = decode( subprocess.check_output(["git", "rev-parse", "--show-toplevel"], stderr=subprocess.STDOUT)).strip() vcs = Git(Path(git_dir)) except (subprocess.CalledProcessError, OSError): vcs = None finally: os.chdir(str(working_dir)) return vcs
def test_directory_dependency_pep_508_local_absolute(): path = (Path(__file__).parent.parent / "fixtures" / "project_with_multi_constraints_dependency") requirement = "{} @ file://{}".format("demo", path.as_posix()) _test_directory_dependency_pep_508("demo", path, requirement) requirement = "{} @ {}".format("demo", path) _test_directory_dependency_pep_508("demo", path, requirement)
def test_builder_find_excluded_files(mocker): p = mocker.patch("poetry.core.vcs.git.Git.get_ignored_files") p.return_value = [] builder = Builder( Factory().create_poetry(Path(__file__).parent / "fixtures" / "complete") ) assert builder.find_excluded_files() == {"my_package/sub_pkg1/extra_file.xml"}
def test_default_with_excluded_data(mocker): # Patch git module to return specific excluded files p = mocker.patch("poetry.core.vcs.git.Git.get_ignored_files") p.return_value = [ ( ( Path(__file__).parent / "fixtures" / "default_with_excluded_data" / "my_package" / "data" / "sub_data" / "data2.txt" ) .relative_to(project("default_with_excluded_data")) .as_posix() ) ] poetry = Factory().create_poetry(project("default_with_excluded_data")) builder = SdistBuilder(poetry) # Check setup.py setup = builder.build_setup() setup_ast = ast.parse(setup) setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)] ns = {} exec(compile(setup_ast, filename="setup.py", mode="exec"), ns) assert "package_dir" not in ns assert ns["packages"] == ["my_package"] assert ns["package_data"] == { "": ["*"], "my_package": ["data/*", "data/sub_data/data3.txt"], } builder.build() sdist = ( fixtures_dir / "default_with_excluded_data" / "dist" / "my-package-1.2.3.tar.gz" ) assert sdist.exists() with tarfile.open(str(sdist), "r") as tar: names = tar.getnames() assert len(names) == len(set(names)) assert "my-package-1.2.3/LICENSE" in names assert "my-package-1.2.3/README.rst" in names assert "my-package-1.2.3/my_package/__init__.py" in names assert "my-package-1.2.3/my_package/data/data1.txt" in names assert "my-package-1.2.3/pyproject.toml" in names assert "my-package-1.2.3/setup.py" in names assert "my-package-1.2.3/PKG-INFO" in names # all last modified times should be set to a valid timestamp for tarinfo in tar.getmembers(): assert 0 < tarinfo.mtime
def test_builder_find_invalid_case_sensitive_excluded_files(mocker): p = mocker.patch("poetry.core.vcs.git.Git.get_ignored_files") p.return_value = [] builder = Builder(Factory().create_poetry( Path(__file__).parent / "fixtures" / "invalid_case_sensitive_exclusions")) assert {"my_package/Bar/foo/bar/Foo.py"} == builder.find_excluded_files()
def test_metadata_with_vcs_dependencies(): builder = Builder(Factory().create_poetry( Path(__file__).parent / "fixtures" / "with_vcs_dependency")) metadata = Parser().parsestr(builder.get_metadata_content()) requires_dist = metadata["Requires-Dist"] assert "cleo @ git+https://github.com/sdispater/cleo.git@master" == requires_dist
def test_to_dependency_for_directory(): path = Path(__file__).parent.parent.joinpath("fixtures/simple_project") package = Package( "foo", "1.2.3", source_type="directory", source_url=path.as_posix(), features=["baz", "bar"], ) dep = package.to_dependency() assert "foo" == dep.name assert package.version == dep.constraint assert frozenset({"bar", "baz"}) == dep.features assert dep.is_directory() assert path == dep.path assert "directory" == dep.source_type assert path.as_posix() == dep.source_url
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): poetry = Factory().create_poetry(Path(".").resolve()) builder = WheelBuilder(poetry) dist_info = Path(metadata_directory, builder.dist_info) dist_info.mkdir(parents=True, exist_ok=True) if "scripts" in poetry.local_config or "plugins" in poetry.local_config: with (dist_info / "entry_points.txt").open("w", encoding="utf-8") as f: builder._write_entry_points(f) with (dist_info / "WHEEL").open("w", encoding="utf-8") as f: builder._write_wheel_file(f) with (dist_info / "METADATA").open("w", encoding="utf-8") as f: builder._write_metadata_file(f) return dist_info.name
def test_to_dependency_for_file(): path = Path(__file__).parent.parent.joinpath( "fixtures/distributions/demo-0.1.0.tar.gz") package = Package( "foo", "1.2.3", source_type="file", source_url=path.as_posix(), features=["baz", "bar"], ) dep = package.to_dependency() assert "foo" == dep.name assert package.version == dep.constraint assert frozenset({"bar", "baz"}) == dep.features assert dep.is_file() assert path == dep.path assert "file" == dep.source_type assert path.as_posix() == dep.source_url
def find_nearest_pkg(rel_path): parts = rel_path.split(os.sep) for i in reversed(range(1, len(parts))): ancestor = "/".join(parts[:i]) if ancestor in subpkg_paths: pkg = ".".join([pkg_name] + parts[:i]) return pkg, "/".join(parts[i:]) # Relative to the top-level package return pkg_name, Path(rel_path).as_posix()
def test_pip_wheel_build(temporary_directory, project_source_root): tmp = str(temporary_directory) pip = subprocess_run("pip", "wheel", "--use-pep517", "-w", tmp, str(project_source_root)) assert "Successfully built poetry-core" in pip.stdout assert pip.returncode == 0 wheels = list(Path(tmp).glob("poetry_core-*-none-any.whl")) assert len(wheels) == 1
def unpacked_tarball(cls, path): tf = tarfile.open(str(path)) with cls.temporary_directory() as tmpdir: tf.extractall(tmpdir) files = os.listdir(tmpdir) assert len(files) == 1, files yield Path(tmpdir) / files[0]
def test_pyproject_toml_no_build_system_defaults(): pyproject_toml = (Path(__file__).parent.parent / "fixtures" / "project_with_build_system_requires" / "pyproject.toml") build_system = PyProjectTOML(pyproject_toml).build_system assert build_system.requires == ["poetry-core", "Cython~=0.29.6"] assert len(build_system.dependencies) == 2 assert build_system.dependencies[0].to_pep_508() == "poetry-core" assert build_system.dependencies[1].to_pep_508( ) == "Cython (>=0.29.6,<0.30.0)"
def _make_file_or_dir_dep( name, path, base=None ): # type: (str, Path, Optional[Path]) -> Optional[Union[FileDependency, DirectoryDependency]] """ Helper function to create a file or directoru dependency with the given arguments. If path is not a file or directory that exists, `None` is returned. """ _path = path if not path.is_absolute() and base: # a base path was specified, so we should respect that _path = Path(base) / path if _path.is_file(): return FileDependency(name, path, base=base) elif _path.is_dir(): return DirectoryDependency(name, path, base=base) return None
def _test_directory_dependency_pep_508(name, path, pep_508_input, pep_508_output=None): dep = dependency_from_pep_508(pep_508_input, relative_to=Path(__file__).parent) assert dep.is_directory() assert dep.name == name assert dep.path == path assert dep.to_pep_508() == pep_508_output or pep_508_input
def test_url_to_path(url, win_expected, non_win_expected): if sys.platform == "win32": expected_path = win_expected else: expected_path = non_win_expected if expected_path is None: with pytest.raises(ValueError): url_to_path(url) else: assert url_to_path(url) == Path(expected_path)
def test_metadata_with_url_dependencies(): builder = Builder(Factory().create_poetry( Path(__file__).parent / "fixtures" / "with_url_dependency")) metadata = Parser().parsestr(builder.get_metadata_content()) requires_dist = metadata["Requires-Dist"] assert ( "demo @ https://python-poetry.org/distributions/demo-0.1.0-py2.py3-none-any.whl" == requires_dist)
def test_find_files_to_add(): poetry = Factory().create_poetry(project("complete")) builder = SdistBuilder(poetry) result = [f.relative_to_source_root() for f in builder.find_files_to_add()] assert sorted(result) == sorted([ Path("LICENSE"), Path("README.rst"), Path("my_package/__init__.py"), Path("my_package/data1/test.json"), Path("my_package/sub_pkg1/__init__.py"), Path("my_package/sub_pkg2/__init__.py"), Path("my_package/sub_pkg2/data2/data.json"), Path("my_package/sub_pkg3/foo.py"), Path("pyproject.toml"), ])
def _test_file_dependency_pep_508( mocker, name, path, pep_508_input, pep_508_output=None ): mocker.patch.object(Path, "exists").return_value = True mocker.patch.object(Path, "is_file").return_value = True dep = dependency_from_pep_508(pep_508_input, relative_to=Path(__file__).parent) assert dep.is_file() assert dep.name == name assert dep.path == path assert dep.to_pep_508() == pep_508_output or pep_508_input
def executable(): global _executable if _executable is not None: return _executable if WINDOWS and PY36: # Finding git via where.exe where = "%WINDIR%\\System32\\where.exe" paths = decode( subprocess.check_output([where, "git"], shell=True, encoding="oem")).split("\n") for path in paths: if not path: continue path = Path(path.strip()) try: path.relative_to(Path.cwd()) except ValueError: _executable = str(path) break else: _executable = "git" if _executable is None: raise RuntimeError("Unable to find a valid git executable") return _executable
def __init__( self, name, # type: str path, # type: Path category="main", # type: str optional=False, # type: bool base=None, # type: Path develop=False, # type: bool extras=None, # type: Union[List[str], FrozenSet[str]] ): self._path = path self._base = base or Path.cwd() self._full_path = path if not self._path.is_absolute(): try: self._full_path = self._base.joinpath(self._path).resolve() except FileNotFoundError: raise ValueError("Directory {} does not exist".format(self._path)) self._develop = develop self._supports_poetry = False if not self._full_path.exists(): raise ValueError("Directory {} does not exist".format(self._path)) if self._full_path.is_file(): raise ValueError("{} is a file, expected a directory".format(self._path)) # Checking content to determine actions setup = self._full_path / "setup.py" self._supports_poetry = PyProjectTOML( self._full_path / "pyproject.toml" ).is_poetry_project() if not setup.exists() and not self._supports_poetry: raise ValueError( "Directory {} does not seem to be a Python package".format( self._full_path ) ) super(DirectoryDependency, self).__init__( name, "*", category=category, optional=optional, allows_prereleases=True, source_type="directory", source_url=self._full_path.as_posix(), extras=extras, )
def test_get_metadata_content(): builder = Builder(Factory().create_poetry( Path(__file__).parent / "fixtures" / "complete")) metadata = builder.get_metadata_content() p = Parser() parsed = p.parsestr(metadata) assert parsed["Metadata-Version"] == "2.1" assert parsed["Name"] == "my-package" assert parsed["Version"] == "1.2.3" assert parsed["Summary"] == "Some description." assert parsed["Author"] == "Sébastien Eustace" assert parsed["Author-email"] == "*****@*****.**" assert parsed["Keywords"] == "packaging,dependency,poetry" assert parsed["Requires-Python"] == ">=3.6,<4.0" assert parsed["License"] == "MIT" assert parsed["Home-page"] == "https://python-poetry.org/" classifiers = parsed.get_all("Classifier") assert classifiers == [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Libraries :: Python Modules", ] extras = parsed.get_all("Provides-Extra") assert extras == ["time"] requires = parsed.get_all("Requires-Dist") assert requires == [ "cachy[msgpack] (>=0.2.0,<0.3.0)", "cleo (>=0.6,<0.7)", 'pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time")', ] urls = parsed.get_all("Project-URL") assert urls == [ "Documentation, https://python-poetry.org/docs", "Issue Tracker, https://github.com/python-poetry/poetry/issues", "Repository, https://github.com/python-poetry/poetry", ]
def test_make_pkg_info_multi_constraints_dependency(): poetry = Factory().create_poetry( Path(__file__).parent.parent.parent / "fixtures" / "project_with_multi_constraints_dependency") builder = SdistBuilder(poetry) pkg_info = builder.build_pkg_info() p = Parser() parsed = p.parsestr(to_str(pkg_info)) requires = parsed.get_all("Requires-Dist") assert requires == [ 'pendulum (>=1.5,<2.0); python_version < "3.4"', 'pendulum (>=2.0,<3.0); python_version >= "3.4" and python_version < "4.0"', ]
class BuildIncludeFile: def __init__( self, path, # type: Path source_root=None, # type: Optional[Path] ): """ :param path: a full path to the file to be included :param source_root: the root path to resolve to """ self.path = Path(path) self.source_root = None if not source_root else Path( source_root).resolve() if not self.path.is_absolute() and self.source_root: self.path = (self.source_root / self.path).resolve() else: self.path = self.path.resolve() def __eq__(self, other): # type: (Union[BuildIncludeFile, Path]) -> bool if hasattr(other, "path"): return self.path == other.path return self.path == other def __ne__(self, other): # type: (Union[BuildIncludeFile, Path]) -> bool return not self.__eq__(other) def __hash__(self): return hash(self.path) def __repr__(self): # type: () -> str return str(self.path) def relative_to_source_root(self): # type(): -> Path if self.source_root is not None: return self.path.relative_to(self.source_root) return self.path
def __init__( self, poetry, ignore_packages_formats=False, executable=None ): # type: ("Poetry", bool, Optional[Union[Path, str]]) -> None self._poetry = poetry self._package = poetry.package self._path = poetry.file.parent self._original_path = self._path self._excluded_files = None self._executable = Path(executable or sys.executable) # type: Path packages = [] for p in self._package.packages: formats = p.get("format", []) if not isinstance(formats, list): formats = [formats] if ( formats and self.format and self.format not in formats and not ignore_packages_formats ): continue packages.append(p) includes = [] for include in self._package.include: formats = include.get("format", []) if ( formats and self.format and self.format not in formats and not ignore_packages_formats ): continue includes.append(include) self._module = Module( self._package.name, self._path.as_posix(), packages=packages, includes=includes, ) self._meta = Metadata.from_package(self._package)
def test_builder_find_case_sensitive_excluded_files(mocker): p = mocker.patch("poetry.core.vcs.git.Git.get_ignored_files") p.return_value = [] builder = Builder(Factory().create_poetry( Path(__file__).parent / "fixtures" / "case_sensitive_exclusions")) assert builder.find_excluded_files() == { "my_package/FooBar/Bar.py", "my_package/FooBar/lowercasebar.py", "my_package/Foo/SecondBar.py", "my_package/Foo/Bar.py", "my_package/Foo/lowercasebar.py", "my_package/bar/foo.py", "my_package/bar/CapitalFoo.py", }
def _find_dist_info(path): # type: (Path) -> Iterator[Path] """ Discover all `*.*-info` directories in a given path. :param path: Path to search. """ pattern = "**/*.*-info" if PY35: # Sometimes pathlib will fail on recursive symbolic links, so we need to workaround it # and use the glob module instead. Note that this does not happen with pathlib2 # so it's safe to use it for Python < 3.4. directories = glob.iglob(path.joinpath(pattern).as_posix(), recursive=True) else: directories = path.glob(pattern) for d in directories: yield Path(d)
def test_create_poetry_fails_on_invalid_configuration(): with pytest.raises(RuntimeError) as e: Factory().create_poetry( Path(__file__).parent / "fixtures" / "invalid_pyproject" / "pyproject.toml") if PY2: expected = """\ The Poetry configuration is invalid: - u'description' is a required property """ else: expected = """\ The Poetry configuration is invalid: - 'description' is a required property """ assert expected == str(e.value)