Skip to content

Commit

Permalink
feat(optional_logger): new optional logger class
Browse files Browse the repository at this point in the history
  • Loading branch information
serhez committed Apr 23, 2024
1 parent 63d4ab2 commit 7f79684
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 6 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,35 @@ You can extend the base class `Logger` in order to create a custom logger to sui
### Customized log levels

You can register new log levels by using `register_level(level, color)`. Once you register a level `"MyLevel"`, you can use it as `logger.log(message, LogLevel.MYLEVEL)`. The method `log` also supports a string as a level, which will be upper-cased and given a default color; the level can also be `None`, which will simply log the message as a stand-alone.

### Optional loggers
This library also includes a wrapper around the `Logger` class called `OptionalLogger`, which allows you to use a logger which could be `None` without having to check its validity before every use. Hence, instead of this:

```python
from mloggers import Logger


class MyClass:
def __init__(self, logger: Logger | None):
self._logger = logger

def my_function(self):
if self._logger is not None:
self._logger.info("Message")
```

You can do this:

```python
from mloggers import Logger, OptionalLogger


class MyClass:
def __init__(self, logger: Logger | None):
self._logger = OptionalLogger(logger)

def my_function(self):
self._logger.info("Message")
```

If the logger is `None`, nothing will happen (not even an error!).
4 changes: 3 additions & 1 deletion mloggers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
from .file_logger import FileLogger
from .logger import Logger
from .multi_logger import MultiLogger
from .optional_logger import OptionalLogger
from .wandb_logger import WandbLogger

__all__ = [
"ConsoleLogger",
"FileLogger",
"LogLevel",
"Logger",
"MultiLogger",
"OptionalLogger",
"WandbLogger",
"LogLevel",
"register_level",
]
9 changes: 5 additions & 4 deletions mloggers/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def set_level(self, level: LogLevel | int):
- If a string, it must be a valid log level (e.g., INFO, WARN, ERROR, DEBUG, etc.).
- If a `LogLevel` object, it will be used as-is.
"""

self._log_level = level.value["level"] if isinstance(level, LogLevel) else level

def _call_impl(self, *args, **kwargs):
Expand All @@ -107,7 +108,7 @@ def info(
**kwargs: Any,
):
"""
Wrapper for calling `log` with level=LogLevel.INFO.
Wrapper for calling `log` with `level=LogLevel.INFO`.
### Parameters
----------
Expand All @@ -127,7 +128,7 @@ def warn(
**kwargs: Any,
):
"""
Wrapper for calling `log` with level=LogLevel.WARN.
Wrapper for calling `log` with `level=LogLevel.WARN`.
### Parameters
----------
Expand All @@ -150,7 +151,7 @@ def error(
**kwargs: Any,
):
"""
Wrapper for calling `log` with level=LogLevel.ERROR.
Wrapper for calling `log` with `level=LogLevel.ERROR`.
### Parameters
----------
Expand All @@ -170,7 +171,7 @@ def debug(
**kwargs: Any,
):
"""
Wrapper for calling `log` with level=LogLevel.DEBUG.
Wrapper for calling `log` with `level=LogLevel.DEBUG`.
### Parameters
----------
Expand Down
45 changes: 45 additions & 0 deletions mloggers/optional_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from mloggers.logger import Logger


class OptionalLogger(Logger):
"""
A wrapper for a logger which can be `None`.
This object can be useful library-side to not force your users to use `mloggers`.
The benefit of this wrapper is that you never have to use `if logger is not None:` again!
Example:
```python
from mloggers import Logger, OptionalLogger
def some_function(logger: Logger | None):
my_logger = OptionalLogger(logger)
# If the logger is None, nothing will happen (not even an error)
my_logger.info("This will only log if the logger is not None.")
```
"""

def __init__(self, logger: Logger | None = None):
"""
Initialize the OptionalLogger.
### Parameters
- [optional] `logger`: the logger to wrap.
"""

# Hopefully this name is unique enough
self.__internal_wrapped_logger__ = logger

def log(self, _):
pass

def __getattribute__(self, attr: str):
"""Re-route all attribute access to the logger if it exists."""

if attr == "__internal_wrapped_logger__":
return object.__getattribute__(self, attr)

try:
return getattr(self.__internal_wrapped_logger__, attr)
except AttributeError:
return lambda *_: None
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "mloggers"
version = "1.2.0"
version = "1.2.1"
authors = [
{ name = "Sergio Hernandez Gutierrez", email = "[email protected]" },
{ name = "Matteo Merler", email = "[email protected]" },
Expand Down

0 comments on commit 7f79684

Please sign in to comment.