Skip to content

Commit 3a36f90

Browse files
authored
Merge pull request #2 from OutSquareCapital/typing-improvements
Typing improvements
2 parents b82f769 + b0c540a commit 3a36f90

File tree

12 files changed

+267
-185
lines changed

12 files changed

+267
-185
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ dist/
1313

1414
.pytest_cache/
1515

16+
# uv lock file
17+
uv.lock

logeye/config.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import sys
3+
from typing import Literal, TypeAlias
34

45
_ENABLED = True
56
_START_TIME = None
@@ -26,23 +27,25 @@
2627

2728
_GLOBAL_LOG_FILE = None
2829
_GLOBAL_LOG_FILE_ENABLED = True
30+
PathMode: TypeAlias = Literal["absolute", "project", "file"]
2931

32+
Mode: TypeAlias = Literal["edu", "educational", "full"]
3033

3134
# =========
3235
# TOGGLES
3336
# =========
3437

35-
def toggle_logs(enabled: bool):
38+
def toggle_logs(enabled: bool) -> None:
3639
global _ENABLED
3740
_ENABLED = enabled
3841

3942

40-
def toggle_global_log_file(enabled: bool):
43+
def toggle_global_log_file(enabled: bool) -> None:
4144
global _GLOBAL_LOG_FILE_ENABLED
4245
_GLOBAL_LOG_FILE_ENABLED = enabled
4346

4447

45-
def toggle_decorator_log_only(enabled: bool):
48+
def toggle_decorator_log_only(enabled: bool) -> None:
4649
"""
4750
Toggle only @log-decorated tracing.
4851
"""
@@ -51,7 +54,7 @@ def toggle_decorator_log_only(enabled: bool):
5154
_DECORATORS_ONLY = enabled
5255

5356

54-
def toggle_message_metadata(enabled: bool):
57+
def toggle_message_metadata(enabled: bool) -> None:
5558
"""
5659
Enable or disable metadata for message logs
5760
@@ -69,8 +72,7 @@ def toggle_message_metadata(enabled: bool):
6972
# =========
7073
# SETTERS
7174
# =========
72-
73-
def set_mode(mode: str):
75+
def set_mode(mode: Mode) -> None:
7476
"""
7577
Set global logging mode
7678
full or edu / educational
@@ -80,32 +82,22 @@ def set_mode(mode: str):
8082
_LOG_MODE = _normalize_mode(mode)
8183

8284

83-
def set_global_log_file(filepath):
85+
def set_global_log_file(filepath: str | None) -> None:
8486
"""
8587
Route LogEye output to this file globally.
8688
"""
8789
global _GLOBAL_LOG_FILE
8890
_GLOBAL_LOG_FILE = None if filepath is None else os.fspath(filepath)
8991

9092

91-
def set_path_mode(mode: str):
93+
def set_path_mode(mode: PathMode) -> None:
9294
global _PATH_MODE
9395

94-
if mode not in ("absolute", "project", "file"):
95-
raise ValueError("mode must be: absolute, project, file")
96-
9796
_PATH_MODE = mode
9897

99-
100-
# =========
101-
# OTHER
102-
# =========
103-
104-
def _normalize_mode(mode: str) -> str:
98+
def _normalize_mode(mode: Mode) -> Mode:
10599
if mode in ("edu", "educational"):
106100
return "educational"
107101

108102
if mode == "full":
109103
return "full"
110-
111-
raise ValueError("mode must be: full, edu, educational")

logeye/core.py

Lines changed: 89 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
from __future__ import annotations
2+
from types import FrameType
3+
from logeye.wrappers import LoggedObject
14
import sys
25
import inspect
36
import functools
4-
from collections.abc import Mapping
7+
from collections.abc import Callable, Iterable, Mapping
8+
from typing import TYPE_CHECKING, Literal, TypeVar, ParamSpec, overload
59

610
from . import config
711
from .emmiter import _emit
@@ -20,11 +24,17 @@
2024
_path,
2125
)
2226
from .introspection.frames import _caller_frame, _get_location
23-
27+
if TYPE_CHECKING:
28+
from .config import Mode
2429
_NO_VALUE = object()
25-
26-
27-
def _resolve_filepath(file=None, filepath=None):
30+
T = TypeVar("T")
31+
K = TypeVar("K")
32+
V = TypeVar("V")
33+
P = ParamSpec("P")
34+
35+
Level = Literal["call", "state", "full"]
36+
Kind = Literal["change", "message", "set", "call", "return"]
37+
def _resolve_filepath(file: str |None =None, filepath: str | None=None) -> str | None:
2838
if file is not None and filepath is not None:
2939
raise TypeError("Use only one of 'file' or 'filepath'")
3040
return file if file is not None else filepath
@@ -35,13 +45,13 @@ def _resolve_filepath(file=None, filepath=None):
3545
# ===============
3646

3747
def _log_class(
38-
cls,
48+
cls: type,
3949
*,
40-
filepath=None,
41-
show_time=True,
42-
show_file=True,
43-
show_lineno=True
44-
):
50+
filepath: str | None=None,
51+
show_time: bool=True,
52+
show_file: bool=True,
53+
show_lineno: bool=True
54+
) -> type:
4555
"""
4656
Wrap a class so its instances become LoggedObjects
4757
@@ -57,7 +67,7 @@ def _log_class(
5767

5868
class LoggedClass(cls):
5969
@functools.wraps(original_init)
60-
def __init__(self, *args, **kwargs):
70+
def __init__(self, *args: object, **kwargs: object):
6171
if not config._ENABLED:
6272
original_init(self, *args, **kwargs)
6373
return
@@ -79,7 +89,7 @@ def __init__(self, *args, **kwargs):
7989

8090
original_init(self, *args, **kwargs)
8191

82-
def __setattr__(self, name, value):
92+
def __setattr__(self, name: str, value: object) -> None:
8393
if name.startswith("_"):
8494
object.__setattr__(self, name, value)
8595
return
@@ -129,7 +139,7 @@ def __setattr__(self, name, value):
129139
# WATCH (value logging)
130140
# =====================
131141

132-
def watch(value, name=None, *, show_time=True, show_file=True, show_lineno=True):
142+
def watch(value: T, name: str | None=None, *, show_time: bool=True, show_file: bool=True, show_lineno: bool=True) -> T:
133143
"""
134144
Log without changing behaviour
135145
"""
@@ -201,16 +211,16 @@ def _shorten_name(name: str) -> str:
201211

202212

203213
def _log_function(
204-
func,
214+
func: Callable[P, T],
205215
*,
206-
filepath=None,
207-
level="full",
208-
filter_set=None,
209-
mode="full",
210-
show_time=True,
211-
show_file=True,
212-
show_lineno=True
213-
):
216+
filepath: str | None=None,
217+
level: Level="full",
218+
filter_set: set[str] | None=None,
219+
mode: Mode="full",
220+
show_time: bool=True,
221+
show_file: bool=True,
222+
show_lineno: bool=True
223+
) -> Callable[P, T]:
214224
"""
215225
Wrap a function to trace:
216226
- calls (arguments)
@@ -246,7 +256,7 @@ def wrapper(*args, **kwargs):
246256
call_id = call_counter
247257
call_name = f"{func_path}{'' if call_counter == 1 else f'#{call_id}'}"
248258

249-
def _should_emit(kind, name):
259+
def _should_emit(kind: Kind, name: str) -> bool:
250260
if mode == "educational":
251261
var = name.split(".")[-1]
252262

@@ -309,7 +319,7 @@ def _should_emit(kind, name):
309319
last_values = {}
310320

311321
try:
312-
def tracer(frame, event, arg):
322+
def tracer(frame: FrameType, event: str, arg: object):
313323
code = frame.f_code
314324

315325
if not (
@@ -436,13 +446,13 @@ def tracer(frame, event, arg):
436446
# ========================
437447

438448
def _log_object(
439-
obj,
440-
name=None,
449+
obj: T | Mapping[K, V],
450+
name: str | None=None,
441451
*,
442-
show_time=True,
443-
show_file=True,
444-
show_lineno=True
445-
):
452+
show_time: bool=True,
453+
show_file: bool=True,
454+
show_lineno: bool=True
455+
) -> T | LoggedObject[T | Mapping[K, V]] | Mapping[K, V]:
446456
if not config._ENABLED or config._DECORATORS_ONLY:
447457
return obj
448458

@@ -482,13 +492,13 @@ def _log_object(
482492

483493

484494
def _log_message(
485-
text,
486-
*args,
487-
show_time=True,
488-
show_file=True,
489-
show_lineno=True,
490-
**kwargs
491-
):
495+
text: str,
496+
*args: object,
497+
show_time: bool=True,
498+
show_file: bool=True,
499+
show_lineno: bool=True,
500+
**kwargs: object
501+
) -> str:
492502
frame = _caller_frame()
493503

494504
try:
@@ -536,19 +546,47 @@ def _log_message(
536546
# PUBLIC ENTRYPOINT
537547
# =====================
538548

549+
@overload
550+
def log(
551+
obj: Mapping[K, V],
552+
*args: object,
553+
file: str | None=...,
554+
filepath: str | None=...,
555+
level: Level=...,
556+
filter: Iterable[str] | None=...,
557+
mode: Mode | None=...,
558+
show_time: bool | None=...,
559+
show_file: bool | None=...,
560+
show_lineno: bool | None=...,
561+
**kwargs: object
562+
) -> Mapping[K, V]: ...
563+
@overload
564+
def log(
565+
obj: Callable[P, T],
566+
*args: object,
567+
file: str | None=...,
568+
filepath: str | None=...,
569+
level: Level=...,
570+
filter: Iterable[str] | None=...,
571+
mode: Mode | None=...,
572+
show_time: bool | None=...,
573+
show_file: bool | None=...,
574+
show_lineno: bool | None=...,
575+
**kwargs: object
576+
) -> Callable[P, T]: ...
539577
def log(
540-
obj=_NO_VALUE,
541-
*args,
542-
file=None,
543-
filepath=None,
544-
level="full",
545-
filter=None,
546-
mode=None,
547-
show_time=None,
548-
show_file=None,
549-
show_lineno=None,
550-
**kwargs
551-
):
578+
obj: Callable[P, T] | Mapping[K, V] | object=_NO_VALUE,
579+
*args: object,
580+
file: str | None=None,
581+
filepath: str | None=None,
582+
level: Level="full",
583+
filter: Iterable[str] | None=None,
584+
mode: Mode | None=None,
585+
show_time: bool | None=None,
586+
show_file: bool | None=None,
587+
show_lineno: bool | None=None,
588+
**kwargs: object
589+
) -> object:
552590
"""
553591
Dispatches behaviour based on input type:
554592
@@ -577,7 +615,7 @@ def log(
577615
show_lineno = config._SHOW_LINENO
578616

579617
if obj is _NO_VALUE:
580-
def decorator(target):
618+
def decorator(target: T):
581619
if inspect.isclass(target):
582620
return _log_class(
583621
target,

logeye/emmiter.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
from __future__ import annotations
12
import os
23
import time
4+
from typing import TYPE_CHECKING
35

46
from . import config
57
from .formatting import _formatter
68
from .introspection import _is_user_code
9+
if TYPE_CHECKING:
10+
from .core import Kind
711

8-
9-
def _write_line_to_file(filepath, line):
12+
def _write_line_to_file(filepath: str, line: str) -> None:
1013
directory = os.path.dirname(filepath)
1114
if directory:
1215
os.makedirs(directory, exist_ok=True)
@@ -15,7 +18,7 @@ def _write_line_to_file(filepath, line):
1518
f.write(line + "\n")
1619

1720

18-
def _emit(kind, name, value, *, filename=None, lineno=None, filepath=None, show_time=True, show_file=True, show_lineno=True):
21+
def _emit(kind: Kind, name: str, value: object, *, filename: str | None=None, lineno: int | None=None, filepath: str | None=None, show_time: bool=True, show_file: bool=True, show_lineno: bool=True) -> None:
1922
if config._START_TIME is None:
2023
config._START_TIME = time.perf_counter()
2124

0 commit comments

Comments
 (0)