-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enhance plugin system with dependency management (#118)
- Loading branch information
Showing
43 changed files
with
2,044 additions
and
334 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from pathlib import Path | ||
|
||
import pytest | ||
from baby_steps import given, then, when | ||
from pytest import raises | ||
|
||
from vedro.commands.run_command._config_validator import ConfigValidator | ||
from vedro.core import Config | ||
|
||
from ._utils import tmp_dir | ||
|
||
__all__ = ("tmp_dir",) # fixtures | ||
|
||
|
||
def test_validate(): | ||
with given: | ||
class CustomScenarioDir(Config): | ||
pass | ||
|
||
validator = ConfigValidator(CustomScenarioDir) | ||
|
||
with when: | ||
res = validator.validate() | ||
|
||
with then: | ||
assert res is None | ||
|
||
|
||
def test_validate_with_invalid_scenario_dir(): | ||
with given: | ||
class CustomScenarioDir(Config): | ||
default_scenarios_dir = None | ||
|
||
validator = ConfigValidator(CustomScenarioDir) | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate() | ||
|
||
with then: | ||
assert exc.type is TypeError | ||
assert str(exc.value) == ( | ||
"Expected `default_scenarios_dir` to be a Path or str, got <class 'NoneType'> (None)" | ||
) | ||
|
||
|
||
@pytest.mark.usefixtures(tmp_dir.__name__) | ||
def test_validate_with_nonexistent_scenario_dir(): | ||
with given: | ||
class CustomScenarioDir(Config): | ||
default_scenarios_dir = "nonexisting/" | ||
|
||
validator = ConfigValidator(CustomScenarioDir) | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate() | ||
|
||
with then: | ||
assert exc.type is FileNotFoundError | ||
|
||
scenarios_dir = Path(CustomScenarioDir.default_scenarios_dir).resolve() | ||
assert str(exc.value) == f"`default_scenarios_dir` ('{scenarios_dir}') does not exist" | ||
|
||
|
||
def test_validate_with_non_directory_scenario_dir(tmp_dir: Path): | ||
with given: | ||
existing_file = tmp_dir / "scenario.py" | ||
existing_file.touch() | ||
|
||
class CustomScenarioDir(Config): | ||
default_scenarios_dir = existing_file | ||
|
||
validator = ConfigValidator(CustomScenarioDir) | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate() | ||
|
||
with then: | ||
assert exc.type is NotADirectoryError | ||
assert str(exc.value) == f"`default_scenarios_dir` ('{existing_file}') is not a directory" | ||
|
||
|
||
@pytest.mark.usefixtures(tmp_dir.__name__) | ||
def test_validate_with_scenario_dir_outside_project_dir(): | ||
with given: | ||
class CustomScenarioDir(Config): | ||
default_scenarios_dir = "/tmp" | ||
|
||
validator = ConfigValidator(CustomScenarioDir) | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate() | ||
|
||
with then: | ||
assert exc.type is ValueError | ||
|
||
scenario_dir = Path(CustomScenarioDir.default_scenarios_dir).resolve() | ||
assert str(exc.value) == ( | ||
f"`default_scenarios_dir` ('{scenario_dir}') must be inside the project directory " | ||
f"('{Config.project_dir}')" | ||
) |
143 changes: 143 additions & 0 deletions
143
tests/commands/run_command/test_plugin_config_validator.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
from os import linesep | ||
|
||
from baby_steps import given, then, when | ||
from pytest import raises | ||
|
||
from vedro.commands.run_command._plugin_config_validator import PluginConfigValidator | ||
from vedro.core import Plugin, PluginConfig | ||
|
||
from ._utils import tmp_dir | ||
|
||
__all__ = ("tmp_dir",) # fixtures | ||
|
||
|
||
class CustomPlugin(Plugin): | ||
pass | ||
|
||
|
||
class CustomPluginConfig(PluginConfig): | ||
plugin = CustomPlugin | ||
|
||
|
||
def test_validate(): | ||
with given: | ||
class CustomPluginConfigWithDependency(PluginConfig): | ||
plugin = CustomPlugin | ||
depends_on = [CustomPluginConfig] | ||
|
||
validator = PluginConfigValidator() | ||
|
||
with when: | ||
res = validator.validate(CustomPluginConfigWithDependency) | ||
|
||
with then: | ||
assert res is None | ||
|
||
|
||
def test_validate_not_subclass(): | ||
with given: | ||
validator = PluginConfigValidator() | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate(object) | ||
|
||
with then: | ||
assert exc.type is TypeError | ||
assert str(exc.value) == ( | ||
"PluginConfig '<class 'object'>' must be a subclass of 'vedro.core.PluginConfig'" | ||
) | ||
|
||
|
||
def test_validate_not_subclass_plugin(): | ||
with given: | ||
class InvalidPluginConfig(PluginConfig): | ||
plugin = object | ||
|
||
validator = PluginConfigValidator() | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate(InvalidPluginConfig) | ||
|
||
with then: | ||
assert exc.type is TypeError | ||
assert str(exc.value) == ( | ||
"Attribute 'plugin' in 'InvalidPluginConfig' must be a subclass of 'vedro.core.Plugin'" | ||
) | ||
|
||
|
||
def test_validate_depends_on_not_sequence(): | ||
with given: | ||
class InvalidPluginConfig(PluginConfig): | ||
plugin = CustomPlugin | ||
depends_on = object() | ||
|
||
validator = PluginConfigValidator() | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate(InvalidPluginConfig) | ||
|
||
with then: | ||
assert exc.type is TypeError | ||
assert str(exc.value) == ( | ||
"Attribute 'depends_on' in 'InvalidPluginConfig' plugin must be a list or " | ||
"another sequence type (<class 'object'> provided). " + | ||
linesep.join([ | ||
"Example:", | ||
" @computed", | ||
" def depends_on(cls):", | ||
" return [Config.Plugins.Tagger]" | ||
]) | ||
) | ||
|
||
|
||
def test_validate_depends_on_not_subclass(): | ||
with given: | ||
class InvalidPluginConfig(PluginConfig): | ||
plugin = CustomPlugin | ||
depends_on = [object] | ||
|
||
validator = PluginConfigValidator() | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate(InvalidPluginConfig) | ||
|
||
with then: | ||
assert exc.type is TypeError | ||
assert str(exc.value) == ( | ||
"Dependency '<class 'object'>' in 'depends_on' of 'InvalidPluginConfig' " | ||
"must be a subclass of 'vedro.core.PluginConfig'" | ||
) | ||
|
||
|
||
def test_validate_unknown_attributes(): | ||
with given: | ||
class InvalidPluginConfig(PluginConfig): | ||
plugin = CustomPlugin | ||
unknown = "unknown" | ||
|
||
validator = PluginConfigValidator() | ||
|
||
with when, raises(BaseException) as exc: | ||
validator.validate(InvalidPluginConfig) | ||
|
||
with then: | ||
assert exc.type is AttributeError | ||
assert str(exc.value) == ( | ||
"InvalidPluginConfig configuration contains unknown attributes: unknown" | ||
) | ||
|
||
|
||
def test_validate_unknown_attributes_disabled(): | ||
with given: | ||
class InvalidPluginConfig(PluginConfig): | ||
plugin = CustomPlugin | ||
unknown = "unknown" | ||
|
||
validator = PluginConfigValidator(validate_plugins_attrs=False) | ||
|
||
with when: | ||
validator.validate(InvalidPluginConfig) | ||
|
||
with then: | ||
# no exception raised | ||
pass |
Oops, something went wrong.