From 3ef3e72720567d540a890a99976accd4fb992d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl> Date: Wed, 30 Sep 2020 17:19:47 +0200 Subject: [PATCH] add satella.dao, 2.11.20 --- CHANGELOG.md | 1 + docs/dao.rst | 17 +++++++++++++ docs/index.rst | 1 + satella/__init__.py | 2 +- satella/dao/__init__.py | 53 +++++++++++++++++++++++++++++++++++++++++ tests/test_dao.py | 20 ++++++++++++++++ 6 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 docs/dao.rst create mode 100644 satella/dao/__init__.py create mode 100644 tests/test_dao.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1d10fd..eae85507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ * unit testing engine changed from nose2 to pytest, since nose2 couldn't run tests on multiple threads It's much faster this way. * added extra wait for the futures in `sync_threadpool` +* added `satella.dao` diff --git a/docs/dao.rst b/docs/dao.rst new file mode 100644 index 00000000..30da9b4c --- /dev/null +++ b/docs/dao.rst @@ -0,0 +1,17 @@ +=== +DAO +=== + +This is for objects that are meant to represent a database entry, +and are lazily loadable. + +It's constructor expects identifier and a keyword argument of load_lazy, which will control +when will the object be fetched from DB. + +If True, then it will be fetched at constructor time, ie. the constructor will call .refresh(). +If False, then it will be fetched when it is first requested, via `must_be_loaded` decorator. + +.. autoclass:: satella.dao.Loadable + :members: + +.. autofunction:: satella.dao.must_be_loaded diff --git a/docs/index.rst b/docs/index.rst index 72ccf786..7e77d41f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ Visit the project's page at GitHub_! instrumentation/memory instrumentation/metrics exception_handling + dao json posix import diff --git a/satella/__init__.py b/satella/__init__.py index 5d0387b3..f1f3d0f4 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.11.20_a2' +__version__ = '2.11.20' diff --git a/satella/dao/__init__.py b/satella/dao/__init__.py new file mode 100644 index 00000000..201492fd --- /dev/null +++ b/satella/dao/__init__.py @@ -0,0 +1,53 @@ +from abc import ABCMeta, abstractmethod + +from satella.coding.decorators.decorators import wraps + +__all__ = ['Loadable', 'must_be_loaded'] + +class Loadable(metaclass=ABCMeta): + """ + Any class that can be loaded lazily. + + It's keyword argument, load_lazy is expected to control lazy loading. If set to True, + DB will be hit as a part of this object's constructor. + + If False, you will need to load it on-demand via must_be_loaded decorator. + """ + + __slots__ = ('_loaded',) + + def __init__(self, load_lazy: bool = False): + self._loaded: bool = False + if not load_lazy: + self.refresh() + + @abstractmethod + def refresh(self, load_from=None) -> None: + """ + Optionally provide a class to load this class from. + + Override me, calling me in a super method. + + :param load_from: serialized object. If not given, the DB will be hit + """ + self._loaded = True + + +def must_be_loaded(fun): + """ + A decorator for Loadable's methods. + + Assures that .refresh() is called prior to executing that method, ie. the object + is loaded from the DB + """ + + @wraps(fun) + def inner(self, *args, **kwargs): + assert isinstance(self, + Loadable), 'must_be_loaded called with a class that does not subclass ' \ + 'Loadable' + if not self._loaded: + self.refresh() + return fun(self, *args, **kwargs) + + return inner diff --git a/tests/test_dao.py b/tests/test_dao.py new file mode 100644 index 00000000..7d37808b --- /dev/null +++ b/tests/test_dao.py @@ -0,0 +1,20 @@ +import unittest + +from satella.dao import Loadable, must_be_loaded + + +class TestDAO(unittest.TestCase): + def test_something(self): + class Load(Loadable): + def __init__(self, load_lazy=False): + super().__init__(load_lazy=load_lazy) + + @must_be_loaded + def method_accessed(self): + assert self._loaded + + def refresh(self, load_from=None) -> None: + super().refresh(load_from=load_from) + + l = Load() + l.method_accessed() -- GitLab