Commit deff8fa6 by Sébastien Eustace

Add pyproject.toml file validation

parent f169b487
......@@ -7,6 +7,7 @@
- Added support for virtualenv autogeneration (Python 3.6+ only).
- Added the `run` command to execute commands inside the created virtualenvs.
- Added the `debug:resolve` command to debug dependency resolution.
- Added `pyproject.toml` file validation.
### Fixed
......
......@@ -10,6 +10,7 @@ from poetry.poetry import Poetry
from .commands import AboutCommand
from .commands import AddCommand
from .commands import BuildCommand
from .commands import CheckCommand
from .commands import ConfigCommand
from .commands import InstallCommand
from .commands import LockCommand
......@@ -84,6 +85,7 @@ class Application(BaseApplication):
AboutCommand(),
AddCommand(),
BuildCommand(),
CheckCommand(),
ConfigCommand(),
InstallCommand(),
LockCommand(),
......
from .about import AboutCommand
from .add import AddCommand
from .build import BuildCommand
from .check import CheckCommand
from .config import ConfigCommand
from .install import InstallCommand
from .lock import LockCommand
......
import jsonschema
from .command import Command
class CheckCommand(Command):
"""
Checks the validity of the <comment>pyproject.toml</comment> file.
check
"""
def handle(self):
# Load poetry and display errors, if any
_ = self.poetry
self.info('All set!')
class PoetryException(Exception):
pass
class InvalidProjectFile(PoetryException):
pass
{
"$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"
},
"readme": {
"type": "string",
"description": "The path to the README file"
},
"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."
}
},
"patternProperties": {
"^[a-zA-Z-_0-9]+$": {
"oneOf": [
{
"$ref": "#/definitions/dependency"
},
{
"$ref": "#/definitions/long-dependency"
},
{
"$ref": "#/definitions/git-dependency"
}
]
}
},
"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).",
"patternProperties": {
"^[a-zA-Z-_0-9]+$": {
"oneOf": [
{
"$ref": "#/definitions/dependency"
},
{
"$ref": "#/definitions/long-dependency"
},
{
"$ref": "#/definitions/git-dependency"
}
]
}
},
"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"
}
}
},
"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"
}
},
"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 c=which the dependency should be installed."
},
"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"
}
}
}
},
"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 c=which the dependency should be installed."
},
"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"
}
}
}
},
"repository": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the repository"
},
"url": {
"type": "string",
"description": "The url of the repository",
"format": "uri"
}
}
}
}
}
import json
from pathlib import Path
import jsonschema
from .__version__ import __version__
from .exceptions import InvalidProjectFile
from .packages import Dependency
from .packages import Locker
from .packages import Package
......@@ -60,7 +65,6 @@ class Poetry:
f'Poetry could not find a pyproject.toml file in {cwd}'
)
# TODO: validate file content
local_config = TomlFile(poetry_file.as_posix()).read(True)
if 'tool' not in local_config or 'poetry' not in local_config['tool']:
raise RuntimeError(
......@@ -68,6 +72,9 @@ class Poetry:
)
local_config = local_config['tool']['poetry']
# Checking validity
cls.check(local_config)
# Load package
name = local_config['name']
version = local_config['version']
......@@ -119,3 +126,29 @@ class Poetry:
locker = Locker(poetry_file.with_suffix('.lock'), local_config)
return cls(poetry_file, local_config, package, locker)
@classmethod
def check(cls, config: dict, strict: bool = False):
"""
Checks the validity of a configuration
"""
schema = (
Path(__file__).parent
/ 'json' / 'schemas' / 'poetry-schema.json'
)
schema = json.loads(schema.read_text())
try:
jsonschema.validate(
config,
schema
)
except jsonschema.ValidationError as e:
message = e.message
if e.path:
message = f"[{'.'.join(e.path)}] {message}"
raise InvalidProjectFile(message)
return True
......@@ -24,6 +24,8 @@ toml = "^0.9"
cachy = "^0.1.0"
pip-tools = "^1.11"
requests-toolbelt = "^0.8.0"
jsonschema = "^2.6"
pyrsistent = "^0.14.2"
[tool.poetry.dev-dependencies]
pytest = "~3.4"
......
[tool.poetry]
name = "poetry"
version = "0.5.0"
description = "Python dependency management and packaging made easy."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://poetry.eustace.io/"
repository = "https://github.com/sdispater/poet"
documentation = "https://poetry.eustace.io/docs"
keywords = ["packaging", "dependency", "poetry"]
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.2" # Compatible python versions must be declared here
toml = "^0.9"
# Dependencies with extras
requests = { version = "^2.13", extras = [ "security" ] }
# Python specific dependencies with prereleases allowed
pathlib2 = { version = "^2.2", python = "~2.7", allows_prereleases = true }
# Git dependencies
cleo = { git = "https://github.com/sdispater/cleo.git", branch = "master" }
# Optional dependencies (extras)
pendulum = { version = "^1.4", optional = true }
[tool.poetry.extras]
time = [ "pendulum" ]
[tool.poetry.dev-dependencies]
pytest = "^3.0"
pytest-cov = "^2.4"
[tool.poetry.scripts]
my-script = 'my_package:main'
[[tool.poetry.source]]
name = "foo"
url = "https://bar.com"
......@@ -24,7 +24,7 @@ pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
requests = { version = "^2.18", optional = true, extras=[ "security" ] }
pathlib2 = { version = "^2.2", python = "~2.7" }
orator = { version = "^0.9", optional = true}
orator = { version = "^0.9", optional = true }
[tool.poetry.extras]
......
......@@ -12,5 +12,3 @@ readme = "README.rst"
homepage = "https://poetry.eustace.io/"
build = "build.py"
include = ["extended.c", "README.rst", "build.py"]
import toml
from pathlib import Path
from poetry import Poetry
......@@ -52,3 +54,10 @@ def test_poetry():
assert not pathlib2.is_optional()
assert 'db' in package.extras
def test_check():
complete = fixtures_dir / 'complete.toml'
content = toml.loads(complete.read_text())['tool']['poetry']
assert Poetry.check(content)
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