Skip to content

Commit 99359ee

Browse files
committed
refactor: decorators to work from inside the readylog package
1 parent 1646fa6 commit 99359ee

File tree

6 files changed

+118
-11
lines changed

6 files changed

+118
-11
lines changed

ruff.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@ select = [
4242
"tests/test_log.py" = [
4343
"N813", # camelcase-imported-as-lowercase
4444
]
45+
"tests/test_decorators.py" = [
46+
"N813", # camelcase-imported-as-lowercase
47+
"PLR917", # too-many-positional-arguments
48+
]
4549
"src/readylog/__init__.py" = [
4650
"PLR0913", # too-many-arguments
4751
"PLR0914", # too-many-local-variables
4852
"PLR0917", # too-many-positional-arguments
53+
"PLC2701", # import-private-name
4954
]
5055

5156
extend-safe-fixes = [

src/readylog/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections.abc import Callable
2-
from logging import FileHandler, StreamHandler, getLevelName, getLevelNamesMapping
2+
from logging import FileHandler, StreamHandler, _checkLevel, getLevelName
33
from pathlib import Path
4+
from sys import modules
45

56

67
def create_dict_config(
@@ -11,10 +12,9 @@ def create_dict_config(
1112
console_handler_factory: Callable = StreamHandler,
1213
file_handler_factory: Callable = FileHandler,
1314
) -> dict[str, str]:
14-
level_names = getLevelNamesMapping().keys()
15-
assert console_log_level in level_names
16-
assert file_log_level in level_names
17-
min_level = getLevelName(min(getLevelName(console_log_level), getLevelName(file_log_level)))
15+
console_log_level = _checkLevel(console_log_level)
16+
file_log_level = _checkLevel(file_log_level)
17+
min_level = getLevelName(min(console_log_level, file_log_level))
1818

1919
custom_file_formatter_conf = {
2020
"format": "{message:<50s} {levelname:>9s} {asctime}.{msecs:03.0f} {module}({lineno}) {funcName}",
@@ -100,6 +100,7 @@ def create_dict_config(
100100
loggers_dict = {
101101
app_name: custom_logger_conf,
102102
"__main__": custom_logger_conf,
103+
f"{modules[__name__].__spec__.parent}.decorators": custom_logger_conf,
103104
}
104105

105106
dict_config = {

src/readylog/decorators.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from functools import wraps
22
from itertools import product
3-
from logging import DEBUG, getLevelName, getLevelNameMapping, getLogger
3+
from logging import DEBUG, getLevelName, getLevelNamesMapping, getLogger
4+
from sys import modules
45

56
logger = getLogger(__name__)
67

@@ -48,7 +49,7 @@ def wrapper(*args, **kwargs):
4849
return decorator
4950

5051

51-
level_names = getLevelNameMapping().values()
52+
level_names = getLevelNamesMapping().keys()
5253

5354
for level, direction in product(level_names, ("_in", "_out", "")):
5455
if direction == "_in":
@@ -61,7 +62,7 @@ def wrapper(*args, **kwargs):
6162
enter = True
6263
exit = True
6364
setattr(
64-
__name__,
65+
modules[__name__],
6566
f"{level.lower()}{direction}",
6667
log_io(getLevelName(level), enter=enter, exit=exit),
6768
)

tests/conftest.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1+
from collections.abc import Callable
2+
from pathlib import Path
3+
from typing import Any
4+
15
from pytest import FixtureRequest, fixture
26
from watchdog.events import FileModifiedEvent, FileSystemEventHandler
37
from watchdog.observers import Observer
48

59

610
@fixture
7-
def tmp_log_file(tmp_path):
11+
def tmp_log_file(tmp_path) -> Path:
812
path = tmp_path / "log" / "debug.log"
913
path.parent.mkdir(parents=True, exist_ok=False)
1014
return path
1115

1216

1317
@fixture
14-
def app_name():
18+
def app_name() -> str:
1519
return "test_app"
1620

1721

@@ -39,3 +43,14 @@ def on_modified(event: FileModifiedEvent):
3943

4044
observer.stop()
4145
observer.join()
46+
47+
48+
def function_under_test(*args, **kwargs) -> tuple[tuple[Any, ...], dict[Any, Any]]:
49+
return args, kwargs
50+
51+
52+
@fixture
53+
def decorated_function(request: FixtureRequest) -> Callable:
54+
decorator = request.param
55+
decorated_func = decorator(function_under_test)
56+
return decorated_func

tests/test_decorators.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from collections.abc import Callable
2+
from logging import DEBUG
3+
from logging.config import dictConfig as configure_logging
4+
from pathlib import Path
5+
6+
from pytest import CaptureFixture, mark, param
7+
8+
from readylog import create_dict_config
9+
from readylog.decorators import (
10+
critical,
11+
critical_in,
12+
critical_out,
13+
debug,
14+
debug_in,
15+
debug_out,
16+
error,
17+
error_in,
18+
error_out,
19+
info,
20+
info_in,
21+
info_out,
22+
warning,
23+
warning_in,
24+
warning_out,
25+
)
26+
27+
decorators = {
28+
"debug": debug,
29+
"debug_in": debug_in,
30+
"debug_out": debug_out,
31+
"info": info,
32+
"info_in": info_in,
33+
"info_out": info_out,
34+
"warning": warning,
35+
"warning_in": warning_in,
36+
"warning_out": warning_out,
37+
"error": error,
38+
"error_in": error_in,
39+
"error_out": error_out,
40+
"critical": critical,
41+
"critical_in": critical_in,
42+
"critical_out": critical_out,
43+
}
44+
45+
46+
@mark.parametrize(
47+
"decorated_function, expect_function_entry_logged, expect_function_return_logged",
48+
(
49+
*(
50+
param(
51+
decorator,
52+
not decorator_name.endswith("_out"),
53+
not decorator_name.endswith("_in"),
54+
id=decorator_name,
55+
)
56+
for decorator_name, decorator in decorators.items()
57+
),
58+
),
59+
indirect=["decorated_function"],
60+
)
61+
def test_decorators(
62+
tmp_log_file: Path,
63+
app_name: str,
64+
decorated_function: Callable,
65+
expect_function_entry_logged: bool,
66+
expect_function_return_logged: bool,
67+
capsys: CaptureFixture,
68+
) -> None:
69+
config = create_dict_config(tmp_log_file, app_name, console_log_level=DEBUG)
70+
configure_logging(config)
71+
72+
decorated_function("some_arg", some_kwarg="some_value")
73+
74+
captured = capsys.readouterr()
75+
lines = captured.err.strip()
76+
77+
if expect_function_entry_logged:
78+
assert "Calling function_under_test" in lines
79+
if not expect_function_return_logged:
80+
assert "function_under_test returned" not in lines
81+
82+
if expect_function_return_logged:
83+
assert "function_under_test returned" in lines
84+
if not expect_function_entry_logged:
85+
assert "Calling function_under_test" not in lines

tests/test_general.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def test_config_creation(tmp_log_file, app_name):
1616
),
1717
)
1818
def test_config_creation_with_wrong_level(tmp_log_file, app_name, kwargs):
19-
with raises(AssertionError):
19+
with raises(ValueError):
2020
_ = create_dict_config(tmp_log_file, app_name, **kwargs)
2121

2222

0 commit comments

Comments
 (0)