diff --git a/src/pypelines/__init__.py b/src/pypelines/__init__.py index 0f49f0c017c872af43574f59c416d888e215ee26..c488c86fa35291fea2c19cf2eaafec35cc08c773 100644 --- a/src/pypelines/__init__.py +++ b/src/pypelines/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.70" +__version__ = "0.0.71" from . import loggs from .pipes import * diff --git a/src/pypelines/pipes.py b/src/pypelines/pipes.py index bce4e8a4d234614815f6fd3d6f710a1398bfecd0..74ed279c5bf4fa9ac5ae73f2e6ebacc2d0de4ed6 100644 --- a/src/pypelines/pipes.py +++ b/src/pypelines/pipes.py @@ -2,6 +2,7 @@ from .steps import BaseStep from .multisession import BaseMultisessionAccessor from .sessions import Session from .disk import BaseDiskObject +from .utils import to_snake_case from functools import wraps import inspect, hashlib @@ -13,6 +14,7 @@ from abc import ABCMeta, abstractmethod from typing import Callable, Type, Iterable, Protocol, TYPE_CHECKING, Literal, Dict from types import MethodType + if TYPE_CHECKING: from .pipelines import Pipeline @@ -65,7 +67,7 @@ class BasePipe(BasePipeType, metaclass=ABCMeta): None """ self.pipeline = parent_pipeline - self.pipe_name = self.__class__.__name__ + self.pipe_name = to_snake_case(self.__class__.__name__) _steps: Dict[str, MethodType] = {} @@ -84,6 +86,7 @@ class BasePipe(BasePipeType, metaclass=ABCMeta): for step_name, step in steps_members_scanner: print("step:", step_name) if not requires_is_step_attr or getattr(step, "is_step", False): + step_name = to_snake_case(step_name) _steps[step_name] = step if len(_steps) < 1: diff --git a/src/pypelines/steps.py b/src/pypelines/steps.py index 7b3174456117f358cd7819e0b792ed328c4c5436..5d6f0b69700033473cd8ae416a7cd40842e6e15c 100644 --- a/src/pypelines/steps.py +++ b/src/pypelines/steps.py @@ -1,6 +1,7 @@ from functools import wraps, partial, update_wrapper from .loggs import loggedmethod, NAMELENGTH from .arguments import autoload_arguments +from .utils import to_snake_case import logging, inspect from pandas import DataFrame @@ -56,7 +57,7 @@ def stepmethod(requires=[], version=None, do_dispatch=True, on_save_callbacks=[] function.is_step = True function.version = version function.do_dispatch = do_dispatch - function.step_name = function.__name__ + function.step_name = to_snake_case(function.__name__) function.callbacks = [on_save_callbacks] if not isinstance(on_save_callbacks, list) else on_save_callbacks return function @@ -77,7 +78,7 @@ class BaseStep: pipe: "BasePipe" pipeline: "Pipeline" - def __init__(self, pipeline: "Pipeline", pipe: "BasePipe", worker: MethodType, step_name=None): + def __init__(self, pipeline: "Pipeline", pipe: "BasePipe", worker: MethodType, step_name: str = ""): """Initialize a BaseStep object. Args: @@ -109,7 +110,7 @@ class BaseStep: self.do_dispatch = getattr(self.worker, "do_dispatch", False) self.version = getattr(self.worker, "version", 0) self.requires = getattr(self.worker, "requires", []) - self.step_name = getattr(self.worker, "step_name", step_name) + self.step_name = to_snake_case(getattr(self.worker, "step_name", step_name)) if not self.step_name: raise ValueError("Step name cannot be blank nor None") diff --git a/src/pypelines/utils.py b/src/pypelines/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e2820c1e7c60f6af93c14f37356f01fd4947788b --- /dev/null +++ b/src/pypelines/utils.py @@ -0,0 +1,10 @@ +import re + + +def to_snake_case(text): + # Replace spaces or hyphens with underscores + text = re.sub(r"[\s-]+", "_", text) + # Convert CamelCase to snake_case + text = re.sub(r"([a-z])([A-Z])", r"\1_\2", text) + # Convert all characters to lowercase + return text.lower() diff --git a/tests/test_core.py b/tests/test_core.py index ab56a380c8ae445718c670057d98a2741883e026..572cd4f33879e6dffec671468a48cdaae1d18415 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -13,38 +13,97 @@ from pypelines.sessions import Session from pypelines import Pipeline, stepmethod, BaseStep from pypelines.pickle_backend import PicklePipe +from pathlib import Path + @pytest.fixture -def test_class_based_pypeline(): +def pipeline_steps_group_class_based(): - pipeline = Pipeline("test_class_based") + test_pipeline = Pipeline("test_class_based") - @pipeline.register_pipe + @test_pipeline.register_pipe class MyPipe(PicklePipe): class Steps: def my_step(self, session, extra=""): - return 1 + return "a_good_result" my_step.requires = [] - return pipeline + return test_pipeline @pytest.fixture -def test_method_based_pypeline(): +def pipeline_method_based(): - pipeline = Pipeline("test_method_based") + test_pipeline = Pipeline("test_method_based") - @pipeline.register_pipe + @test_pipeline.register_pipe class MyPipe(PicklePipe): @stepmethod(requires=[]) def my_step(self, session, extra=""): - return 1 + return "a_good_result" + + return test_pipeline + + +def get_pipelines_fixtures(): + return ["pipeline_method_based", "pipeline_steps_group_class_based"] + + +@pytest.fixture +def session_root_path(): + directory = Path("./tests/temp_sessions_directory") + directory.mkdir(parents=True, exist_ok=True) + yield directory + + if directory.exists(): + + def remove_directory(path: Path): + print("removing :", path) + for child in path.iterdir(): + if child.is_file(): + child.unlink() + else: + remove_directory(child) + path.rmdir() + + remove_directory(directory) + + +@pytest.fixture +def session(session_root_path): + test_session = Session(subject="test_subject", date="2024-10-05", number=1, auto_path=True, path=session_root_path) + return test_session + + +@pytest.mark.parametrize("pipeline_fixture_name", get_pipelines_fixtures()) +def test_pypeline_creation(request, pipeline_fixture_name): + pipeline = request.getfixturevalue(pipeline_fixture_name) + + assert isinstance(pipeline.my_pipe.my_step, BaseStep) + assert hasattr(pipeline.my_pipe.my_step, "generate") + assert hasattr(pipeline.my_pipe.my_step, "load") + assert hasattr(pipeline.my_pipe.my_step, "save") + + +@pytest.mark.parametrize("pipeline_fixture_name", get_pipelines_fixtures()) +def test_pypeline_call(request, pipeline_fixture_name: str, session): + pipeline = request.getfixturevalue(pipeline_fixture_name) + + # expecting the output to not be present if the pipeline step was not generated first + with pytest.raises(ValueError): + assert pipeline.my_pipe.my_step.load(session) == "a_good_result" + + # this only calculates and returns the pipeline step output, and do not saves it + assert pipeline.my_pipe.my_step(session) == "a_good_result" + + # expecting the output to not be present if the pipeline step was not generated first + with pytest.raises(ValueError): + assert pipeline.my_pipe.my_step.load(session) == "a_good_result" + # generate the pipeline step output to file (saves it with generation mechanism) + assert pipeline.my_pipe.my_step.generate(session) == "a_good_result" -def test_pypeline_creation(test_class_based_pypeline): - assert isinstance(test_class_based_pypeline.MyPipe.my_step, BaseStep) - assert hasattr(test_class_based_pypeline.MyPipe.my_step, "generate") - assert hasattr(test_class_based_pypeline.MyPipe.my_step, "load") - assert hasattr(test_class_based_pypeline.MyPipe.my_step, "save") + # expecting the output to be present now + assert pipeline.my_pipe.my_step.load(session) == "a_good_result"