Commit c70edfd7 by Sébastien Eustace

Improve check command

parent fbb90678
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
- Improved virtualenv detection and management. - Improved virtualenv detection and management.
- Wilcard `python` dependencies are now equivalent to `~2.7 || ^3.4`. - Wilcard `python` dependencies are now equivalent to `~2.7 || ^3.4`.
- Changed behavior of the resolver for conditional dependencies. - Changed behavior of the resolver for conditional dependencies.
- Improved the `check` command.
### Fixed ### Fixed
......
...@@ -10,6 +10,16 @@ class CheckCommand(Command): ...@@ -10,6 +10,16 @@ class CheckCommand(Command):
def handle(self): def handle(self):
# Load poetry and display errors, if any # Load poetry and display errors, if any
self.poetry.check(self.poetry.local_config, strict=True) check_result = self.poetry.check(self.poetry.local_config, strict=True)
if not check_result["errors"] and not check_result["warnings"]:
self.info("All set!") self.info("All set!")
return 0
for error in check_result["errors"]:
self.error("Error: {}".format(error))
for error in check_result["warnings"]:
self.line("<warning>Warning: {}</warning>".format(error))
return 1
...@@ -3,6 +3,8 @@ import os ...@@ -3,6 +3,8 @@ import os
import jsonschema import jsonschema
from typing import List
SCHEMA_DIR = os.path.join(os.path.dirname(__file__), "schemas") SCHEMA_DIR = os.path.join(os.path.dirname(__file__), "schemas")
...@@ -11,7 +13,7 @@ class ValidationError(ValueError): ...@@ -11,7 +13,7 @@ class ValidationError(ValueError):
pass pass
def validate_object(obj, schema_name): # type: (dict, str) -> None def validate_object(obj, schema_name): # type: (dict, str) -> List[str]
schema = os.path.join(SCHEMA_DIR, "{}.json".format(schema_name)) schema = os.path.join(SCHEMA_DIR, "{}.json".format(schema_name))
if not os.path.exists(schema): if not os.path.exists(schema):
...@@ -20,11 +22,16 @@ def validate_object(obj, schema_name): # type: (dict, str) -> None ...@@ -20,11 +22,16 @@ def validate_object(obj, schema_name): # type: (dict, str) -> None
with open(schema) as f: with open(schema) as f:
schema = json.loads(f.read()) schema = json.loads(f.read())
try: validator = jsonschema.Draft4Validator(schema)
jsonschema.validate(obj, schema) validation_errors = sorted(validator.iter_errors(obj), key=lambda e: e.path)
except jsonschema.ValidationError as e:
message = e.message errors = []
if e.path:
message = "[{}] {}".format(".".join(e.path), message) for error in validation_errors:
message = error.message
if error.path:
message = "[{}] {}".format(".".join(error.path), message)
errors.append(message)
raise ValidationError(message) return errors
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import json
import shutil import shutil
from typing import Dict
from typing import List
from .__version__ import __version__ from .__version__ import __version__
from .config import Config from .config import Config
from .exceptions import InvalidProjectFile from .exceptions import InvalidProjectFile
from .json import validate_object from .json import validate_object
from .json import ValidationError
from .packages import Dependency from .packages import Dependency
from .packages import Locker from .packages import Locker
from .packages import Package from .packages import Package
...@@ -105,7 +106,12 @@ class Poetry: ...@@ -105,7 +106,12 @@ class Poetry:
package.description = local_config.get("description", "") package.description = local_config.get("description", "")
package.homepage = local_config.get("homepage") package.homepage = local_config.get("homepage")
package.repository_url = local_config.get("repository") package.repository_url = local_config.get("repository")
package.license = local_config.get("license") try:
license_ = license_by_id(local_config.get("license"))
except ValueError:
license_ = None
package.license = license_
package.keywords = local_config.get("keywords", []) package.keywords = local_config.get("keywords", [])
package.classifiers = local_config.get("classifiers", []) package.classifiers = local_config.get("classifiers", [])
...@@ -179,14 +185,15 @@ class Poetry: ...@@ -179,14 +185,15 @@ class Poetry:
return cls(poetry_file, local_config, package, locker) return cls(poetry_file, local_config, package, locker)
@classmethod @classmethod
def check(cls, config, strict=False): # type: (dict, bool) -> bool def check(cls, config, strict=False): # type: (dict, bool) -> Dict[str, List[str]]
""" """
Checks the validity of a configuration Checks the validity of a configuration
""" """
try: result = {"errors": [], "warnings": []}
validate_object(config, "poetry-schema") # Schema validation errors
except ValidationError as e: validation_errors = validate_object(config, "poetry-schema")
raise InvalidProjectFile(str(e))
result["errors"] += validation_errors
if strict: if strict:
# If strict, check the file more thoroughly # If strict, check the file more thoroughly
...@@ -197,6 +204,14 @@ class Poetry: ...@@ -197,6 +204,14 @@ class Poetry:
try: try:
license_by_id(license) license_by_id(license)
except ValueError: except ValueError:
raise InvalidProjectFile("Invalid license") result["errors"].append("{} is not a valid license".format(license))
if "dependencies" in config:
python_versions = config["dependencies"]["python"]
if python_versions == "*":
result["warnings"].append(
"A wildcard Python dependency is ambiguous. "
"Consider specifying a more explicit one."
)
return True return result
from cleo.testers import CommandTester from cleo.testers import CommandTester
from poetry.utils._compat import Path
from poetry.poetry import Poetry
def test_about(app):
def test_check_valid(app):
command = app.find("check") command = app.find("check")
tester = CommandTester(command) tester = CommandTester(command)
...@@ -12,3 +15,21 @@ All set! ...@@ -12,3 +15,21 @@ All set!
""" """
assert tester.get_display(True) == expected assert tester.get_display(True) == expected
def test_check_invalid(app):
app._poetry = Poetry.create(
Path(__file__).parent.parent.parent / "fixtures" / "invalid_pyproject"
)
command = app.find("check")
tester = CommandTester(command)
tester.execute([("command", command.get_name())])
expected = """\
Error: 'description' is a required property
Error: INVALID is not a valid license
Warning: A wildcard Python dependency is ambiguous. Consider specifying a more explicit one.
"""
assert tester.get_display(True) == expected
[tool.poetry]
name = "invalid"
version = "1.0.0"
authors = [
"Foo <foo@bar.com>"
]
license = "INVALID"
[tool.poetry.dependencies]
python = "*"
...@@ -18,4 +18,4 @@ def test_path_dependencies(base_object): ...@@ -18,4 +18,4 @@ def test_path_dependencies(base_object):
base_object["dependencies"].update({"foo": {"path": "../foo"}}) base_object["dependencies"].update({"foo": {"path": "../foo"}})
base_object["dev-dependencies"].update({"foo": {"path": "../foo"}}) base_object["dev-dependencies"].update({"foo": {"path": "../foo"}})
assert validate_object(base_object, "poetry-schema") is None assert len(validate_object(base_object, "poetry-schema")) == 0
...@@ -138,12 +138,17 @@ def test_check(): ...@@ -138,12 +138,17 @@ def test_check():
complete = TomlFile(fixtures_dir / "complete.toml") complete = TomlFile(fixtures_dir / "complete.toml")
content = complete.read()["tool"]["poetry"] content = complete.read()["tool"]["poetry"]
assert Poetry.check(content) assert Poetry.check(content) == {"errors": [], "warnings": []}
def test_check_fails(): def test_check_fails():
complete = TomlFile(fixtures_dir / "complete.toml") complete = TomlFile(fixtures_dir / "complete.toml")
content = complete.read()["tool"]["poetry"] content = complete.read()["tool"]["poetry"]
content["this key is not in the schema"] = "" content["this key is not in the schema"] = ""
with pytest.raises(InvalidProjectFile): assert Poetry.check(content) == {
Poetry.check(content) "errors": [
"Additional properties are not allowed "
"('this key is not in the schema' was unexpected)"
],
"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