From 77882e4343cef16090c1e438ec3f0fde95db0bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Mon, 20 Mar 2023 23:18:23 +0100 Subject: [PATCH] Add support for PEP-621 --- .../json/schemas/poetry-pep621-schema.json | 693 ++++++++++++++++++ src/poetry/core/packages/package.py | 2 + .../formats/standard_content_format.py | 196 +++++ src/poetry/core/pyproject/toml.py | 6 +- tests/fixtures/sample_pep621_project/LICENSE | 0 .../fixtures/sample_pep621_project/README.md | 1 + .../sample_pep621_project/pyproject.toml | 64 ++ tests/test_factory.py | 131 +++- 8 files changed, 1087 insertions(+), 6 deletions(-) create mode 100644 src/poetry/core/json/schemas/poetry-pep621-schema.json create mode 100644 src/poetry/core/pyproject/formats/standard_content_format.py create mode 100644 tests/fixtures/sample_pep621_project/LICENSE create mode 100644 tests/fixtures/sample_pep621_project/README.md create mode 100644 tests/fixtures/sample_pep621_project/pyproject.toml diff --git a/src/poetry/core/json/schemas/poetry-pep621-schema.json b/src/poetry/core/json/schemas/poetry-pep621-schema.json new file mode 100644 index 000000000..e5854c881 --- /dev/null +++ b/src/poetry/core/json/schemas/poetry-pep621-schema.json @@ -0,0 +1,693 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "name": "Package", + "type": "object", + "required": [ + "project" + ], + "properties": { + "project": { + "$ref": "#/definitions/project" + }, + "tool": { + "type": "object", + "properties": { + "poetry": { + "$ref": "#/definitions/poetry-config" + } + } + } + }, + "definitions": { + "project": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "type": "string", + "description": "Package name." + }, + "version": { + "type": "string", + "description": "Package version." + }, + "description": { + "type": "string", + "description": "Short package description.", + "pattern": "^[^\n]*$" + }, + "keywords": { + "type": "array", + "items": { + "type": "string", + "description": "A tag/keyword that this package relates to." + } + }, + "license": { + "$ref": "#/definitions/license" + }, + "authors": { + "$ref": "#/definitions/authors" + }, + "maintainers": { + "$ref": "#/definitions/maintainers" + }, + "readme": { + "$ref": "#/definitions/readme" + }, + "requires-python": { + "type": "string", + "description": "The Python version requirements of the project." + }, + "classifiers": { + "type": "array", + "description": "A list of trove classifiers." + }, + "urls": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "string", + "description": "The full url of the custom url." + } + } + }, + "dependencies": { + "type": "array", + "description": "A list of runtime dependencies in the PEP 508 format", + "items": { + "type": "string" + } + }, + "optional-dependencies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "extras": { + "type": "object", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "scripts": { + "type": "object", + "description": "A hash of scripts to be installed.", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "string" + } + } + }, + "gui-scripts": { + "type": "object", + "description": "A hash of scripts to be installed.", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "string" + } + } + }, + "entry-points": { + "type": "object", + "description": "A hash of hashes representing plugins", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "object", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "string" + } + } + } + } + } + } + }, + "poetry-config": { + "type": "object", + "additionalProperties": true, + "packages": { + "type": "array", + "description": "A list of packages to include in the final distribution.", + "items": { + "type": "object", + "description": "Information about where the package resides.", + "additionalProperties": false, + "required": [ + "include" + ], + "properties": { + "include": { + "$ref": "#/definitions/include-path" + }, + "from": { + "type": "string", + "description": "Where the source directory of the package resides." + }, + "format": { + "$ref": "#/definitions/package-formats" + } + } + } + }, + "include": { + "type": "array", + "description": "A list of files and folders to include.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/include-path" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path" + ], + "properties": { + "path": { + "$ref": "#/definitions/include-path" + }, + "format": { + "$ref": "#/definitions/package-formats" + } + } + } + ] + } + }, + "exclude": { + "type": "array", + "description": "A list of files and folders to exclude." + }, + "group": { + "type": "object", + "description": "This represents groups of dependencies", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "object", + "description": "This represents a single dependency group", + "required": [ + "dependencies" + ], + "properties": { + "optional": { + "type": "boolean", + "description": "Whether the dependency group is optional or not" + }, + "dependencies": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": true + } + } + }, + "build": { + "$ref": "#/definitions/build-section" + } + }, + "license": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "file": { + "type": "string" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "text": { + "type": "string" + } + } + } + ] + }, + "authors": { + "type": "array", + "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "maintainers": { + "type": "array", + "description": "List of maintainers, other than the original author(s), that upkeep the package.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + }, + "readme": { + "oneOf": [ + { + "type": "string", + "description": "Relative path to the README file" + }, + { + "oneOf": [ + { + "type": "object", + "description": "Readme with file and content type", + "properties": { + "file": { + "type": "string", + "description": "The relative path to the readme file" + }, + "content-type": { + "type": "string", + "description": "The content type of the README file content" + } + } + }, + { + "type": "object", + "description": "Readme with full description and content type", + "properties": { + "file": { + "type": "string", + "description": "The full content of the description" + }, + "content-type": { + "type": "string", + "description": "The content type of the description" + } + } + } + ] + } + ] + }, + "include-path": { + "type": "string", + "description": "Path to file or directory to include." + }, + "package-format": { + "type": "string", + "enum": [ + "sdist", + "wheel" + ], + "description": "A Python packaging format." + }, + "package-formats": { + "oneOf": [ + { + "$ref": "#/definitions/package-format" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/package-format" + } + } + ], + "description": "The format(s) for which the package must be included." + }, + "dependencies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "oneOf": [ + { + "$ref": "#/definitions/dependency" + }, + { + "$ref": "#/definitions/long-dependency" + }, + { + "$ref": "#/definitions/git-dependency" + }, + { + "$ref": "#/definitions/file-dependency" + }, + { + "$ref": "#/definitions/path-dependency" + }, + { + "$ref": "#/definitions/url-dependency" + }, + { + "$ref": "#/definitions/multiple-constraints-dependency" + } + ] + } + } + }, + "dependency": { + "type": "string", + "description": "The constraint of the dependency." + }, + "long-dependency": { + "type": "object", + "required": [ + "version" + ], + "additionalProperties": false, + "properties": { + "version": { + "type": "string", + "description": "The constraint of the dependency." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "allow-prereleases": { + "type": "boolean", + "description": "Whether the dependency allows prereleases or not." + }, + "allows-prereleases": { + "type": "boolean", + "description": "Whether the dependency allows prereleases or not." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." + }, + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + }, + "source": { + "type": "string", + "description": "The exclusive source used to search for this dependency." + } + } + }, + "git-dependency": { + "type": "object", + "required": [ + "git" + ], + "additionalProperties": false, + "properties": { + "git": { + "type": "string", + "description": "The url of the git repository.", + "format": "uri" + }, + "branch": { + "type": "string", + "description": "The branch to checkout." + }, + "tag": { + "type": "string", + "description": "The tag to checkout." + }, + "rev": { + "type": "string", + "description": "The revision to checkout." + }, + "subdirectory": { + "type": "string", + "description": "The relative path to the directory where the package is located." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "allow-prereleases": { + "type": "boolean", + "description": "Whether the dependency allows prereleases or not." + }, + "allows-prereleases": { + "type": "boolean", + "description": "Whether the dependency allows prereleases or not." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." + }, + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + }, + "develop": { + "type": "boolean", + "description": "Whether to install the dependency in development mode." + } + } + }, + "file-dependency": { + "type": "object", + "required": [ + "file" + ], + "additionalProperties": false, + "properties": { + "file": { + "type": "string", + "description": "The path to the file." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." + }, + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + } + } + }, + "path-dependency": { + "type": "object", + "required": [ + "path" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "description": "The path to the dependency." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." + }, + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + }, + "develop": { + "type": "boolean", + "description": "Whether to install the dependency in development mode." + } + } + }, + "url-dependency": { + "type": "object", + "required": [ + "url" + ], + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "description": "The url to the file." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." + }, + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + } + } + }, + "multiple-constraints-dependency": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dependency" + }, + { + "$ref": "#/definitions/long-dependency" + }, + { + "$ref": "#/definitions/git-dependency" + }, + { + "$ref": "#/definitions/file-dependency" + }, + { + "$ref": "#/definitions/path-dependency" + }, + { + "$ref": "#/definitions/url-dependency" + } + ] + } + }, + "repository": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the repository" + }, + "url": { + "type": "string", + "description": "The url of the repository", + "format": "uri" + }, + "default": { + "type": "boolean", + "description": "Make this repository the default (disable PyPI)" + }, + "secondary": { + "type": "boolean", + "description": "Declare this repository as secondary, i.e. it will only be looked up last for packages." + }, + "links": { + "type": "boolean", + "description": "Declare this as a link source. Links at uri/path can point to sdist or bdist archives." + }, + "indexed": { + "type": "boolean", + "description": "For PEP 503 simple API repositories, pre-fetch and index the available packages. (experimental)" + } + } + }, + "build-script": { + "type": "string", + "description": "The python script file used to build extensions." + }, + "build-config": { + "type": "object", + "description": "Build specific configurations.", + "additionalProperties": false, + "properties": { + "generate-setup-file": { + "type": "boolean", + "description": "Generate and include a setup.py file in sdist.", + "default": true + }, + "script": { + "$ref": "#/definitions/build-script" + } + } + }, + "build-section": { + "oneOf": [ + { + "$ref": "#/definitions/build-script" + }, + { + "$ref": "#/definitions/build-config" + } + ] + } + } +} diff --git a/src/poetry/core/packages/package.py b/src/poetry/core/packages/package.py index 3c32bb68a..c9a6411eb 100644 --- a/src/poetry/core/packages/package.py +++ b/src/poetry/core/packages/package.py @@ -103,6 +103,8 @@ def __init__( self.keywords: list[str] = [] self._license: License | None = None self._readme: Path | None = None + self.readme_content_type: str | None = None + self.readme_content: str | None = None self.readmes: tuple[Path, ...] = () self.extras: dict[NormalizedName, list[Dependency]] = {} diff --git a/src/poetry/core/pyproject/formats/standard_content_format.py b/src/poetry/core/pyproject/formats/standard_content_format.py new file mode 100644 index 000000000..47b8241e2 --- /dev/null +++ b/src/poetry/core/pyproject/formats/standard_content_format.py @@ -0,0 +1,196 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from packaging.utils import canonicalize_name + +from poetry.core.json import validate_object +from poetry.core.packages.project_package import ProjectPackage +from poetry.core.pyproject.formats.content_format import ContentFormat +from poetry.core.pyproject.formats.validation_result import ValidationResult +from poetry.core.utils.helpers import combine_unicode + + +if TYPE_CHECKING: + from pathlib import Path + from typing import Any + + from poetry.core.packages.dependency_group import DependencyGroup + from poetry.core.spdx.license import License + + +class StandardContentFormat(ContentFormat): + @classmethod + def supports(cls, content: dict[str, Any]) -> bool: + return "project" in content + + def validate(self, strict: bool = False) -> ValidationResult: + result = ValidationResult([], []) + + # Schema validation errors + validation_errors = validate_object(self._content, "poetry-pep621-schema") + + result.errors += validation_errors + + return result + + def to_package(self, 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.spdx.helpers import license_by_id + + config = self._content["project"] + + package = ProjectPackage(config["name"], config["version"]) + package.root_dir = root + + if "requires-python" in config: + package.python_versions = config["requires-python"] + + for author in config.get("authors", []): + name, email = author.get("name"), author.get("email") + if name and email: + package.authors.append( + f"{combine_unicode(name)} <{combine_unicode(email)}>" + ) + elif name: + package.authors.append(combine_unicode(name)) + else: + package.authors.append(combine_unicode(name)) + + for maintainer in config.get("maintainers", []): + name, email = maintainer.get("name"), maintainer.get("email") + if name and email: + package.maintainers.append( + f"{combine_unicode(name)} <{combine_unicode(email)}>" + ) + elif name: + package.maintainers.append(combine_unicode(name)) + else: + package.maintainers.append(combine_unicode(name)) + + package.description = config.get("description") + + if "text" in config.get("license", {}): + try: + license_: License | None = license_by_id(config["license"]["text"]) + except ValueError: + license_ = None + else: + license_ = None + + package.license = license_ + package.keywords = config.get("keywords", []) + package.classifiers = config.get("classifiers", []) + + if "readme" in config: + readme = config["readme"] + if isinstance(readme, str): + package.readme = root / readme + elif "file" in readme: + package.readme = root / readme["file"] + package.readme_content_type = readme["content-type"] + elif "text" in readme: + package.readme_content = root / readme["text"] + package.readme_content_type = readme["content-type"] + + if "dependencies" in config: + self._add_package_group_dependencies( + package, MAIN_GROUP, config["dependencies"], root_dir=root + ) + + if "optional-dependencies" in config: + for extra_name, dependencies in config["optional-dependencies"].items(): + extra_name = canonicalize_name(extra_name) + package.extras[extra_name] = [] + + for dependency_constraint in dependencies: + dependency = Dependency.create_from_pep_508( + dependency_constraint, relative_to=root + ) + dependency._optional = True + dependency.in_extras.append(extra_name) + package.extras[extra_name].append(dependency) + + if not package.has_dependency_group(MAIN_GROUP): + group = DependencyGroup(MAIN_GROUP) + package.add_dependency_group(group) + else: + group = package.dependency_group(MAIN_GROUP) + + group.add_dependency(dependency) + + # Custom urls + if "urls" in config: + package.custom_urls = config["urls"] + + package.scripts = config.get("scripts", {}) + package.gui_scripts = config.get("gui-scripts", {}) + package.entrypoints = config.get("entry-points", {}) + + poetry_config = config.get("tool", {}).get("poetry", {}) + + if "build" in poetry_config: + build = poetry_config["build"] + if not isinstance(build, dict): + build = {"script": build} + package.build_config = build or {} + + if "include" in poetry_config: + package.include = [] + + for include in poetry_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 poetry_config: + package.exclude = poetry_config["exclude"] + + if "packages" in poetry_config: + package.packages = poetry_config["packages"] + + return package + + @property + def hash_content(self) -> dict[str, Any]: + ... + + @property + 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) + """ + ... + + @classmethod + def _add_package_group_dependencies( + cls, + package: ProjectPackage, + group: str | DependencyGroup, + dependencies: list[str], + root_dir: Path | None = None, + ) -> None: + from poetry.core.packages.dependency import Dependency + + 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 constraint in dependencies: + dependency = Dependency.create_from_pep_508( + constraint, relative_to=root_dir + ) + group.add_dependency(dependency) + + package.add_dependency_group(group) diff --git a/src/poetry/core/pyproject/toml.py b/src/poetry/core/pyproject/toml.py index 2160c4ab3..5fd06c0b1 100644 --- a/src/poetry/core/pyproject/toml.py +++ b/src/poetry/core/pyproject/toml.py @@ -7,6 +7,7 @@ from poetry.core.pyproject.formats.content_format import ContentFormat from poetry.core.pyproject.formats.legacy_content_format import LegacyContentFormat +from poetry.core.pyproject.formats.standard_content_format import StandardContentFormat if TYPE_CHECKING: @@ -19,7 +20,10 @@ class PyProjectTOML: - SUPPORTED_FORMATS: list[type[ContentFormat]] = [LegacyContentFormat] + SUPPORTED_FORMATS: list[type[ContentFormat]] = [ + LegacyContentFormat, + StandardContentFormat, + ] def __init__(self, path: str | Path) -> None: from poetry.core.toml import TOMLFile diff --git a/tests/fixtures/sample_pep621_project/LICENSE b/tests/fixtures/sample_pep621_project/LICENSE new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/sample_pep621_project/README.md b/tests/fixtures/sample_pep621_project/README.md new file mode 100644 index 000000000..e2f7c1c06 --- /dev/null +++ b/tests/fixtures/sample_pep621_project/README.md @@ -0,0 +1 @@ +# My Package diff --git a/tests/fixtures/sample_pep621_project/pyproject.toml b/tests/fixtures/sample_pep621_project/pyproject.toml new file mode 100644 index 000000000..35633eafb --- /dev/null +++ b/tests/fixtures/sample_pep621_project/pyproject.toml @@ -0,0 +1,64 @@ +[project] +name = "my-package" +version = "1.2.3" +description = "Some description." +readme = "README.md" +requires-python = ">=3.6" +license = { text = "MIT" } +keywords = ["packaging", "dependency", "poetry"] +authors = [ + { name = "Sébastien Eustace", email = "sebastien@eustace.io" } +] +maintainers = [ + { name = "Sébastien Eustace", email = "sebastien@eustace.io" } +] + +classifiers = [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules" +] + +# Requirements +dependencies = [ + "cleo ~=0.6", + "pendulum @ git+https://github.com/sdispater/pendulum.git@2.0", + "tomlkit @ git+https://github.com/sdispater/tomlkit.git@3bff550", + "pathlib2 ~=2.2 ; python_version == '2.7'", + # File dependency + "demo @ ../distributions/demo-0.1.0-py2.py3-none-any.whl", + # Dir dependency with setup.py + "my-package @ ../project_with_setup/", + # Dir dependency with pyproject.toml + "simple-project @ ../simple_project/", + # Dependency with markers + "functools32 ~=3.2.3 ; python_version ~= '2.7' and sys_platform == 'win32' or python_version in '3.4 3.5'", + # Dependency with python constraint + "dataclasses ~=0.7 ; python_full_version >= '3.6.1' and python_version < '3.7'" +] + +[project.optional-dependencies] +db = [ + "orator ~=0.9" +] +network = [ + "requests[security] ~=2.18" +] + +[project.urls] +homepage = "https://python-poetry.org" +repository = "https://github.com/python-poetry/poetry" +documentation = "https://python-poetry.org/docs" + +[project.scripts] +my-script = "my_package:main" + +[project.entry-points."blogtool.parsers"] +".rst" = "some_module::SomeClass" + +[tool.poetry.dependency-options] +tomlkit = { develop = true } + +[tool.poetry.group.dev] +dependencies = [ + "pytest ~=3.4" +] diff --git a/tests/test_factory.py b/tests/test_factory.py index 98012a150..25964b3ad 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -3,7 +3,6 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import cast import pytest @@ -12,6 +11,7 @@ from poetry.core.constraints.version import parse_constraint from poetry.core.factory import Factory from poetry.core.packages.url_dependency import URLDependency +from poetry.core.packages.vcs_dependency import VCSDependency from poetry.core.toml import TOMLFile from poetry.core.version.markers import SingleMarker @@ -20,8 +20,6 @@ from _pytest.logging import LogCaptureFixture from poetry.core.packages.dependency import Dependency - from poetry.core.packages.vcs_dependency import VCSDependency - fixtures_dir = Path(__file__).parent / "fixtures" @@ -59,7 +57,7 @@ def test_create_poetry() -> None: pendulum = dependencies["pendulum"] assert pendulum.pretty_constraint == "branch 2.0" assert pendulum.is_vcs() - pendulum = cast("VCSDependency", pendulum) + assert isinstance(pendulum, VCSDependency) assert pendulum.vcs == "git" assert pendulum.branch == "2.0" assert pendulum.source == "https://github.com/sdispater/pendulum.git" @@ -69,7 +67,7 @@ def test_create_poetry() -> None: tomlkit = dependencies["tomlkit"] assert tomlkit.pretty_constraint == "rev 3bff550" assert tomlkit.is_vcs() - tomlkit = cast("VCSDependency", tomlkit) + assert isinstance(tomlkit, VCSDependency) assert tomlkit.vcs == "git" assert tomlkit.rev == "3bff550" assert tomlkit.source == "https://github.com/sdispater/tomlkit.git" @@ -151,6 +149,129 @@ def test_create_poetry() -> None: ] +def test_create_poetry_pep621() -> None: + poetry = Factory().create_poetry(fixtures_dir / "sample_pep621_project") + + package = poetry.package + + assert package.name == "my-package" + assert package.version.text == "1.2.3" + assert package.description == "Some description." + assert package.authors == ["Sébastien Eustace "] + assert package.license + assert package.license.id == "MIT" + assert ( + package.readme.relative_to(fixtures_dir).as_posix() + == "sample_pep621_project/README.md" + ) + assert package.urls["homepage"] == "https://python-poetry.org" + assert package.urls["repository"] == "https://github.com/python-poetry/poetry" + assert package.keywords == ["packaging", "dependency", "poetry"] + + assert package.python_versions == ">=3.6" + assert str(package.python_constraint) == ">=3.6" + + dependencies = {} + for dep in package.requires: + dependencies[dep.name] = dep + + cleo = dependencies["cleo"] + assert cleo.pretty_constraint == ">=0.6,<1.0" + assert not cleo.is_optional() + + pendulum = dependencies["pendulum"] + assert pendulum.pretty_constraint == "rev 2.0" + assert pendulum.is_vcs() + assert isinstance(pendulum, VCSDependency) + assert pendulum.vcs == "git" + assert pendulum.rev == "2.0" + assert pendulum.source == "https://github.com/sdispater/pendulum.git" + assert pendulum.allows_prereleases() + assert not pendulum.develop + + tomlkit = dependencies["tomlkit"] + assert tomlkit.pretty_constraint == "rev 3bff550" + assert tomlkit.is_vcs() + assert isinstance(tomlkit, VCSDependency) + assert tomlkit.vcs == "git" + assert tomlkit.rev == "3bff550" + assert tomlkit.source == "https://github.com/sdispater/tomlkit.git" + assert tomlkit.allows_prereleases() + assert not tomlkit.develop + + requests = dependencies["requests"] + assert requests.pretty_constraint == ">=2.18,<3.0" + assert not requests.is_vcs() + assert not requests.allows_prereleases() + assert requests.is_optional() + assert requests.extras == frozenset({"security"}) + + pathlib2 = dependencies["pathlib2"] + assert pathlib2.pretty_constraint == ">=2.2,<3.0" + assert pathlib2.python_versions == "~2.7" + assert not pathlib2.is_optional() + + demo = dependencies["demo"] + assert demo.is_file() + assert not demo.is_vcs() + assert demo.name == "demo" + assert demo.pretty_constraint == "*" + + demo = dependencies["my-package"] + assert not demo.is_file() + assert demo.is_directory() + assert not demo.is_vcs() + assert demo.name == "my-package" + assert demo.pretty_constraint == "*" + + simple_project = dependencies["simple-project"] + assert not simple_project.is_file() + assert simple_project.is_directory() + assert not simple_project.is_vcs() + assert simple_project.name == "simple-project" + assert simple_project.pretty_constraint == "*" + + functools32 = dependencies["functools32"] + assert functools32.name == "functools32" + assert functools32.pretty_constraint == ">=3.2.3,<3.3.0" + assert ( + str(functools32.marker) + == 'python_version ~= "2.7" and sys_platform == "win32" or python_version in' + ' "3.4 3.5"' + ) + + dataclasses = dependencies["dataclasses"] + assert dataclasses.name == "dataclasses" + assert dataclasses.pretty_constraint == ">=0.7,<1.0" + assert dataclasses.python_versions == ">=3.6.1 <3.7" + assert ( + str(dataclasses.marker) + == 'python_full_version >= "3.6.1" and python_version < "3.7"' + ) + + assert "db" in package.extras + + classifiers = package.classifiers + + assert classifiers == [ + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules", + ] + + assert package.all_classifiers == [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries :: Python Modules", + ] + + def test_create_poetry_with_packages_and_includes() -> None: poetry = Factory().create_poetry( fixtures_dir.parent / "masonry" / "builders" / "fixtures" / "with-include"