Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance plugin system with dependency management #118

Merged
merged 14 commits into from
Jan 7, 2025
2 changes: 1 addition & 1 deletion tests/commands/run_command/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from vedro.commands import CommandArgumentParser

__all__ = ("tmp_dir", "create_scenario", "arg_parser",)
__all__ = ("tmp_dir", "create_scenario", "arg_parser", "ArgumentParser",)


@pytest.fixture()
Expand Down
100 changes: 100 additions & 0 deletions tests/commands/run_command/test_config_validator.py
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 tests/commands/run_command/test_plugin_config_validator.py
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
Loading
Loading