Skip to content

API Reference

core

logged_property

Bases: property

Marks a property to be logged.

Thin wrapper around a standard property. Used in a logger class when the property is to be logged each time the wrapped function is called.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/core.py
 7
 8
 9
10
11
class logged_property(property):
    """Marks a property to be logged.

    Thin wrapper around a standard property. Used in a logger class when the property is
    to be logged each time the wrapped function is called."""

log(value, key=None)

Log value with the closest Logger.

This function takes a value and optionally a key to call it. If no key is provided, the name of the variable that the value is assigned to is used.

The closest logger is determined by stepping backwards through the call stack until a __call__ method of a class subclassing BaseLogger is reached.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/core.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def log(value: Any, key: str = None):
    """Log `value` with the closest Logger.

    This function takes a value and optionally a key to call it.
    If no key is provided, the name of the variable that the value is
    assigned to is used.

    The closest logger is determined by stepping backwards through the call stack until
    a `__call__` method of a class subclassing `BaseLogger` is reached."""
    # Import outside toplevel avoids circular import
    from declog.logger import BaseLogger

    if key is None:
        key = _get_var_name(value)

    # Search for closest Logger
    for frame, *_ in inspect.stack():
        try:
            (logger,) = [
                val for val in frame.f_locals.values() if isinstance(val, BaseLogger)
            ]
        except ValueError:
            pass  # move on to next frame
        else:
            logger.log(key, value)
            break
    else:
        raise SyntaxError("No logger found in the call stack.")

database

BaseDatabase

Bases: UserDict

Base class for Databases to be used with declog Loggers.

When a key which does not yet exist is accessed, an empty database is created as the value for that key. This enables a nested keys to be used. All Database types must have this functionality in order for them to be used by a declog Logger.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/database.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class BaseDatabase(UserDict):
    """Base class for Databases to be used with declog Loggers.

    When a key which does not yet exist is accessed, an empty database
    is created as the value for that key. This enables a nested keys to
    be used. All Database types must have this functionality in order
    for them to be used by a declog Logger."""

    def __init__(self, writeback=False):
        super(BaseDatabase, self).__init__()
        self.writeback = writeback

    def __setitem__(self, key, value):
        super(BaseDatabase, self).__setitem__(key, value)
        if self.writeback:
            self.write()

    def __missing__(self, key):
        self[key] = BaseDatabase()
        return self[key]

    @staticmethod
    def cast(mutable_mapping, new_type):
        out = new_type()
        for key, value in mutable_mapping.items():
            if isinstance(value, MutableMapping):
                out[key] = BaseDatabase.cast(value, new_type)
            else:
                out[key] = value
        return out

    def to_dict(self):
        return BaseDatabase.cast(self.data, dict)

    @classmethod
    def from_dict(cls, dictionary):
        instance = cls()
        instance.data = BaseDatabase.cast(dictionary, BaseDatabase)
        return instance

PickleDatabase

Bases: PersistentDatabase

BaseDatabase which gets written to a pickle file.

Drawback of pickle is the whole thing must be read from path so for a big database this will be slow.

Upside is native python objects can be stored so absolutely anything can be logged, while for others it is more limited.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/database.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
class PickleDatabase(PersistentDatabase):
    """BaseDatabase which gets written to a pickle file.

    Drawback of pickle is the whole thing must be read
    from path so for a big database this will be slow.

    Upside is native python objects can be stored so absolutely
    anything can be logged, while for others it is more limited."""

    def read(self):
        try:
            with open(self.path, "rb") as f:
                self.data = pickle.load(f)
        except FileNotFoundError:
            pass

    def write(self):
        with open(self.path, "wb") as f:
            pickle.dump(self.data, f)

StdOutDatabase

Bases: BaseDatabase

Database which prints itself every time a value gets logged.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/database.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class StdOutDatabase(BaseDatabase):
    """Database which prints itself every time a value gets logged."""

    def __init__(self, _root=None):
        super(StdOutDatabase, self).__init__(writeback=True)
        if _root is None:
            self.root = self
        else:
            self.root = _root

    def __missing__(self, key):
        self[key] = StdOutDatabase(_root=self.root)
        return self[key]

    def write(self):
        print(self.root)

logger

Loggers are classes which are applied to functions as decorators. When the function is called, the __call__ method of the Logger is executed, which allows it to log the arguments supplied to the function, in addition to other marked properties.

The Logger is separated from the database backends, allowing one logger to be used with multiple backends and vice versa.

BaseLogger

Base Logger to be subclassed by the user.

The class attributes db and unique_keys should be set by subclasses.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/__init__.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class BaseLogger:
    """Base Logger to be subclassed by the user.

    The class attributes `db` and `unique_keys` should be set by
    subclasses."""

    db: Type[BaseDatabase] = None
    unique_keys: list[str] = None

    def __init__(self, func: callable):
        self._func = func
        update_wrapper(self, self._func)
        self.db_entry = None  # generated at call time

    def __call__(self, *args, **kwargs):
        """Evaluate wrapped function and log variables.

        Collates all the arguments and any [logged properties]() in a dictionary.
        Saves all items in the dictionary to the database `db` under a key specified
        by the class' `unique_keys` attribute."""
        # populate database with arguments and environment information
        argument_dict = self._build_arg_dict(args, kwargs)
        environment_dict = self._build_env_dict()
        call_dict = argument_dict | environment_dict
        self.db_entry = self._generate_db_entry_and_remove_keys_from_info(call_dict)
        self.db_entry |= call_dict
        # populate database with intermediate variables and result
        result = self._func(*args, *kwargs)
        self.db_entry["result"] = result
        self.db_entry = None
        try:
            self.db.write()
        except AttributeError:
            pass
        return result

    def log(self, key: str, value: Any):
        """Log the key and value to `db` under the current entry."""
        self.db_entry[key] = value

    @classmethod
    def set(cls, **kwargs):
        """Set kwargs as logged properties."""

        def inner(func):
            for key, value in kwargs.items():
                setattr(cls, key, logged_property(lambda instance: value))
            logger = cls(func)
            return logger

        return inner

    def _build_arg_dict(self, args: list[Any], kwargs: dict[str, Any]) -> dict:
        positional_args = {
            k: v for k, v in zip(inspect.signature(self._func).parameters, args)
        }
        default_args = {
            k: v.default
            for k, v in inspect.signature(self._func).parameters.items()
            if v.default is not inspect.Parameter.empty
        }
        keyword_args = (
            default_args | kwargs
        )  # update defaults with supplied keyword arguments
        return positional_args | keyword_args

    def _build_env_dict(self) -> dict:
        env_dict = {}
        cls = type(self)
        for obj_name in dir(self):
            try:
                obj_type = getattr(cls, obj_name)
            except AttributeError:
                obj_type = getattr(self, obj_name)
            if isinstance(obj_type, logged_property):
                obj = getattr(self, obj_name)
                env_dict[obj_name] = obj
        return env_dict

    def _generate_db_entry_and_remove_keys_from_info(self, info: dict) -> dict:
        """Returns the dictionary corresponding to the specific call entry"""
        if self.unique_keys is None:
            raise NotImplementedError
        else:
            entry = self.db
            for key in self.unique_keys:
                value = info.pop(key)
                entry = entry[value]
            return entry

__call__(*args, **kwargs)

Evaluate wrapped function and log variables.

Collates all the arguments and any logged properties in a dictionary. Saves all items in the dictionary to the database db under a key specified by the class' unique_keys attribute.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/__init__.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def __call__(self, *args, **kwargs):
    """Evaluate wrapped function and log variables.

    Collates all the arguments and any [logged properties]() in a dictionary.
    Saves all items in the dictionary to the database `db` under a key specified
    by the class' `unique_keys` attribute."""
    # populate database with arguments and environment information
    argument_dict = self._build_arg_dict(args, kwargs)
    environment_dict = self._build_env_dict()
    call_dict = argument_dict | environment_dict
    self.db_entry = self._generate_db_entry_and_remove_keys_from_info(call_dict)
    self.db_entry |= call_dict
    # populate database with intermediate variables and result
    result = self._func(*args, *kwargs)
    self.db_entry["result"] = result
    self.db_entry = None
    try:
        self.db.write()
    except AttributeError:
        pass
    return result

log(key, value)

Log the key and value to db under the current entry.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/__init__.py
53
54
55
def log(self, key: str, value: Any):
    """Log the key and value to `db` under the current entry."""
    self.db_entry[key] = value

set(**kwargs) classmethod

Set kwargs as logged properties.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/__init__.py
57
58
59
60
61
62
63
64
65
66
67
@classmethod
def set(cls, **kwargs):
    """Set kwargs as logged properties."""

    def inner(func):
        for key, value in kwargs.items():
            setattr(cls, key, logged_property(lambda instance: value))
        logger = cls(func)
        return logger

    return inner

DefaultLogger

Bases: BaseLogger, FunctionNameMixin, DateTimeMixin

Logger to capture function name, arguments and call time.

A basic 'batteries included' logger. For use as a quick way to go from zero to logging and demonstrating how easy it is to write a custom Logger using mixins.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/__init__.py
108
109
110
111
112
113
114
115
116
class DefaultLogger(BaseLogger, FunctionNameMixin, DateTimeMixin):
    """Logger to capture function name, arguments and call time.

    A basic 'batteries included' logger. For use as a quick way to go
    from zero to logging and demonstrating how easy it is to write a
    custom Logger using mixins."""

    db = PickleDatabase("db.pickle")
    unique_keys = ["function_name", "datetime"]

mixins

Collection of classes containing commonly used logged properties.

These are used as mixins to save time making custom loggers. For an example of this in action, take a look at the DefaultLogger.

DateTimeMixin

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/mixins.py
20
21
22
23
24
class DateTimeMixin:
    @logged_property
    def datetime(self) -> str:
        """The time and date."""
        return str(datetime.now())
datetime()

The time and date.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/mixins.py
21
22
23
24
@logged_property
def datetime(self) -> str:
    """The time and date."""
    return str(datetime.now())

FunctionNameMixin

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/mixins.py
13
14
15
16
17
class FunctionNameMixin:
    @logged_property
    def function_name(self) -> str:
        """Name of the wrapped function."""
        return self._func.__name__
function_name()

Name of the wrapped function.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/mixins.py
14
15
16
17
@logged_property
def function_name(self) -> str:
    """Name of the wrapped function."""
    return self._func.__name__

UserMixin

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/mixins.py
27
28
29
30
31
class UserMixin:
    @logged_property
    def user(self) -> str:
        """The current user."""
        return getuser()
user()

The current user.

Source code in /opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/declog/logger/mixins.py
28
29
30
31
@logged_property
def user(self) -> str:
    """The current user."""
    return getuser()

utils