diff --git a/README.md b/README.md index 8c46335..7d2bed8 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,9 @@ occurs. Here is an example using the `Observable` module: ```python +from myutils.observable import make_observable, bind + + class Demo: def __init__(self): self.number = 1 @@ -121,6 +124,7 @@ MyUtils ## Contributing If you'd like to contribute: + 1. Fork the repository. 2. Create a feature branch (`git checkout -b feature/your-feature`). 3. Commit your changes (`git commit -m "Add some feature"`). @@ -138,6 +142,7 @@ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file ## Maintainers For any inquiries or support, feel free to contact the maintainer: + - **Name**: [Your Name Here] - **Email**: [your-email@example.com] - **GitHub**: [Your GitHub Profile](https://github.com/your-profile) @@ -146,4 +151,9 @@ For any inquiries or support, feel free to contact the maintainer: ## Acknowledgments -Special thanks to the Python and open-source community for their tools, inspiration, and support. \ No newline at end of file +Special thanks to the Python and open-source community for their tools, inspiration, and support. + +## Release History + +* 0.1.0 : Initial release +* 0.2.0 : Observable results can be collected using `collect_return_values` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 72726ba..8e7a2cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "myutils" -version = "0.1.0" +version = "0.2.0" description = "Base useful classes." readme = "README.md" authors = [ diff --git a/src/myutils/observable.py b/src/myutils/observable.py index ef053dc..c3db3c6 100644 --- a/src/myutils/observable.py +++ b/src/myutils/observable.py @@ -12,7 +12,7 @@ def make_observable(obj): class ObservableVersion(original_class): def __setattr__(self, name, value): - if name == '_listeners': + if name in ('_listeners', '_return_values'): super().__setattr__(name, value) return @@ -20,14 +20,24 @@ def make_observable(obj): super().__setattr__(name, value) if hasattr(self, '_listeners') and name in self._listeners: + res = [] for callback in self._listeners[name]: - callback(old_value, value) + res.append(callback(old_value, value)) + setattr(self, '_return_values', res) obj.__class__ = ObservableVersion obj._listeners = {} return obj +def collect_return_values(obj): + if not hasattr(obj, '_return_values'): + raise NotObservableError( + f"Object must be made observable with make_observable() before collecting return values" + ) + return obj._return_values + + def bind(obj, attr_name, callback): if not hasattr(obj, '_listeners'): raise NotObservableError( diff --git a/tests/test_observable.py b/tests/test_observable.py index b9255b6..34636ea 100644 --- a/tests/test_observable.py +++ b/tests/test_observable.py @@ -1,6 +1,6 @@ import pytest -from myutils.observable import NotObservableError, make_observable, bind +from myutils.observable import NotObservableError, make_observable, bind, collect_return_values # Test fixtures @@ -170,4 +170,38 @@ def test_i_can_preserve_original_class_behavior(): assert demo.get_double() == 10 # Test that isinstance still works - assert isinstance(demo, Demo) \ No newline at end of file + assert isinstance(demo, Demo) + + +def test_i_can_collect_the_updates(): + demo = Demo() + make_observable(demo) + + bind(demo, 'number', lambda old, new: new) + bind(demo, 'number', lambda old, new: new * 2) + + demo.number = 5 + assert collect_return_values(demo) == [5, 10] + + # another time to make sure there is no side effect + demo.number = 10 + assert collect_return_values(demo) == [10, 20] + + +def test_i_cannot_collect_updates_before_making_observable(): + demo = Demo() + + with pytest.raises(NotObservableError) as exc_info: + collect_return_values(demo) + + assert "must be made observable" in str(exc_info.value).lower() + + +def test_i_can_collect_none(): + demo = Demo() + make_observable(demo) + + bind(demo, 'number', lambda old, new: None) + + demo.number = 5 + assert collect_return_values(demo) == [None]