def test_setup_reader_read_first_level_setup_call_with_variables(setup): result = SetupReader.read_from_directory(setup("requests")) expected_name = None expected_version = None expected_install_requires = [ "chardet>=3.0.2,<3.1.0", "idna>=2.5,<2.8", "urllib3>=1.21.1,<1.25", "certifi>=2017.4.17", ] expected_extras_require = { "security": ["pyOpenSSL >= 0.14", "cryptography>=1.3.4", "idna>=2.0.0"], "socks": ["PySocks>=1.5.6, !=1.5.7"], 'socks:sys_platform == "win32" and python_version == "2.7"': ["win_inet_pton"], } expected_python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" assert expected_name == result["name"] assert expected_version == result["version"] assert expected_install_requires == result["install_requires"] assert expected_extras_require == result["extras_require"] assert expected_python_requires == result["python_requires"]
def test_setup_reader_read_first_level_setup_call_with_direct_types(setup): result = SetupReader.read_from_directory(setup("flask")) expected_name = "Flask" expected_version = None expected_install_requires = [ "Werkzeug>=0.14", "Jinja2>=2.10", "itsdangerous>=0.24", "click>=5.1", ] expected_extras_require = { "dotenv": ["python-dotenv"], "dev": [ "pytest>=3", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", ], "docs": ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet"], } expected_python_requires = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" assert expected_name == result["name"] assert expected_version == result["version"] assert expected_install_requires == result["install_requires"] assert expected_extras_require == result["extras_require"] assert expected_python_requires == result["python_requires"]
def test_setup_reader_setuptools(setup: Callable[[str], str]): result = SetupReader.read_from_directory(setup("setuptools_setup")) expected_name = "my_package" expected_version = "0.1.2" assert result["name"] == expected_name assert result["version"] == expected_version
def test_setup_reader_setuptools(setup): result = SetupReader.read_from_directory(setup("setuptools_setup")) expected_name = "my_package" expected_version = "0.1.2" assert expected_name == result["name"] assert expected_version == result["version"]
def from_setup_files(cls, path): # type: (Path) -> PackageInfo """ Mechanism to parse package information from a `setup.[py|cfg]` file. This uses the implementation at `poetry.utils.setup_reader.SetupReader` in order to parse the file. This is not reliable for complex setup files and should only attempted as a fallback. :param path: Path to `setup.py` file """ if not cls.has_setup_files(path): raise PackageInfoError( path, "No setup files (setup.py, setup.cfg) was found." ) try: result = SetupReader.read_from_directory(path) except Exception as e: raise PackageInfoError(path, e) python_requires = result["python_requires"] if python_requires is None: python_requires = "*" requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items(): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" requirements = parse_requires(requires) info = cls( name=result.get("name"), version=result.get("version"), summary=result.get("description", ""), requires_dist=requirements or None, requires_python=python_requires, ) if not (info.name and info.version) and not info.requires_dist: # there is nothing useful here raise PackageInfoError( path, "No core metadata (name, version, requires-dist) could be retrieved.", ) return info
def test_setup_reader_read_setup_kwargs(setup): result = SetupReader.read_from_directory(setup("pendulum")) expected_name = "pendulum" expected_version = "2.0.4" expected_install_requires = ["python-dateutil>=2.6,<3.0", "pytzdata>=2018.3"] expected_extras_require = {':python_version < "3.5"': ["typing>=3.6,<4.0"]} expected_python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" assert expected_name == result["name"] assert expected_version == result["version"] assert expected_install_requires == result["install_requires"] assert expected_extras_require == result["extras_require"] assert expected_python_requires == result["python_requires"]
def test_setup_reader_read_setup_call_in_main(setup): result = SetupReader.read_from_directory(setup("pyyaml")) expected_name = "PyYAML" expected_version = "3.13" expected_install_requires = [] expected_extras_require = {} expected_python_requires = None assert expected_name == result["name"] assert expected_version == result["version"] assert expected_install_requires == result["install_requires"] assert expected_extras_require == result["extras_require"] assert expected_python_requires == result["python_requires"]
def test_setup_reader_read_extras_require_with_variables(setup): result = SetupReader.read_from_directory(setup("extras_require_with_vars")) expected_name = "extras_require_with_vars" expected_version = "0.0.1" expected_install_requires = [] expected_extras_require = {"test": ["pytest"]} expected_python_requires = None assert expected_name == result["name"] assert expected_version == result["version"] assert expected_install_requires == result["install_requires"] assert expected_extras_require == result["extras_require"] assert expected_python_requires == result["python_requires"]
def test_setup_reader_read_setup_cfg(setup): result = SetupReader.read_from_directory(setup("with-setup-cfg")) expected_name = "with-setup-cfg" expected_version = "1.2.3" expected_install_requires = ["six", "tomlkit"] expected_extras_require = { "validation": ["cerberus"], "tests": ["pytest", "pytest-xdist", "pytest-cov"], } expected_python_requires = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3" assert expected_name == result["name"] assert expected_version == result["version"] assert expected_install_requires == result["install_requires"] assert expected_extras_require == result["extras_require"] assert expected_python_requires == result["python_requires"]
def test_setup_reader_read_sub_level_setup_call_with_direct_types(setup): result = SetupReader.read_from_directory(setup("sqlalchemy")) expected_name = "SQLAlchemy" expected_version = None expected_install_requires = [] expected_extras_require = { "mysql": ["mysqlclient"], "pymysql": ["pymysql"], "postgresql": ["psycopg2"], "postgresql_pg8000": ["pg8000"], "postgresql_psycopg2cffi": ["psycopg2cffi"], "oracle": ["cx_oracle"], "mssql_pyodbc": ["pyodbc"], "mssql_pymssql": ["pymssql"], } assert expected_name == result["name"] assert expected_version == result["version"] assert expected_install_requires == result["install_requires"] assert expected_extras_require == result["extras_require"] assert result["python_requires"] is None
def _inspect_sdist_with_setup(self, sdist_dir): info = {"requires_python": None, "requires_dist": None} result = SetupReader.read_from_directory(sdist_dir) requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items(): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" info["requires_dist"] = parse_requires(requires) info["requires_python"] = result["python_requires"] return info
def get_package_from_directory( cls, directory, name=None): # type: (Path, Optional[str]) -> Package supports_poetry = False pyproject = directory.joinpath("pyproject.toml") if pyproject.exists(): pyproject = TomlFile(pyproject) pyproject_content = pyproject.read() supports_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if supports_poetry: poetry = Factory().create_poetry(directory) pkg = poetry.package package = Package(pkg.name, pkg.version) for dep in pkg.requires: if not dep.is_optional(): package.requires.append(dep) for extra, deps in pkg.extras.items(): if extra not in package.extras: package.extras[extra] = [] for dep in deps: package.extras[extra].append(dep) package.python_versions = pkg.python_versions else: # Execute egg_info current_dir = os.getcwd() os.chdir(str(directory)) try: cwd = directory venv = EnvManager().get(cwd) venv.run("python", "setup.py", "egg_info") except EnvCommandError: result = SetupReader.read_from_directory(directory) if not result["name"]: # The name could not be determined # We use the dependency name result["name"] = name if not result["version"]: # The version could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the package version for {}".format( directory)) package_name = result["name"] package_version = result["version"] python_requires = result["python_requires"] if python_requires is None: python_requires = "*" package_summary = "" requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items(): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" reqs = parse_requires(requires) else: os.chdir(current_dir) # 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. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(directory), "**", "*.egg-info"), recursive=True, )) else: egg_info = next(directory.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) package_name = meta.name package_version = meta.version package_summary = meta.summary python_requires = meta.requires_python if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open(encoding="utf-8") as f: reqs = parse_requires(f.read()) finally: os.chdir(current_dir) package = Package(package_name, package_version) package.description = package_summary for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) if not dep.is_optional(): package.requires.append(dep) if python_requires: package.python_versions = python_requires if name and name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}" .format(name, package.name)) package.source_type = "directory" package.source_url = directory.as_posix() return package
def search_for_directory( self, dependency): # type: (DirectoryDependency) -> List[Package] if dependency.supports_poetry(): from poetry.poetry import Poetry poetry = Poetry.create(dependency.full_path) pkg = poetry.package package = Package(pkg.name, pkg.version) for dep in pkg.requires: if not dep.is_optional(): package.requires.append(dep) for extra, deps in pkg.extras.items(): if extra not in package.extras: package.extras[extra] = [] for dep in deps: package.extras[extra].append(dep) package.python_versions = pkg.python_versions else: # Execute egg_info current_dir = os.getcwd() os.chdir(str(dependency.full_path)) try: cwd = dependency.full_path venv = Env.get(NullIO(), cwd=cwd) venv.run("python", "setup.py", "egg_info") except EnvCommandError: result = SetupReader.read_from_directory(dependency.full_path) if not result["name"]: # The name could not be determined # We use the dependency name result["name"] = dependency.name if not result["version"]: # The version could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the package version for {}".format( dependency.path)) package_name = result["name"] package_version = result["version"] python_requires = result["python_requires"] if python_requires is None: python_requires = "*" package_summary = "" requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items(): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" reqs = parse_requires(requires) else: os.chdir(current_dir) # 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. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(dependency.full_path), "**", "*.egg-info"), recursive=True, )) else: egg_info = next(dependency.full_path.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) package_name = meta.name package_version = meta.version package_summary = meta.summary python_requires = meta.requires_python if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) finally: os.chdir(current_dir) package = Package(package_name, package_version) if dependency.name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}" .format(dependency.name, package.name)) package.description = package_summary for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) if not dep.is_optional(): package.requires.append(dep) if python_requires: package.python_versions = python_requires package.source_type = "directory" package.source_url = dependency.path.as_posix() for extra in dependency.extras: if extra in package.extras: for dep in package.extras[extra]: dep.activate() package.requires += package.extras[extra] return [package]
def __init__( self, path, # type: Path category="main", # type: str optional=False, # type: bool base=None, # type: Path develop=True, # type: bool ): from . import dependency_from_pep_508 from .package import Package self._path = path self._base = base self._full_path = path self._develop = develop self._supports_poetry = False if self._base and not self._path.is_absolute(): self._full_path = self._base / self._path 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 dertermine actions setup = self._full_path / "setup.py" pyproject = TomlFile(self._full_path / "pyproject.toml") if pyproject.exists(): pyproject_content = pyproject.read() self._supports_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if not setup.exists() and not self._supports_poetry: raise ValueError( "Directory {} does not seem to be a Python package".format( self._full_path)) if self._supports_poetry: from poetry.poetry import Poetry poetry = Poetry.create(self._full_path) package = poetry.package self._package = Package(package.pretty_name, package.version) self._package.requires += package.requires self._package.dev_requires += package.dev_requires self._package.extras = package.extras self._package.python_versions = package.python_versions else: # Execute egg_info current_dir = os.getcwd() os.chdir(str(self._full_path)) try: cwd = base venv = Env.get(NullIO(), cwd=cwd) venv.run("python", "setup.py", "egg_info") except EnvCommandError: result = SetupReader.read_from_directory(self._full_path) if not result["name"]: # The name could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the package name for {}".format( path)) if not result["version"]: # The version could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the package version for {}".format( path)) package_name = result["name"] package_version = result["version"] python_requires = result["python_requires"] if python_requires is None: python_requires = "*" package_summary = "" requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items(): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" reqs = parse_requires(requires) else: os.chdir(current_dir) # 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. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(self._full_path), "**", "*.egg-info"), recursive=True, )) else: egg_info = next(self._full_path.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) package_name = meta.name package_version = meta.version package_summary = meta.summary python_requires = meta.requires_python if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) finally: os.chdir(current_dir) package = Package(package_name, package_version) package.description = package_summary for req in reqs: package.requires.append(dependency_from_pep_508(req)) if python_requires: package.python_versions = python_requires self._package = package self._package.source_type = "directory" self._package.source_url = self._path.as_posix() super(DirectoryDependency, self).__init__( self._package.name, self._package.version, category=category, optional=optional, allows_prereleases=True, )
def test_setup_reader_read_setup_cfg_with_attr(setup): with pytest.raises(ParseVersionError): SetupReader.read_from_directory(setup("with-setup-cfg-attr"))
def test_setup_reader_read_setup_cfg_with_attr(setup: Callable[[str], str]): with pytest.raises(InvalidVersion): SetupReader.read_from_directory(setup("with-setup-cfg-attr"))
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] """ Search for the specifications that match the given VCS dependency. Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ if dependency.vcs != "git": raise ValueError("Unsupported VCS dependency {}".format( dependency.vcs)) tmp_dir = Path( mkdtemp(prefix="pypoetry-git-{}".format(dependency.name))) try: git = Git() git.clone(dependency.source, tmp_dir) git.checkout(dependency.reference, tmp_dir) revision = git.rev_parse(dependency.reference, tmp_dir).strip() if dependency.tag or dependency.rev: revision = dependency.reference pyproject = TomlFile(tmp_dir / "pyproject.toml") pyproject_content = None has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if pyproject_content and has_poetry: # If a pyproject.toml file exists # We use it to get the information we need info = pyproject_content["tool"]["poetry"] name = info["name"] version = info["version"] package = Package(name, version, version) package.source_type = dependency.vcs package.source_url = dependency.source package.source_reference = dependency.reference for req_name, req_constraint in info["dependencies"].items(): if req_name == "python": package.python_versions = req_constraint continue package.add_dependency(req_name, req_constraint) else: # We need to use setup.py here # to figure the information we need # We need to place ourselves in the proper # folder for it to work venv = Env.get(self._io) current_dir = os.getcwd() os.chdir(tmp_dir.as_posix()) try: try: venv.run("python", "setup.py", "egg_info") except EnvCommandError: # Most likely an error with the egg_info command self.debug( "<warning>Error executing the egg_info command. Reading setup files.</warning>" ) result = SetupReader.read_from_directory(tmp_dir) if not result["name"]: result["name"] = dependency.name if not result["version"]: # The version could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the version for {}".format( dependency.name)) package_name = result["name"] package_version = result["version"] python_requires = result["python_requires"] requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items( ): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" reqs = parse_requires(requires) else: # 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. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(tmp_dir), "**", "*.egg-info"), recursive=True, )) else: egg_info = next(tmp_dir.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) package_name = meta.name package_version = meta.version python_requires = meta.requires_python if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) package = Package(package_name, package_version) if python_requires: package.python_versions = python_requires for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) package.requires.append(dep) except Exception: raise finally: os.chdir(current_dir) package.source_type = "git" package.source_url = dependency.source package.source_reference = revision except Exception: raise finally: shutil.rmtree(tmp_dir.as_posix()) if dependency.name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}" .format(dependency.name, package.name)) if dependency.extras: for extra in dependency.extras: if extra in package.extras: for dep in package.extras[extra]: dep.activate() return [package]