Skip to content

Commit

Permalink
Enhance plugin system with dependency management (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsv1 authored Jan 7, 2025
1 parent 37425fc commit 8a0479e
Show file tree
Hide file tree
Showing 43 changed files with 2,044 additions and 334 deletions.
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

0 comments on commit 8a0479e

Please sign in to comment.