Commit ddbcfcda by Arun Babu Neelicattu Committed by Bjorn Neergaard

schema: validate source objects in poetry

This change is intended to help migrate validation of sections in
pyproject.toml that are not relevant to poetry-core into poetry. This
allows for poetry frontend specific configuration to be introduced
without dependent changes in poetry-core.
parent 711cca93
......@@ -17,6 +17,7 @@ from poetry.core.toml.file import TOMLFile
from tomlkit.toml_document import TOMLDocument
from poetry.config.config import Config
from poetry.json import validate_object
from poetry.packages.locker import Locker
from poetry.plugins.plugin import Plugin
from poetry.plugins.plugin_manager import PluginManager
......@@ -300,3 +301,13 @@ class Factory(BaseFactory):
)
return cast(TOMLDocument, pyproject)
@classmethod
def validate(
cls, config: dict[str, Any], strict: bool = False
) -> dict[str, list[str]]:
results = super().validate(config, strict)
results["errors"].extend(validate_object(config))
return results
......@@ -3,10 +3,13 @@ from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any
import jsonschema
from poetry.core.json import SCHEMA_DIR as CORE_SCHEMA_DIR
SCHEMA_DIR = os.path.join(os.path.dirname(__file__), "schemas")
......@@ -16,14 +19,9 @@ class ValidationError(ValueError):
pass
def validate_object(obj: dict[str, Any], schema_name: str) -> list[str]:
schema_file = os.path.join(SCHEMA_DIR, f"{schema_name}.json")
if not os.path.exists(schema_file):
raise ValueError(f"Schema {schema_name} does not exist.")
with open(schema_file, encoding="utf-8") as f:
schema = json.loads(f.read())
def validate_object(obj: dict[str, Any]) -> list[str]:
schema_file = Path(SCHEMA_DIR, "poetry.json")
schema = json.loads(schema_file.read_text(encoding="utf-8"))
validator = jsonschema.Draft7Validator(schema)
validation_errors = sorted(
......@@ -41,4 +39,17 @@ def validate_object(obj: dict[str, Any], schema_name: str) -> list[str]:
errors.append(message)
core_schema = json.loads(
Path(CORE_SCHEMA_DIR, "poetry-schema.json").read_text(encoding="utf-8")
)
if core_schema["additionalProperties"]:
# TODO: make this un-conditional once core update to >1.1.0b2
properties = {*schema["properties"].keys(), *core_schema["properties"].keys()}
additional_properties = set(obj.keys()) - properties
for key in additional_properties:
errors.append(
f"Additional properties are not allowed ('{key}' was unexpected)"
)
return errors
{
"$schema": "http://json-schema.org/draft-04/schema#",
"name": "Package",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"version",
"description"
],
"properties": {
"name": {
"type": "string",
"description": "Package name."
},
"version": {
"type": "string",
"description": "Package version."
},
"description": {
"type": "string",
"description": "Short package description."
},
"keywords": {
"type": "array",
"items": {
"type": "string",
"description": "A tag/keyword that this package relates to."
}
},
"homepage": {
"type": "string",
"description": "Homepage URL for the project.",
"format": "uri"
},
"repository": {
"type": "string",
"description": "Repository URL for the project.",
"format": "uri"
},
"documentation": {
"type": "string",
"description": "Documentation URL for the project.",
"format": "uri"
},
"license": {
"type": "string",
"description": "License name."
},
"authors": {
"$ref": "#/definitions/authors"
},
"maintainers": {
"$ref": "#/definitions/maintainers"
},
"readme": {
"type": "string",
"description": "The path to the README file"
},
"classifiers": {
"type": "array",
"description": "A list of trove classifiers."
},
"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": {
"type": "string",
"description": "What to include in the package."
},
"from": {
"type": "string",
"description": "Where the source directory of the package resides."
},
"format": {
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"description": "The format(s) for which the package must be included."
}
}
}
},
"include": {
"type": "array",
"description": "A list of files and folders to include."
},
"exclude": {
"type": "array",
"description": "A list of files and folders to exclude."
},
"dependencies": {
"type": "object",
"description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.",
"required": [
"python"
],
"properties": {
"python": {
"type": "string",
"description": "The Python versions the package is compatible with."
}
},
"$ref": "#/definitions/dependencies",
"additionalProperties": false
},
"dev-dependencies": {
"type": "object",
"description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).",
"$ref": "#/definitions/dependencies",
"additionalProperties": false
},
"extras": {
"type": "object",
"patternProperties": {
"^[a-zA-Z-_.0-9]+$": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"build": {
"type": "string",
"description": "The file used to build extensions."
},
"source": {
"type": "array",
"description": "A set of additional repositories where packages can be found.",
"additionalProperties": {
"$ref": "#/definitions/repository"
},
"items": {
"$ref": "#/definitions/repository"
}
},
"scripts": {
"type": "object",
"description": "A hash of scripts to be installed.",
"items": {
"type": "string"
}
},
"plugins": {
"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"
}
}
}
}
},
"urls": {
"type": "object",
"patternProperties": {
"^.+$": {
"type": "string",
"description": "The full url of the custom url."
}
}
}
},
"definitions": {
"authors": {
"type": "array",
"description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.",
"items": {
"type": "string"
}
},
"maintainers": {
"type": "array",
"description": "List of maintainers, other than the original author(s), that upkeep the package.",
"items": {
"type": "string"
}
},
"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."
},
"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"
}
}
}
},
"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"
}
]
}
},
"scripts": {
"type": "object",
"patternProperties": {
"^[a-zA-Z-_.0-9]+$": {
"oneOf": [
{
"$ref": "#/definitions/script"
},
{
"$ref": "#/definitions/extra-script"
}
]
}
}
},
"script": {
"type": "string",
"description": "A simple script pointing to a callable object."
},
"extra-script": {
"type": "object",
"description": "A script that should be installed only if extras are activated.",
"additionalProperties": false,
"properties": {
"callable": {
"$ref": "#/definitions/script"
},
"extras": {
"type": "array",
"description": "The required extras for this script.",
"items": {
"type": "string"
}
}
}
},
"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."
}
}
}
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"additionalProperties": true,
"type": "object",
"required": [],
"properties": {
"source": {
"type": "array",
"description": "A set of additional repositories where packages can be found.",
"additionalProperties": {
"$ref": "#/definitions/repository"
},
"items": {
"$ref": "#/definitions/repository"
}
}
},
"definitions": {
"repository": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"url"
],
"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)"
}
}
}
}
}
[tool.poetry]
name = "foobar"
version = "0.1.0"
description = ""
[tool.poetry.dependencies]
python = "^3.10"
[[tool.poetry.source]]
name = "pypi-simple"
default = false
secondary = false
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "foobar"
version = "0.1.0"
description = ""
[tool.poetry.dependencies]
python = "^3.10"
[[tool.poetry.source]]
name = "pypi-simple"
url = "https://pypi.org/simple/"
default = false
secondary = false
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
from __future__ import annotations
from pathlib import Path
from poetry.core.toml import TOMLFile
from poetry.factory import Factory
FIXTURE_DIR = Path(__file__).parent / "fixtures" / "source"
def test_pyproject_toml_valid() -> None:
toml = TOMLFile(FIXTURE_DIR / "complete_valid.toml").read()
content = toml["tool"]["poetry"]
assert Factory.validate(content) == {"errors": [], "warnings": []}
def test_pyproject_toml_invalid() -> None:
toml = TOMLFile(FIXTURE_DIR / "complete_invalid.toml").read()
content = toml["tool"]["poetry"]
assert Factory.validate(content) == {
"errors": ["[source.0] 'url' is a required property"],
"warnings": [],
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment