Skip to content

Commit

Permalink
Merge pull request #81 from nqminds/feat/add-LoggerContextManager
Browse files Browse the repository at this point in the history
feat: add `LoggerContextManager` Python class
  • Loading branch information
aloisklink authored Oct 31, 2023
2 parents 52a856f + 8ba88ea commit 3bb346b
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 6 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `nqm.logger.LoggerContextManager` Python class, which automatically
creates/deletes a
[`nqm.logger.Logger`](https://nqminds.github.io/nqm-irimager/apidoc/nqm.irimager.html#nqm.irimager.Logger)
object when used in a [`with:` statement][PEP 343] ([#81][]).

[#81]: https://github.com/nqminds/nqm-irimager/pull/81
[PEP 343]: https://peps.python.org/pep-0343/

## [1.0.0] - 2023-10-30

Initial release.
Expand Down
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ add_custom_command(
"-I;$<JOIN:$<TARGET_PROPERTY:irimager,INCLUDE_DIRECTORIES>,;-I;>"
-std=c++17
"${CMAKE_CURRENT_SOURCE_DIR}/src/nqm/irimager/irimager_class.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/nqm/irimager/logger_context_manager.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/nqm/irimager/logger.hpp"
COMMAND_EXPAND_LISTS
VERBATIM
Expand Down Expand Up @@ -165,6 +166,19 @@ target_link_libraries(logger
irlogger_to_spd
)

add_library(logger_context_manager OBJECT
"src/nqm/irimager/logger_context_manager.cpp"
)
set_target_properties(logger_context_manager PROPERTIES
PRIVATE_HEADER
"src/nqm/irimager/logger_context_manager.hpp"
POSITION_INDEPENDENT_CODE ON # -fPIC
)
target_link_libraries(logger_context_manager
PUBLIC
logger
)

pybind11_add_module(irimager MODULE
"src/nqm/irimager/irimager.cpp"
"${CMAKE_CURRENT_BINARY_DIR}/docstrings.h"
Expand All @@ -180,6 +194,7 @@ target_link_libraries(irimager
irimager_class
irlogger_parser
irlogger_to_spd
logger_context_manager
logger
)

Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,17 @@ easy as:
```python
import datetime
import logging
from nqm.irimager import IRImager, Logger
from nqm.irimager import IRImager, LoggerContextManager

logging.basicConfig()
logging.getLogger().setLevel(1) # trace
logger = Logger()

# Your XML config,
# see http://documentation.evocortex.com/libirimager2/html/Overview.html#subsec_overview_config_file
XML_CONFIG = "tests/__fixtures__/382x288@27Hz.xml"
irimager = IRImager(XML_CONFIG)
with irimager:
with LoggerContextManager():
irimager = IRImager(XML_CONFIG)
with irimager:
print(f"Started at {datetime.datetime.now()}")
while True: # press CTRL+C to stop this program
try:
Expand All @@ -90,8 +91,6 @@ with irimager:
raise error
frame_in_celsius = array / (10 ** irimager.get_temp_range_decimal()) - 100
print(f"At {timestamp}: Average temperature is {frame_in_celsius.mean()}")

del logger # to stop
```

## Development
Expand Down
16 changes: 16 additions & 0 deletions src/nqm/irimager/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,19 @@ class Logger:
def __init__(self) -> None:
"""Creates a new logger using the default Python :py:class:`logging.Logger`"""
def __del__(self): ...

class LoggerContextManager:
"""Context Manager around a Logger object.
Designed for use with Python's ``with`` syntax.
"""

def __enter__(self) -> None:
"""Starts the logger if it's not already started."""
def __exit__(
self,
exc_type: typing.Optional[typing.Type[BaseException]],
exc: typing.Optional[BaseException],
traceback: typing.Optional[types.TracebackType],
) -> None:
"""Stops the logger"""
13 changes: 13 additions & 0 deletions src/nqm/irimager/irimager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "./irimager_class.hpp"
#include "./logger.hpp"
#include "./logger_context_manager.hpp"

#ifndef DOCSTRINGS_H
#error DOCSTRINGS_H must be defined to the output of pybind11_mkdocs
Expand Down Expand Up @@ -76,4 +77,16 @@ to control these cameras.)";

pybind11::class_<Logger>(m, "Logger", DOC(Logger))
.def(pybind11::init<>(), DOC(Logger, Logger));

pybind11::class_<LoggerContextManager>(m, "LoggerContextManager",
DOC(LoggerContextManager))
.def(pybind11::init<>())
.def("__enter__", &LoggerContextManager::start)
.def("__exit__",
[](LoggerContextManager *logger_cm,
[[maybe_unused]] const std::optional<pybind11::type>,
[[maybe_unused]] const std::optional<pybind11::object>,
[[maybe_unused]] const std::optional<pybind11::object>) {
logger_cm->stop();
});
}
9 changes: 9 additions & 0 deletions src/nqm/irimager/logger_context_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "./logger_context_manager.hpp"

LoggerContextManager::~LoggerContextManager() = default;

void LoggerContextManager::start() {
if (!logger_) logger_ = std::make_unique<Logger>();
}

void LoggerContextManager::stop() { logger_ = nullptr; }
31 changes: 31 additions & 0 deletions src/nqm/irimager/logger_context_manager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef NQM_IRIMAGER_LOGGER_CONTEXT_MANAGER
#define NQM_IRIMAGER_LOGGER_CONTEXT_MANAGER

#include <memory>

#include <pybind11/pybind11.h>

#include "./logger.hpp"

/**
* Context Manager around a Logger object.
*
* Designed for use with Python's ``with`` syntax.
*/
class LoggerContextManager {
public:
virtual ~LoggerContextManager();

/**
* Starts the logger if it's not already started.
*/
void start();

/** Stops the logger */
void stop();

private:
std::unique_ptr<Logger> logger_;
};

#endif /* NQM_IRIMAGER_LOGGER_CONTEXT_MANAGER */
41 changes: 41 additions & 0 deletions tests/test_logger_context_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Tests for nqm.irimager.Logging"""
import logging

import pytest

from nqm.irimager import LoggerContextManager


def test_logger_in_context_manager(caplog):
"""We should be able to wrap logger in a context manager"""

with caplog.at_level(logging.DEBUG):
with LoggerContextManager():
assert "Redirecting spdlogs to a callback." in caplog.text

caplog.clear()

# creating a second logger should be fine after the first contextmanager
# closes
with LoggerContextManager():
assert "Redirecting spdlogs to a callback." in caplog.text


def test_logger_in_context_manager_handles_exception(caplog):
"""We should be able to wrap logger in a context manager and handle exceptions"""

with caplog.at_level(logging.DEBUG):
# creating a second logger should be fine, even if the first
# contextmanager broke due to an exception
with pytest.raises(
NotImplementedError,
match="Testing whether contextmanager cleans up after errors",
):
with LoggerContextManager():
raise NotImplementedError(
"Testing whether contextmanager cleans up after errors"
)

caplog.clear()
with LoggerContextManager():
assert "Redirecting spdlogs to a callback." in caplog.text

0 comments on commit 3bb346b

Please sign in to comment.