Skip to content

Commit

Permalink
Refactor code to support multiple project type
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Aug 13, 2022
1 parent f49d12f commit 4f3a023
Show file tree
Hide file tree
Showing 16 changed files with 539 additions and 247 deletions.
176 changes: 12 additions & 164 deletions src/poetry/core/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,20 @@
from typing import cast
from warnings import warn

from poetry.core.utils.helpers import combine_unicode
from poetry.core.pyproject.formats.content_format import ContentFormat
from poetry.core.utils.helpers import readme_content_type


if TYPE_CHECKING:
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.dependency_group import DependencyGroup
from poetry.core.packages.project_package import ProjectPackage
from poetry.core.poetry import Poetry
from poetry.core.spdx.license import License

DependencyConstraint = Union[str, Dict[str, Any]]
DependencyConfig = Mapping[
str, Union[List[DependencyConstraint], DependencyConstraint]
]


logger = logging.getLogger(__name__)


Expand All @@ -44,182 +41,33 @@ def create_poetry(
from poetry.core.pyproject.toml import PyProjectTOML

poetry_file = self.locate(cwd)
local_config = PyProjectTOML(path=poetry_file).poetry_config
pyproject = PyProjectTOML(path=poetry_file)

if not pyproject.is_poetry_project():
raise RuntimeError(f"The project at {poetry_file} is not a Poetry project")

# Checking validity
check_result = self.validate(local_config)
if check_result["errors"]:
content_format = cast(ContentFormat, pyproject.content_format)
check_result = content_format.validate(strict=False)
if check_result.errors:
message = ""
for error in check_result["errors"]:
for error in check_result.errors:
message += f" - {error}\n"

raise RuntimeError("The Poetry configuration is invalid:\n" + message)

# Load package
name = cast(str, local_config["name"])
version = cast(str, local_config["version"])
package = self.get_package(name, version)
package = self.configure_package(
package, local_config, poetry_file.parent, with_groups=with_groups
package = content_format.to_package(
root=poetry_file.parent, with_groups=with_groups
)

return Poetry(poetry_file, local_config, package)
return Poetry(poetry_file, pyproject.poetry_config, package)

@classmethod
def get_package(cls, name: str, version: str) -> ProjectPackage:
from poetry.core.packages.project_package import ProjectPackage

return ProjectPackage(name, version, version)

@classmethod
def _add_package_group_dependencies(
cls,
package: ProjectPackage,
group: str | DependencyGroup,
dependencies: DependencyConfig,
) -> None:
from poetry.core.packages.dependency_group import MAIN_GROUP

if isinstance(group, str):
if package.has_dependency_group(group):
group = package.dependency_group(group)
else:
from poetry.core.packages.dependency_group import DependencyGroup

group = DependencyGroup(group)

for name, constraints in dependencies.items():
_constraints = (
constraints if isinstance(constraints, list) else [constraints]
)
for _constraint in _constraints:
if name.lower() == "python":
if group.name == MAIN_GROUP and isinstance(_constraint, str):
package.python_versions = _constraint
continue

group.add_dependency(
cls.create_dependency(
name,
_constraint,
groups=[group.name],
root_dir=package.root_dir,
)
)

package.add_dependency_group(group)

@classmethod
def configure_package(
cls,
package: ProjectPackage,
config: dict[str, Any],
root: Path,
with_groups: bool = True,
) -> ProjectPackage:
from poetry.core.packages.dependency import Dependency
from poetry.core.packages.dependency_group import MAIN_GROUP
from poetry.core.packages.dependency_group import DependencyGroup
from poetry.core.spdx.helpers import license_by_id

package.root_dir = root

for author in config["authors"]:
package.authors.append(combine_unicode(author))

for maintainer in config.get("maintainers", []):
package.maintainers.append(combine_unicode(maintainer))

package.description = config.get("description", "")
package.homepage = config.get("homepage")
package.repository_url = config.get("repository")
package.documentation_url = config.get("documentation")
try:
license_: License | None = license_by_id(config.get("license", ""))
except ValueError:
license_ = None

package.license = license_
package.keywords = config.get("keywords", [])
package.classifiers = config.get("classifiers", [])

if "readme" in config:
if isinstance(config["readme"], str):
package.readmes = (root / config["readme"],)
else:
package.readmes = tuple(root / readme for readme in config["readme"])

if "platform" in config:
package.platform = config["platform"]

if "dependencies" in config:
cls._add_package_group_dependencies(
package=package, group=MAIN_GROUP, dependencies=config["dependencies"]
)

if with_groups and "group" in config:
for group_name, group_config in config["group"].items():
group = DependencyGroup(
group_name, optional=group_config.get("optional", False)
)
cls._add_package_group_dependencies(
package=package,
group=group,
dependencies=group_config["dependencies"],
)

if with_groups and "dev-dependencies" in config:
cls._add_package_group_dependencies(
package=package, group="dev", dependencies=config["dev-dependencies"]
)

extras = 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 config:
build = config["build"]
if not isinstance(build, dict):
build = {"script": build}
package.build_config = build or {}

if "include" in config:
package.include = []

for include in config["include"]:
if not isinstance(include, dict):
include = {"path": include}

formats = include.get("format", [])
if formats and not isinstance(formats, list):
formats = [formats]
include["format"] = formats

package.include.append(include)

if "exclude" in config:
package.exclude = config["exclude"]

if "packages" in config:
package.packages = config["packages"]

# Custom urls
if "urls" in config:
package.custom_urls = config["urls"]

return package

@classmethod
def create_dependency(
cls,
Expand Down
2 changes: 1 addition & 1 deletion src/poetry/core/masonry/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def prepare_metadata_for_build_wheel(
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:
if poetry.package.scripts or poetry.package.entrypoints:
with (dist_info / "entry_points.txt").open("w", encoding="utf-8") as f:
builder._write_entry_points(f)

Expand Down
9 changes: 3 additions & 6 deletions src/poetry/core/masonry/builders/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
if TYPE_CHECKING:
from poetry.core.poetry import Poetry


AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+) <(?P<email>.+?)>$")

METADATA_BASE = """\
Expand Down Expand Up @@ -280,7 +279,7 @@ def convert_entry_points(self) -> dict[str, list[str]]:
result = defaultdict(list)

# Scripts -> Entry points
for name, specification in self._poetry.local_config.get("scripts", {}).items():
for name, specification in self._poetry.package.scripts.items():
if isinstance(specification, str):
# TODO: deprecate this in favour or reference
specification = {"reference": specification, "type": "console"}
Expand All @@ -306,9 +305,7 @@ def convert_entry_points(self) -> dict[str, list[str]]:
if reference:
result["console_scripts"].append(f"{name} = {reference}{extras}")

# Plugins -> entry points
plugins = self._poetry.local_config.get("plugins", {})
for groupname, group in plugins.items():
for groupname, group in self._poetry.package.entrypoints.items():
for name, specification in sorted(group.items()):
result[groupname].append(f"{name} = {specification}")

Expand All @@ -320,7 +317,7 @@ def convert_entry_points(self) -> dict[str, list[str]]:
def convert_script_files(self) -> list[Path]:
script_files: list[Path] = []

for name, specification in self._poetry.local_config.get("scripts", {}).items():
for name, specification in self._poetry.package.scripts.items():
if isinstance(specification, dict) and specification.get("type") == "file":
source = specification["reference"]

Expand Down
4 changes: 2 additions & 2 deletions src/poetry/core/masonry/builders/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,8 @@ def find_files_to_add(self, exclude_build: bool = False) -> set[BuildIncludeFile
additional_files.add(Path("pyproject.toml"))

# add readme if it is specified
if "readme" in self._poetry.local_config:
additional_files.add(self._poetry.local_config["readme"])
for readme in self._poetry.package.readmes:
additional_files.add(readme)

for additional_file in additional_files:
file = BuildIncludeFile(
Expand Down
5 changes: 1 addition & 4 deletions src/poetry/core/masonry/builders/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,7 @@ def _copy_module(self, wheel: zipfile.ZipFile) -> None:
self._add_file(wheel, file.path, file.relative_to_source_root())

def _write_metadata(self, wheel: zipfile.ZipFile) -> None:
if (
"scripts" in self._poetry.local_config
or "plugins" in self._poetry.local_config
):
if self._poetry.package.scripts or self._poetry.package.entrypoints:
with self._write_to_zip(wheel, self.dist_info + "/entry_points.txt") as f:
self._write_entry_points(f)

Expand Down
3 changes: 3 additions & 0 deletions src/poetry/core/packages/project_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def __init__(
self.include: list[dict[str, Any]] = []
self.exclude: list[dict[str, Any]] = []
self.custom_urls: dict[str, str] = {}
self.scripts: dict[str, str | dict[str, Any]] = {}
self.gui_scripts: dict[str, str] = {}
self.entrypoints: dict[str, dict[str, str | dict[str, str]]] = {}

if self._python_versions == "*":
self._python_constraint = parse_constraint("~2.7 || >=3.4")
Expand Down
4 changes: 2 additions & 2 deletions src/poetry/core/packages/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from urllib.parse import urlsplit
from urllib.request import url2pathname

from poetry.core.pyproject.toml import PyProjectTOML
from poetry.core.semver.helpers import parse_constraint
from poetry.core.semver.version import Version
from poetry.core.semver.version_range import VersionRange
Expand All @@ -31,7 +30,6 @@
# this as `dict[str, ...]`
ConvertedMarkers = Dict[str, List[List[Tuple[str, str]]]]


BZ2_EXTENSIONS = (".tar.bz2", ".tbz")
XZ_EXTENSIONS = (".tar.xz", ".txz", ".tlz", ".tar.lz", ".tar.lzma")
ZIP_EXTENSIONS = (".zip", ".whl")
Expand Down Expand Up @@ -127,6 +125,8 @@ def is_python_project(path: Path) -> bool:
if not path.is_dir():
return False

from poetry.core.pyproject.toml import PyProjectTOML

setup_py = path / "setup.py"
setup_cfg = path / "setup.cfg"
setuptools_project = setup_py.exists() or setup_cfg.exists()
Expand Down
Empty file.
44 changes: 44 additions & 0 deletions src/poetry/core/pyproject/formats/content_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from __future__ import annotations

from abc import ABC
from abc import abstractmethod
from typing import TYPE_CHECKING
from typing import Any


if TYPE_CHECKING:
from pathlib import Path

from poetry.core.packages.project_package import ProjectPackage
from poetry.core.pyproject.formats.validation_result import ValidationResult


class ContentFormat(ABC):
def __init__(self, content: dict[str, Any]) -> None:
self._content = content

@classmethod
@abstractmethod
def supports(cls, content: dict[str, Any]) -> bool:
...

@abstractmethod
def validate(self, strict: bool = False) -> ValidationResult:
...

@abstractmethod
def to_package(self, root: Path, with_groups: bool = True) -> ProjectPackage:
...

@property
@abstractmethod
def hash_content(self) -> dict[str, Any]:
...

@property
@abstractmethod
def poetry_config(self) -> dict[str, Any]:
"""
The custom poetry configuration (i.e. the parts in [tool.poetry] that are not related to the package)
"""
...
Loading

0 comments on commit 4f3a023

Please sign in to comment.