Skip to content

Commit 17e0f31

Browse files
max_iter parameter (#80)
* max_iter parameter * lil cleanup * Agree with @penguinolog suggestions * Update logwrap/repr_utils.py fix static check * Apply suggestions from code review Co-authored-by: Alexey Stepanov <[email protected]> --------- Co-authored-by: Alexey Stepanov <[email protected]>
1 parent 25d129e commit 17e0f31

File tree

6 files changed

+121
-17
lines changed

6 files changed

+121
-17
lines changed

README.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ Argumented usage with arguments from signature:
7070
log=None, # if not set: try to find LOGGER, LOG, logger or log object in target module and use it if it logger instance. Fallback: logger named logwrap
7171
log_level=logging.DEBUG,
7272
exc_level=logging.ERROR,
73-
max_indent=20, # forwarded to the pretty_repr
73+
max_indent=20, # forwarded to the pretty_repr,
74+
max_iter=0, # forwarded to the pretty_repr, max number of items in the Iterable before ellipsis. Unlimited if 0.
7475
blacklisted_names=None, # list argument names, which should be dropped from log
7576
blacklisted_exceptions=None, # Exceptions to skip details in log (no traceback, no exception details - just class name)
7677
log_call_args=True, # Log call arguments before call

doc/source/logwrap.rst

+9-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ API: Decorators: `LogWrap` class and `logwrap` function.
1212

1313
.. versionadded:: 2.2.0
1414

15-
.. py:method:: __init__(*, log=None, log_level=logging.DEBUG, exc_level=logging.ERROR, max_indent=20, blacklisted_names=None, blacklisted_exceptions=None, log_call_args=True, log_call_args_on_exc=True, log_traceback=True, log_result_obj=True, )
15+
.. py:method:: __init__(*, log=None, log_level=logging.DEBUG, exc_level=logging.ERROR, max_indent=20, max_iter=0, blacklisted_names=None, blacklisted_exceptions=None, log_call_args=True, log_call_args_on_exc=True, log_traceback=True, log_result_obj=True, )
1616
1717
:param log: logger object for decorator, by default trying to use logger from target module. Fallback: 'logwrap'
1818
:type log: logging.Logger | None
@@ -22,6 +22,8 @@ API: Decorators: `LogWrap` class and `logwrap` function.
2222
:type exc_level: int
2323
:param max_indent: maximum indent before classic `repr()` call.
2424
:type max_indent: int
25+
:param max_iter: maximum number of elements to log from iterable
26+
:type max_iter: int
2527
:param blacklisted_names: Blacklisted argument names.
2628
Arguments with this names will be skipped in log.
2729
:type blacklisted_names: Iterable[str] | None
@@ -44,6 +46,7 @@ API: Decorators: `LogWrap` class and `logwrap` function.
4446
.. versionchanged:: 5.1.0 log_traceback parameter
4547
.. versionchanged:: 8.0.0 pick up logger from target module if possible
4648
.. versionchanged:: 9.0.0 Only LogWrap instance act as decorator
49+
.. versionchanged:: 12.0.0 max_iter parameter
4750

4851
.. py:method:: pre_process_param(self, arg)
4952
@@ -79,6 +82,7 @@ API: Decorators: `LogWrap` class and `logwrap` function.
7982
.. py:attribute:: log_level
8083
.. py:attribute:: exc_level
8184
.. py:attribute:: max_indent
85+
.. py:attribute:: max_iter
8286
.. py:attribute:: blacklisted_names
8387
8488
``list[str]``, modified via mutability
@@ -98,7 +102,7 @@ API: Decorators: `LogWrap` class and `logwrap` function.
98102
:rtype: Callable | Awaitable
99103

100104

101-
.. py:function:: logwrap(func=None, *, log=None, log_level=logging.DEBUG, exc_level=logging.ERROR, max_indent=20, blacklisted_names=None, blacklisted_exceptions=None, log_call_args=True, log_call_args_on_exc=True, log_traceback=True, log_result_obj=True, )
105+
.. py:function:: logwrap(func=None, *, log=None, log_level=logging.DEBUG, exc_level=logging.ERROR, max_indent=20, max_iter=0, blacklisted_names=None, blacklisted_exceptions=None, log_call_args=True, log_call_args_on_exc=True, log_traceback=True, log_result_obj=True, )
102106
103107
Log function calls and return values.
104108

@@ -112,6 +116,8 @@ API: Decorators: `LogWrap` class and `logwrap` function.
112116
:type exc_level: int
113117
:param max_indent: maximum indent before classic `repr()` call.
114118
:type max_indent: int
119+
:param max_iter: maximum number of elements to log from iterable
120+
:type max_iter: int
115121
:param blacklisted_names: Blacklisted argument names. Arguments with this names will be skipped in log.
116122
:type blacklisted_names: Iterable[str] | None
117123
:param blacklisted_exceptions: list of exceptions, which should be re-raised
@@ -134,6 +140,7 @@ API: Decorators: `LogWrap` class and `logwrap` function.
134140
.. versionchanged:: 5.1.0 log_traceback parameter
135141
.. versionchanged:: 8.0.0 pick up logger from target module if possible
136142
.. versionchanged:: 9.0.0 Only LogWrap instance act as decorator
143+
.. versionchanged:: 12.0.0 max_iter parameter
137144

138145

139146
.. py:class:: BoundParameter(inspect.Parameter)

logwrap/log_on_access.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ def __init__(
172172
log_traceback: bool = True,
173173
override_name: str | None = None,
174174
max_indent: int = 20,
175+
max_iter: int = 0,
175176
) -> None:
176177
"""Advanced property main entry point.
177178
@@ -203,6 +204,8 @@ def __init__(
203204
:type override_name: str | None
204205
:param max_indent: maximal indent before classic repr() call
205206
:type max_indent: int
207+
:param max_iter: maximal number of items to display in iterables
208+
:type max_iter: int
206209
"""
207210
super().__init__(fget=fget, fset=fset, fdel=fdel, doc=doc)
208211

@@ -220,6 +223,7 @@ def __init__(
220223
self.__log_traceback: bool = log_traceback
221224
self.__override_name: str | None = override_name
222225
self.__max_indent: int = max_indent
226+
self.__max_iter: int = max_iter
223227
self.__name: str = ""
224228
self.__owner: type[_OwnerT] | None = None
225229

@@ -270,7 +274,7 @@ def __get_obj_source(self, instance: _OwnerT, owner: type[_OwnerT] | None = None
270274
:rtype: str
271275
"""
272276
if self.log_object_repr:
273-
return repr_utils.pretty_repr(instance, max_indent=self.max_indent)
277+
return repr_utils.pretty_repr(instance, max_indent=self.max_indent, max_iter=self.max_iter)
274278
if owner is not None:
275279
return f"<{owner.__name__}() at 0x{id(instance):X}>"
276280
if self.__objclass__ is not None:
@@ -623,6 +627,24 @@ def max_indent(self, value: int) -> None:
623627
"""
624628
self.__max_indent = value
625629

630+
@property
631+
def max_iter(self) -> int:
632+
"""Max number of items in iterables during repr.
633+
634+
:return: maximum iter before classic `repr()` call.
635+
:rtype: int
636+
"""
637+
return self.__max_iter
638+
639+
@max_iter.setter
640+
def max_iter(self, value: int) -> None:
641+
"""Max number of items in iterables during repr.
642+
643+
:param value: maximum iter before classic `repr()` call.
644+
:type value: int
645+
"""
646+
self.__max_iter = value
647+
626648
@property
627649
def __name__(self) -> str: # noqa: A003,PLW3201,RUF100
628650
"""Name getter.

logwrap/log_wrap.py

+40-1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class LogWrap:
186186
"__log_traceback",
187187
"__logger",
188188
"__max_indent",
189+
"__max_iter",
189190
)
190191

191192
def __init__(
@@ -194,6 +195,7 @@ def __init__(
194195
log_level: int = DEBUG,
195196
exc_level: int = ERROR,
196197
max_indent: int = 20,
198+
max_iter: int = 0,
197199
blacklisted_names: Iterable[str] | None = None,
198200
blacklisted_exceptions: Iterable[type[Exception]] | None = None,
199201
log_call_args: bool = True,
@@ -211,6 +213,8 @@ def __init__(
211213
:type exc_level: int
212214
:param max_indent: maximum indent before classic `repr()` call.
213215
:type max_indent: int
216+
:param max_iter: maximum number of elements in iterable before ellipsis.
217+
:type max_iter: int
214218
:param blacklisted_names: Blacklisted argument names. Arguments with this names will be skipped in log.
215219
:type blacklisted_names: Iterable[str] | None
216220
:param blacklisted_exceptions: list of exception, which should be re-raised
@@ -229,6 +233,7 @@ def __init__(
229233
.. versionchanged:: 5.1.0 log_traceback parameter
230234
.. versionchanged:: 8.0.0 pick up logger from target module if possible
231235
.. versionchanged:: 9.0.0 Only LogWrap instance act as decorator
236+
.. versionchanged:: 12.0.0 max_iter parameter
232237
"""
233238
# Typing fix:
234239
if blacklisted_names is None:
@@ -248,6 +253,7 @@ def __init__(
248253
self.__log_level: int = log_level
249254
self.__exc_level: int = exc_level
250255
self.__max_indent: int = max_indent
256+
self.__max_iter: int = max_iter
251257
self.__log_call_args: bool = log_call_args
252258
self.__log_call_args_on_exc: bool = log_call_args_on_exc
253259
self.__log_traceback: bool = log_traceback
@@ -336,6 +342,27 @@ def max_indent(self, val: int) -> None:
336342
raise TypeError(f"Unexpected type: {val.__class__.__name__}. Should be {int.__name__}.")
337343
self.__max_indent = val
338344

345+
@property
346+
def max_iter(self) -> int:
347+
"""Maximum number of elements in iterable before ellipsis.
348+
349+
:return: maximum number of elements in iterable before ellipsis
350+
:rtype: int
351+
"""
352+
return self.__max_iter
353+
354+
@max_iter.setter
355+
def max_iter(self, val: int) -> None:
356+
"""Maximum number of elements in iterable before ellipsis.
357+
358+
:param val: maximum number of elements in iterable before ellipsis
359+
:type val: int
360+
:raises TypeError: max_iter is not integer
361+
"""
362+
if not isinstance(val, int):
363+
raise TypeError(f"Unexpected type: {val.__class__.__name__}. Should be {int.__name__}.")
364+
self.__max_iter = val
365+
339366
@property
340367
def blacklisted_names(self) -> list[str]:
341368
"""List of arguments names to ignore in log.
@@ -459,6 +486,7 @@ def __repr__(self) -> str:
459486
f"log_level={self.log_level}, "
460487
f"exc_level={self.exc_level}, "
461488
f"max_indent={self.max_indent}, "
489+
f"max_iter={self.max_iter}, "
462490
f"blacklisted_names={self.blacklisted_names}, "
463491
f"blacklisted_exceptions={self.blacklisted_exceptions}, "
464492
f"log_call_args={self.log_call_args}, "
@@ -519,6 +547,7 @@ def _safe_val_repr(self, value: Any) -> str:
519547
indent=INDENT,
520548
no_indent_start=True,
521549
max_indent=self.max_indent,
550+
max_iter=self.max_iter,
522551
)
523552
except Exception as exc:
524553
base_name: str = getattr(value, "name", getattr(value, "__name__", value.__class__.__name__))
@@ -610,7 +639,9 @@ def _make_done_record(
610639
msg: str = f"Done: {func_name!r}"
611640

612641
if self.log_result_obj:
613-
msg += f" with result:\n{repr_utils.pretty_repr(result, max_indent=self.max_indent)}"
642+
msg += (
643+
f" with result:\n{repr_utils.pretty_repr(result, max_indent=self.max_indent, max_iter=self.max_iter)}"
644+
)
614645
logger.log(level=self.log_level, msg=msg)
615646

616647
def _make_calling_record(
@@ -743,6 +774,7 @@ def logwrap(
743774
log_level: int = DEBUG,
744775
exc_level: int = ERROR,
745776
max_indent: int = 20,
777+
max_iter: int = 20,
746778
blacklisted_names: Iterable[str] | None = None,
747779
blacklisted_exceptions: Iterable[type[Exception]] | None = None,
748780
log_call_args: bool = True,
@@ -762,6 +794,7 @@ def logwrap(
762794
log_level: int = DEBUG,
763795
exc_level: int = ERROR,
764796
max_indent: int = 20,
797+
max_iter: int = 20,
765798
blacklisted_names: Iterable[str] | None = None,
766799
blacklisted_exceptions: Iterable[type[Exception]] | None = None,
767800
log_call_args: bool = True,
@@ -781,6 +814,7 @@ def logwrap(
781814
log_level: int = DEBUG,
782815
exc_level: int = ERROR,
783816
max_indent: int = 20,
817+
max_iter: int = 0,
784818
blacklisted_names: Iterable[str] | None = None,
785819
blacklisted_exceptions: Iterable[type[Exception]] | None = None,
786820
log_call_args: bool = True,
@@ -799,6 +833,7 @@ def logwrap(
799833
log_level: int = DEBUG,
800834
exc_level: int = ERROR,
801835
max_indent: int = 20,
836+
max_iter: int = 0,
802837
blacklisted_names: Iterable[str] | None = None,
803838
blacklisted_exceptions: Iterable[type[Exception]] | None = None,
804839
log_call_args: bool = True,
@@ -818,6 +853,8 @@ def logwrap(
818853
:type exc_level: int
819854
:param max_indent: maximum indent before classic `repr()` call.
820855
:type max_indent: int
856+
:param max_iter: maximum number of elements to log from iterables.
857+
:type max_iter: int
821858
:param blacklisted_names: Blacklisted argument names. Arguments with this names will be skipped in log.
822859
:type blacklisted_names: Iterable[str] | None
823860
:param blacklisted_exceptions: list of exceptions, which should be re-raised
@@ -840,12 +877,14 @@ def logwrap(
840877
.. versionchanged:: 5.1.0 log_traceback parameter
841878
.. versionchanged:: 8.0.0 pick up logger from target module if possible
842879
.. versionchanged:: 9.0.0 Only LogWrap instance act as decorator
880+
.. versionchanged:: 12.0.0 max_iter parameter
843881
"""
844882
wrapper = LogWrap(
845883
log=log,
846884
log_level=log_level,
847885
exc_level=exc_level,
848886
max_indent=max_indent,
887+
max_iter=max_iter,
849888
blacklisted_names=blacklisted_names,
850889
blacklisted_exceptions=blacklisted_exceptions,
851890
log_call_args=log_call_args,

logwrap/repr_utils.py

+29-12
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,9 @@ class PrettyFormat(abc.ABC):
248248
Designed for usage as __repr__ and __str__ replacement on complex objects
249249
"""
250250

251-
__slots__ = ("__indent_step", "__max_indent")
251+
__slots__ = ("__indent_step", "__max_indent", "__max_iter")
252252

253-
def __init__(self, max_indent: int = 20, indent_step: int = 4) -> None:
253+
def __init__(self, max_indent: int = 20, max_iter: int = 0, indent_step: int = 4) -> None:
254254
"""Pretty Formatter.
255255
256256
:param max_indent: maximal indent before classic repr() call
@@ -259,6 +259,7 @@ def __init__(self, max_indent: int = 20, indent_step: int = 4) -> None:
259259
:type indent_step: int
260260
"""
261261
self.__max_indent: int = max_indent
262+
self.__max_iter: int = max_iter
262263
self.__indent_step: int = indent_step
263264

264265
@property
@@ -270,6 +271,15 @@ def max_indent(self) -> int:
270271
"""
271272
return self.__max_indent
272273

274+
@property
275+
def max_iter(self) -> int:
276+
"""Max iterable items getter.
277+
278+
:return: maximal items count for iterable objects
279+
:rtype: int
280+
"""
281+
return self.__max_iter
282+
273283
@property
274284
def indent_step(self) -> int:
275285
"""Indent step getter.
@@ -556,14 +566,15 @@ def _repr_iterable_items(
556566
"""
557567
next_indent: int = self.next_indent(indent)
558568
buf: list[str] = []
559-
for elem in src:
560-
buf.extend(
561-
(
562-
"\n",
563-
self.process_element(src=elem, indent=next_indent),
564-
",",
565-
)
566-
)
569+
570+
for idx, elem in enumerate(src, start=1):
571+
buf.extend(("\n", self.process_element(src=elem, indent=next_indent)))
572+
573+
if idx == self.max_iter:
574+
buf.append("...")
575+
break
576+
577+
buf.append(",")
567578
return "".join(buf)
568579

569580
def _repr_rich(
@@ -947,6 +958,7 @@ def pretty_repr(
947958
indent: int = 0,
948959
no_indent_start: bool = False,
949960
max_indent: int = 20,
961+
max_iter: int = 0,
950962
indent_step: int = 4,
951963
) -> str:
952964
"""Make human readable repr of object.
@@ -959,12 +971,14 @@ def pretty_repr(
959971
:type no_indent_start: bool
960972
:param max_indent: maximal indent before classic repr() call
961973
:type max_indent: int
974+
:param max_iter: maximal number of items to iterate
975+
:type max_iter: int
962976
:param indent_step: step for the next indentation level
963977
:type indent_step: int
964978
:return: formatted string
965979
:rtype: str
966980
"""
967-
return PrettyRepr(max_indent=max_indent, indent_step=indent_step)(
981+
return PrettyRepr(max_indent=max_indent, max_iter=max_iter, indent_step=indent_step)(
968982
src=src,
969983
indent=indent,
970984
no_indent_start=no_indent_start,
@@ -976,6 +990,7 @@ def pretty_str(
976990
indent: int = 0,
977991
no_indent_start: bool = False,
978992
max_indent: int = 20,
993+
max_iter: int = 20,
979994
indent_step: int = 4,
980995
) -> str:
981996
"""Make human readable str of object.
@@ -988,11 +1003,13 @@ def pretty_str(
9881003
:type no_indent_start: bool
9891004
:param max_indent: maximal indent before classic repr() call
9901005
:type max_indent: int
1006+
:param max_iter: maximal number of items to log in iterables
1007+
:type max_iter: int
9911008
:param indent_step: step for the next indentation level
9921009
:type indent_step: int
9931010
:return: formatted string
9941011
"""
995-
return PrettyStr(max_indent=max_indent, indent_step=indent_step)(
1012+
return PrettyStr(max_indent=max_indent, max_iter=max_iter, indent_step=indent_step)(
9961013
src=src,
9971014
indent=indent,
9981015
no_indent_start=no_indent_start,

0 commit comments

Comments
 (0)