1# This file is part of Hypothesis, which may be found at
2# https://github.com/HypothesisWorks/hypothesis/
3#
4# Copyright the Hypothesis Authors.
5# Individual contributors are listed in AUTHORS.rst and the git log.
6#
7# This Source Code Form is subject to the terms of the Mozilla Public License,
8# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9# obtain one at https://mozilla.org/MPL/2.0/.
10
11"""
12Python advanced pretty printer. This pretty printer is intended to
13replace the old `pprint` python module which does not allow developers
14to provide their own pretty print callbacks.
15This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`.
16Example Usage
17-------------
18To get a string of the output use `pretty`::
19 from pretty import pretty
20 string = pretty(complex_object)
21Extending
22---------
23The pretty library allows developers to add pretty printing rules for their
24own objects. This process is straightforward. All you have to do is to
25add a `_repr_pretty_` method to your object and call the methods on the
26pretty printer passed::
27 class MyObject(object):
28 def _repr_pretty_(self, p, cycle):
29 ...
30Here is an example implementation of a `_repr_pretty_` method for a list
31subclass::
32 class MyList(list):
33 def _repr_pretty_(self, p, cycle):
34 if cycle:
35 p.text('MyList(...)')
36 else:
37 with p.group(8, 'MyList([', '])'):
38 for idx, item in enumerate(self):
39 if idx:
40 p.text(',')
41 p.breakable()
42 p.pretty(item)
43The `cycle` parameter is `True` if pretty detected a cycle. You *have* to
44react to that or the result is an infinite loop. `p.text()` just adds
45non breaking text to the output, `p.breakable()` either adds a whitespace
46or breaks here. If you pass it an argument it's used instead of the
47default space. `p.pretty` prettyprints another object using the pretty print
48method.
49The first parameter to the `group` function specifies the extra indentation
50of the next line. In this example the next item will either be on the same
51line (if the items are short enough) or aligned with the right edge of the
52opening bracket of `MyList`.
53If you just want to indent something you can use the group function
54without open / close parameters. You can also use this code::
55 with p.indent(2):
56 ...
57Inheritance diagram:
58.. inheritance-diagram:: IPython.lib.pretty
59 :parts: 3
60:copyright: 2007 by Armin Ronacher.
61 Portions (c) 2009 by Robert Kern.
62:license: BSD License.
63"""
64
65import ast
66import datetime
67import re
68import struct
69import sys
70import types
71import warnings
72from collections import Counter, OrderedDict, defaultdict, deque
73from collections.abc import Callable, Collection, Generator, Iterable, Sequence
74from contextlib import contextmanager, suppress
75from enum import Enum, Flag
76from functools import partial
77from io import StringIO, TextIOBase
78from math import copysign, isnan
79from typing import TYPE_CHECKING, Any, Optional, TypeAlias, TypeVar
80
81if TYPE_CHECKING:
82 from hypothesis.control import BuildContext
83
84T = TypeVar("T")
85PrettyPrintFunction: TypeAlias = Callable[[Any, "RepresentationPrinter", bool], None]
86# Maps each argument label ("arg[i]" or a keyword) to the index of the span
87# created by drawing that argument.
88ArgLabelsT: TypeAlias = dict[str, int]
89
90__all__ = [
91 "IDKey",
92 "RepresentationPrinter",
93 "_fixeddict_pprinter",
94 "_tuple_pprinter",
95 "pretty",
96]
97
98
99def _safe_getattr(obj: object, attr: str, default: Any | None = None) -> Any:
100 """Safe version of getattr.
101
102 Same as getattr, but will return ``default`` on any Exception,
103 rather than raising.
104
105 """
106 try:
107 return getattr(obj, attr, default)
108 except Exception:
109 return default
110
111
112def pretty(obj: object, *, cycle: bool = False) -> str:
113 """Pretty print the object's representation."""
114 printer = RepresentationPrinter()
115 printer.pretty(obj, cycle=cycle)
116 return printer.getvalue()
117
118
119class IDKey:
120 def __init__(self, value: object):
121 self.value = value
122
123 def __hash__(self) -> int:
124 return hash((type(self), id(self.value)))
125
126 def __eq__(self, __o: object) -> bool:
127 return isinstance(__o, type(self)) and id(self.value) == id(__o.value)
128
129
130def _try_inline_lambda(
131 func_name: str,
132 args: Sequence[object],
133 kwargs: dict[str, object],
134 printer: "RepresentationPrinter",
135) -> bool:
136 """Try to inline single-use lambda arguments into the body expression.
137
138 Given e.g. func_name="lambda b: hashlib.sha256(b).hexdigest()" with
139 args=(b'',), returns the printer output for "hashlib.sha256(b'').hexdigest()"
140 by substituting the argument repr into the AST.
141
142 Returns True if inlining succeeded (the printer has been written to),
143 False if inlining is not possible (parse failure, multi-use params, etc).
144 """
145 try:
146 tree = ast.parse(func_name, mode="eval")
147 except Exception:
148 return False
149 lam = tree.body
150 if not isinstance(lam, ast.Lambda):
151 return False
152
153 # Build param name -> argument repr mapping, matching Python call semantics
154 params = lam.args
155 if params.vararg or params.kwonlyargs or params.kw_defaults or params.kwarg:
156 return False
157
158 param_names = [p.arg for p in params.args]
159 # params.defaults are right-aligned: if there are 3 params and 1 default,
160 # params.defaults applies to the last param only.
161 n_defaults = len(params.defaults)
162 has_default = (
163 set(param_names[len(param_names) - n_defaults :]) if n_defaults else set()
164 )
165
166 # Bail if there are more positional args than parameters, or if any
167 # kwarg doesn't match a parameter name — these can't be inlined.
168 if len(args) > len(param_names):
169 return False
170 if any(k not in param_names for k in kwargs):
171 return False
172
173 arg_reprs: dict[str, str] = {}
174 for i, name in enumerate(param_names):
175 if i < len(args):
176 arg_reprs[name] = pretty(args[i])
177 elif name in kwargs:
178 arg_reprs[name] = pretty(kwargs[name])
179 elif name in has_default:
180 pass # not passed, will use its default — just skip
181 else:
182 return False
183
184 # Bail if any repr is not valid Python (e.g. "HypothesisRandom(generated data)")
185 for repr_str in arg_reprs.values():
186 try:
187 ast.parse(repr_str, mode="eval")
188 except Exception:
189 return False
190
191 use_counts = dict.fromkeys(param_names, 0)
192 for node in ast.walk(lam.body):
193 if isinstance(node, ast.Name) and node.id in use_counts:
194 use_counts[node.id] += 1
195
196 # Bail if any parameter is used more than once (avoid duplicating expressions)
197 if any(count > 1 for count in use_counts.values()):
198 return False
199
200 # Substitute argument reprs into the body AST
201 class _Inliner(ast.NodeTransformer):
202 def visit_Name(self, node: ast.Name) -> ast.AST:
203 if node.id in arg_reprs:
204 # Parse the repr as an expression and splice it in.
205 # Wrap in parens to preserve precedence in all contexts.
206 replacement = ast.parse(arg_reprs[node.id], mode="eval").body
207 return ast.copy_location(replacement, node)
208 return node
209
210 new_body = _Inliner().visit(lam.body)
211 ast.fix_missing_locations(new_body)
212
213 try:
214 result = ast.unparse(new_body)
215 except Exception:
216 return False
217
218 printer.text(result)
219 return True
220
221
222class RepresentationPrinter:
223 """Special pretty printer that has a `pretty` method that calls the pretty
224 printer for a python object.
225
226 This class stores processing data on `self` so you must *never* use
227 this class in a threaded environment. Always lock it or
228 reinstantiate it.
229
230 """
231
232 def __init__(
233 self,
234 output: TextIOBase | None = None,
235 *,
236 context: Optional["BuildContext"] = None,
237 ) -> None:
238 """Optionally pass the output stream and the current build context.
239
240 We use the context to represent objects constructed by strategies by showing
241 *how* they were constructed, and add annotations showing which parts of the
242 minimal failing test case can vary without changing the test result.
243 """
244 self.broken: bool = False
245 self.output: TextIOBase = StringIO() if output is None else output
246 self.max_width: int = 79
247 self.max_seq_length: int = 1000
248 self.output_width: int = 0
249 self.buffer_width: int = 0
250 self.buffer: deque[Breakable | Text] = deque()
251
252 root_group = Group(0)
253 self.group_stack = [root_group]
254 self.group_queue = GroupQueue(root_group)
255 self.indentation: int = 0
256
257 self.stack: list[int] = []
258 self.singleton_pprinters: dict[int, PrettyPrintFunction] = {}
259 self.type_pprinters: dict[type, PrettyPrintFunction] = {}
260 self.deferred_pprinters: dict[tuple[str, str], PrettyPrintFunction] = {}
261 # If IPython has been imported, load up their pretty-printer registry
262 if "IPython.lib.pretty" in sys.modules:
263 ipp = sys.modules["IPython.lib.pretty"]
264 self.singleton_pprinters.update(ipp._singleton_pprinters)
265 self.type_pprinters.update(ipp._type_pprinters)
266 self.deferred_pprinters.update(ipp._deferred_type_pprinters)
267 # If there's overlap between our pprinters and IPython's, we'll use ours.
268 self.singleton_pprinters.update(_singleton_pprinters)
269 self.type_pprinters.update(_type_pprinters)
270 self.deferred_pprinters.update(_deferred_type_pprinters)
271
272 # for which-parts-matter, we track comments keyed by the index of the
273 # span each argument's draw created in the minimal failing test case;
274 # this is per-interesting_origin but we report each separately so
275 # that's someone else's problem here. Invocations of self.repr_call()
276 # can report the span for each argument, which will then be used to
277 # look up the relevant comment if any. (The None key holds the
278 # whole-test comment, which is looked up directly, not per-argument.)
279 self.known_object_printers: dict[IDKey, list[PrettyPrintFunction]]
280 self.span_comments: dict[int | None, str]
281 if context is None:
282 self.known_object_printers = defaultdict(list)
283 self.span_comments = {}
284 else:
285 self.known_object_printers = context.known_object_printers
286 self.span_comments = context.data.span_comments
287 assert all(isinstance(k, IDKey) for k in self.known_object_printers)
288 # Track which spans we've already printed comments for, to avoid
289 # duplicating comments when nested objects share the same span.
290 self._commented_spans: set[int] = set()
291
292 def pretty(self, obj: object, *, cycle: bool = False) -> None:
293 """Pretty print the given object."""
294
295 obj_id = id(obj)
296 cycle = cycle or obj_id in self.stack
297 self.stack.append(obj_id)
298 try:
299 with self.group():
300 obj_class = _safe_getattr(obj, "__class__", None) or type(obj)
301 # First try to find registered singleton printers for the type.
302 try:
303 printer = self.singleton_pprinters[obj_id]
304 except (TypeError, KeyError):
305 pass
306 else:
307 return printer(obj, self, cycle)
308
309 # Look for the _repr_pretty_ method which allows users
310 # to define custom pretty printing.
311 # Some objects automatically create any requested
312 # attribute. Try to ignore most of them by checking for
313 # callability.
314 pretty_method = _safe_getattr(obj, "_repr_pretty_", None)
315 if callable(pretty_method):
316 return pretty_method(self, cycle)
317
318 # Check for object-specific printers which show how this
319 # object was constructed (a Hypothesis special feature).
320 # This must come before type_pprinters so that sub-argument
321 # comments are shown for tuples/dicts/etc.
322 printers = self.known_object_printers[IDKey(obj)]
323 if len(printers) == 1:
324 return printers[0](obj, self, cycle)
325 if printers:
326 # Multiple registered functions for the same object (due to
327 # caching, small ints, etc). Use the first if all produce
328 # the same string; otherwise pretend none were registered.
329 strs = set()
330 for f in printers:
331 p = RepresentationPrinter()
332 f(obj, p, cycle)
333 strs.add(p.getvalue())
334 if len(strs) == 1:
335 return printers[0](obj, self, cycle)
336
337 # Next walk the mro and check for either:
338 # 1) a registered printer
339 # 2) a _repr_pretty_ method
340 for cls in obj_class.__mro__:
341 if cls in self.type_pprinters:
342 # printer registered in self.type_pprinters
343 return self.type_pprinters[cls](obj, self, cycle)
344 else:
345 # Check if the given class is specified in the deferred type
346 # registry; move it to the regular type registry if so.
347 key = (
348 _safe_getattr(cls, "__module__", None),
349 _safe_getattr(cls, "__name__", None),
350 )
351 if key in self.deferred_pprinters:
352 # Move the printer over to the regular registry.
353 printer = self.deferred_pprinters.pop(key)
354 self.type_pprinters[cls] = printer
355 return printer(obj, self, cycle)
356 else:
357 if hasattr(cls, "__attrs_attrs__"): # pragma: no cover
358 return pprint_fields(
359 obj,
360 self,
361 cycle,
362 [at.name for at in cls.__attrs_attrs__ if at.init],
363 )
364 if hasattr(cls, "__dataclass_fields__"):
365 return pprint_fields(
366 obj,
367 self,
368 cycle,
369 [
370 k
371 for k, v in cls.__dataclass_fields__.items()
372 if v.init
373 ],
374 )
375
376 # A user-provided repr. Find newlines and replace them with p.break_()
377 return _repr_pprint(obj, self, cycle)
378 finally:
379 self.stack.pop()
380
381 def _break_outer_groups(self) -> None:
382 while self.max_width < self.output_width + self.buffer_width:
383 group = self.group_queue.deq()
384 if not group:
385 return
386 while group.breakables:
387 x = self.buffer.popleft()
388 self.output_width = x.output(self.output, self.output_width)
389 self.buffer_width -= x.width
390 while self.buffer and isinstance(self.buffer[0], Text):
391 x = self.buffer.popleft()
392 self.output_width = x.output(self.output, self.output_width)
393 self.buffer_width -= x.width
394
395 def text(self, obj: str) -> None:
396 """Add literal text to the output."""
397 width = len(obj)
398 if self.buffer:
399 text = self.buffer[-1]
400 if not isinstance(text, Text):
401 text = Text()
402 self.buffer.append(text)
403 text.add(obj, width)
404 self.buffer_width += width
405 self._break_outer_groups()
406 else:
407 self.output.write(obj)
408 self.output_width += width
409
410 def breakable(self, sep: str = " ") -> None:
411 """Add a breakable separator to the output.
412
413 This does not mean that it will automatically break here. If no
414 breaking on this position takes place the `sep` is inserted
415 which default to one space.
416
417 """
418 width = len(sep)
419 group = self.group_stack[-1]
420 if group.want_break:
421 self.flush()
422 self.output.write("\n" + " " * self.indentation)
423 self.output_width = self.indentation
424 self.buffer_width = 0
425 else:
426 self.buffer.append(Breakable(sep, width, self))
427 self.buffer_width += width
428 self._break_outer_groups()
429
430 def break_(self) -> None:
431 """Explicitly insert a newline into the output, maintaining correct
432 indentation."""
433 self.flush()
434 self.output.write("\n" + " " * self.indentation)
435 self.output_width = self.indentation
436 self.buffer_width = 0
437
438 @contextmanager
439 def indent(self, indent: int) -> Generator[None, None, None]:
440 """`with`-statement support for indenting/dedenting."""
441 self.indentation += indent
442 try:
443 yield
444 finally:
445 self.indentation -= indent
446
447 @contextmanager
448 def group(
449 self, indent: int = 0, open: str = "", close: str = ""
450 ) -> Generator[None, None, None]:
451 """Context manager for an indented group.
452
453 with p.group(1, '{', '}'):
454
455 The first parameter specifies the indentation for the next line
456 (usually the width of the opening text), the second and third the
457 opening and closing delimiters.
458 """
459 self.begin_group(indent=indent, open=open)
460 try:
461 yield
462 finally:
463 self.end_group(dedent=indent, close=close)
464
465 def begin_group(self, indent: int = 0, open: str = "") -> None:
466 """Use the `with group(...) context manager instead.
467
468 The begin_group() and end_group() methods are for IPython compatibility only;
469 see https://github.com/HypothesisWorks/hypothesis/issues/3721 for details.
470 """
471 if open:
472 self.text(open)
473 group = Group(self.group_stack[-1].depth + 1)
474 self.group_stack.append(group)
475 self.group_queue.enq(group)
476 self.indentation += indent
477
478 def end_group(self, dedent: int = 0, close: str = "") -> None:
479 """See begin_group()."""
480 self.indentation -= dedent
481 group = self.group_stack.pop()
482 if not group.breakables:
483 self.group_queue.remove(group)
484 if close:
485 self.text(close)
486
487 def _enumerate(self, seq: Iterable[T]) -> Generator[tuple[int, T], None, None]:
488 """Like enumerate, but with an upper limit on the number of items."""
489 for idx, x in enumerate(seq):
490 if self.max_seq_length and idx >= self.max_seq_length:
491 self.text(",")
492 self.breakable()
493 self.text("...")
494 return
495 yield idx, x
496
497 def flush(self) -> None:
498 """Flush data that is left in the buffer."""
499 for data in self.buffer:
500 self.output_width += data.output(self.output, self.output_width)
501 self.buffer.clear()
502 self.buffer_width = 0
503
504 def getvalue(self) -> str:
505 assert isinstance(self.output, StringIO)
506 self.flush()
507 return self.output.getvalue()
508
509 def maybe_repr_known_object_as_call(
510 self,
511 obj: object,
512 cycle: bool,
513 name: str,
514 args: Sequence[object],
515 kwargs: dict[str, object],
516 arg_labels: ArgLabelsT | None = None,
517 ) -> None:
518 # pprint this object as a call if it seems like a good idea to do so,
519 # otherwise pprint as repr.
520 # Rules:
521 # 1. If there are comments, we *must* print as a call.
522 # 2. Prefer valid syntax to invalid syntax.
523 # 3. Prefer shorter expressions.
524 if cycle:
525 return self.text("<...>")
526 # Look up comments from span_comments if we have arg_labels
527 comments = {}
528 if arg_labels is not None:
529 for key, span_index in arg_labels.items():
530 if span_index in self.span_comments:
531 comments[key] = self.span_comments[span_index]
532 # If there are comments, we must use our call-style repr regardless of syntax
533 if not comments:
534 with suppress(Exception):
535 # Check whether the repr is valid syntax:
536 ast.parse(repr(obj))
537 # Given that the repr is valid syntax, check the call:
538 p = RepresentationPrinter()
539 p.stack = self.stack.copy()
540 p.known_object_printers = self.known_object_printers
541 p.repr_call(name, args, kwargs)
542 # If the call is not valid syntax, use the repr
543 if len(repr(obj)) < len(p.getvalue()):
544 return _repr_pprint(obj, self, cycle)
545 try:
546 ast.parse(p.getvalue())
547 except Exception:
548 return _repr_pprint(obj, self, cycle)
549 return self.repr_call(name, args, kwargs, arg_labels=arg_labels)
550
551 def repr_call(
552 self,
553 func_name: str,
554 args: Sequence[object],
555 kwargs: dict[str, object],
556 *,
557 force_split: bool | None = None,
558 arg_labels: ArgLabelsT | None = None,
559 leading_comment: str | None = None,
560 avoid_realization: bool = False,
561 known_safe_to_repr: Collection[str] = (),
562 ) -> None:
563 """Helper function to represent a function call.
564
565 - func_name, args, and kwargs should all be pretty obvious.
566 - If split_lines, we'll force one-argument-per-line; otherwise we'll place
567 calls that fit on a single line (and split otherwise).
568 - arg_labels is a mapping from pos-idx or keyword to the index of the
569 span created by drawing that argument, by which we can look up
570 comments to add.
571 """
572 assert isinstance(func_name, str)
573 if func_name.startswith(("lambda:", "lambda ")):
574 # Before wrapping the lambda in parens for a call, try to inline
575 # arguments that are used exactly once in the body. If all args
576 # get inlined, we can emit just the body expression with no call.
577 # Skip inlining only when there are actual comments on arguments,
578 # since comments need the call-style repr to attach to.
579 has_comments = arg_labels and any(
580 span_index in self.span_comments
581 and span_index not in self._commented_spans
582 for span_index in arg_labels.values()
583 )
584 if not has_comments:
585 inlined = _try_inline_lambda(func_name, args, kwargs, self)
586 if inlined:
587 return
588 func_name = f"({func_name})"
589 self.text(func_name)
590 # Build list of (label, value) pairs. Labels are "arg[i]" for positional
591 # args, or the keyword name. Skip spans already commented at a higher level.
592 all_args = [(f"arg[{i}]", v) for i, v in enumerate(args)]
593 all_args += list(kwargs.items())
594 arg_labels = arg_labels or {}
595 comments: dict[str, tuple[str, int]] = {}
596 for label, span_index in arg_labels.items():
597 if (
598 span_index in self.span_comments
599 and span_index not in self._commented_spans
600 ):
601 comments[label] = (self.span_comments[span_index], span_index)
602
603 if leading_comment or any(k in comments for k, _ in all_args):
604 # We have to split one arg per line in order to leave comments on them.
605 force_split = True
606 if force_split is None:
607 # We're OK with printing this call on a single line, but will it fit?
608 # If not, we'd rather fall back to one-argument-per-line instead.
609 p = RepresentationPrinter()
610 p.stack = self.stack.copy()
611 p.known_object_printers = self.known_object_printers
612 p.repr_call("_" * self.output_width, args, kwargs, force_split=False)
613 s = p.getvalue()
614 force_split = "\n" in s
615
616 with self.group(indent=4, open="(", close=""):
617 for i, (label, v) in enumerate(all_args):
618 if force_split:
619 if i == 0 and leading_comment:
620 self.break_()
621 self.text(leading_comment)
622 self.break_()
623 else:
624 assert leading_comment is None # only passed by top-level report
625 self.breakable(" " if i else "")
626 if not label.startswith("arg["):
627 self.text(f"{label}=")
628 # Mark span as commented BEFORE printing value, so nested printers skip it
629 entry = comments.get(label)
630 if entry:
631 self._commented_spans.add(entry[1])
632 if avoid_realization and label not in known_safe_to_repr:
633 self.text("<symbolic>")
634 else:
635 self.pretty(v)
636 if force_split or i + 1 < len(all_args):
637 self.text(",")
638 if entry:
639 self.text(f" # {entry[0]}")
640 if all_args and force_split:
641 self.break_()
642 self.text(")") # after dedent
643
644
645class Printable:
646 def output(self, stream: TextIOBase, output_width: int) -> int: # pragma: no cover
647 raise NotImplementedError
648
649
650class Text(Printable):
651 def __init__(self) -> None:
652 self.objs: list[str] = []
653 self.width: int = 0
654
655 def output(self, stream: TextIOBase, output_width: int) -> int:
656 for obj in self.objs:
657 stream.write(obj)
658 return output_width + self.width
659
660 def add(self, obj: str, width: int) -> None:
661 self.objs.append(obj)
662 self.width += width
663
664
665class Breakable(Printable):
666 def __init__(self, seq: str, width: int, pretty: RepresentationPrinter) -> None:
667 self.obj = seq
668 self.width = width
669 self.pretty = pretty
670 self.indentation = pretty.indentation
671 self.group = pretty.group_stack[-1]
672 self.group.breakables.append(self)
673
674 def output(self, stream: TextIOBase, output_width: int) -> int:
675 self.group.breakables.popleft()
676 if self.group.want_break:
677 stream.write("\n" + " " * self.indentation)
678 return self.indentation
679 if not self.group.breakables:
680 self.pretty.group_queue.remove(self.group)
681 stream.write(self.obj)
682 return output_width + self.width
683
684
685class Group(Printable):
686 def __init__(self, depth: int) -> None:
687 self.depth = depth
688 self.breakables: deque[Breakable] = deque()
689 self.want_break: bool = False
690
691
692class GroupQueue:
693 def __init__(self, *groups: Group) -> None:
694 self.queue: list[list[Group]] = []
695 for group in groups:
696 self.enq(group)
697
698 def enq(self, group: Group) -> None:
699 depth = group.depth
700 while depth > len(self.queue) - 1:
701 self.queue.append([])
702 self.queue[depth].append(group)
703
704 def deq(self) -> Group | None:
705 for stack in self.queue:
706 for idx, group in enumerate(reversed(stack)):
707 if group.breakables:
708 del stack[idx]
709 group.want_break = True
710 return group
711 for group in stack:
712 group.want_break = True
713 del stack[:]
714 return None
715
716 def remove(self, group: Group) -> None:
717 try:
718 self.queue[group.depth].remove(group)
719 except ValueError:
720 pass
721
722
723def _seq_pprinter_factory(start: str, end: str, basetype: type) -> PrettyPrintFunction:
724 """Factory that returns a pprint function useful for sequences.
725
726 Used by the default pprint for tuples, dicts, and lists.
727 """
728
729 def inner(
730 obj: tuple[object] | list[object], p: RepresentationPrinter, cycle: bool
731 ) -> None:
732 typ = type(obj)
733 if (
734 basetype is not None
735 and typ is not basetype
736 and typ.__repr__ != basetype.__repr__ # type: ignore[comparison-overlap]
737 ):
738 # If the subclass provides its own repr, use it instead.
739 return p.text(typ.__repr__(obj))
740
741 if cycle:
742 return p.text(start + "..." + end)
743 step = len(start)
744 with p.group(step, start, end):
745 for idx, x in p._enumerate(obj):
746 if idx:
747 p.text(",")
748 p.breakable()
749 p.pretty(x)
750 if len(obj) == 1 and type(obj) is tuple:
751 # Special case for 1-item tuples.
752 p.text(",")
753
754 return inner
755
756
757def get_class_name(cls: type[object]) -> str:
758 class_name = _safe_getattr(cls, "__qualname__", cls.__name__)
759 assert isinstance(class_name, str)
760 return class_name
761
762
763def _set_pprinter_factory(
764 start: str, end: str, basetype: type[object]
765) -> PrettyPrintFunction:
766 """Factory that returns a pprint function useful for sets and
767 frozensets."""
768
769 def inner(
770 obj: set[Any] | frozenset[Any],
771 p: RepresentationPrinter,
772 cycle: bool,
773 ) -> None:
774 typ = type(obj)
775 if (
776 basetype is not None
777 and typ is not basetype
778 and typ.__repr__ != basetype.__repr__
779 ):
780 # If the subclass provides its own repr, use it instead.
781 return p.text(typ.__repr__(obj))
782
783 if cycle:
784 return p.text(start + "..." + end)
785 if not obj:
786 # Special case.
787 p.text(get_class_name(basetype) + "()")
788 else:
789 step = len(start)
790 with p.group(step, start, end):
791 # Like dictionary keys, try to sort the items if there aren't too many
792 items: Iterable[object] = obj
793 if not (p.max_seq_length and len(obj) >= p.max_seq_length):
794 try:
795 items = sorted(obj)
796 except Exception:
797 # Sometimes the items don't sort.
798 pass
799 for idx, x in p._enumerate(items):
800 if idx:
801 p.text(",")
802 p.breakable()
803 p.pretty(x)
804
805 return inner
806
807
808def _dict_pprinter_factory(
809 start: str, end: str, basetype: type[object] | None = None
810) -> PrettyPrintFunction:
811 """Factory that returns a pprint function used by the default pprint of
812 dicts and dict proxies."""
813
814 def inner(obj: dict[object, object], p: RepresentationPrinter, cycle: bool) -> None:
815 typ = type(obj)
816 if (
817 basetype is not None
818 and typ is not basetype
819 and typ.__repr__ != basetype.__repr__
820 ):
821 # If the subclass provides its own repr, use it instead.
822 return p.text(typ.__repr__(obj))
823
824 if cycle:
825 return p.text("{...}")
826 with (
827 p.group(1, start, end),
828 # If the dict contains both "" and b"" (empty string and empty bytes), we
829 # ignore the BytesWarning raised by `python -bb` mode. We can't use
830 # `.items()` because it might be a non-`dict` type of mapping.
831 warnings.catch_warnings(),
832 ):
833 warnings.simplefilter("ignore", BytesWarning)
834 for idx, key in p._enumerate(obj):
835 if idx:
836 p.text(",")
837 p.breakable()
838 p.pretty(key)
839 p.text(": ")
840 p.pretty(obj[key])
841
842 inner.__name__ = f"_dict_pprinter_factory({start!r}, {end!r}, {basetype!r})"
843 return inner
844
845
846def _super_pprint(obj: Any, p: RepresentationPrinter, cycle: bool) -> None:
847 """The pprint for the super type."""
848 with p.group(8, "<super: ", ">"):
849 p.pretty(obj.__thisclass__)
850 p.text(",")
851 p.breakable()
852 p.pretty(obj.__self__)
853
854
855def _re_pattern_pprint(obj: re.Pattern, p: RepresentationPrinter, cycle: bool) -> None:
856 """The pprint function for regular expression patterns."""
857 p.text("re.compile(")
858 pattern = repr(obj.pattern)
859 if pattern[:1] in "uU": # pragma: no cover
860 pattern = pattern[1:]
861 prefix = "ur"
862 else:
863 prefix = "r"
864 pattern = prefix + pattern.replace("\\\\", "\\")
865 p.text(pattern)
866 if obj.flags:
867 p.text(",")
868 p.breakable()
869 done_one = False
870 for flag in (
871 "TEMPLATE",
872 "IGNORECASE",
873 "LOCALE",
874 "MULTILINE",
875 "DOTALL",
876 "UNICODE",
877 "VERBOSE",
878 "DEBUG",
879 ):
880 if obj.flags & getattr(re, flag, 0):
881 if done_one:
882 p.text("|")
883 p.text("re." + flag)
884 done_one = True
885 p.text(")")
886
887
888def _type_pprint(obj: type[object], p: RepresentationPrinter, cycle: bool) -> None:
889 """The pprint for classes and types."""
890 # Heap allocated types might not have the module attribute,
891 # and others may set it to None.
892
893 # Checks for a __repr__ override in the metaclass
894 # != rather than is not because pypy compatibility
895 if type(obj).__repr__ != type.__repr__: # type: ignore[comparison-overlap]
896 _repr_pprint(obj, p, cycle)
897 return
898
899 mod = _safe_getattr(obj, "__module__", None)
900 try:
901 name = obj.__qualname__
902 except Exception: # pragma: no cover
903 name = obj.__name__
904 if not isinstance(name, str):
905 name = "<unknown type>"
906
907 if mod in (None, "__builtin__", "builtins", "exceptions"):
908 p.text(name)
909 else:
910 p.text(mod + "." + name)
911
912
913def _repr_pprint(obj: object, p: RepresentationPrinter, cycle: bool) -> None:
914 """A pprint that just redirects to the normal repr function."""
915 # Find newlines and replace them with p.break_()
916 output = repr(obj)
917 for idx, output_line in enumerate(output.splitlines()):
918 if idx:
919 p.break_()
920 p.text(output_line)
921
922
923def pprint_fields(
924 obj: object, p: RepresentationPrinter, cycle: bool, fields: Iterable[str]
925) -> None:
926 name = get_class_name(obj.__class__)
927 if cycle:
928 return p.text(f"{name}(...)")
929 with p.group(1, name + "(", ")"):
930 for idx, field in enumerate(fields):
931 if idx:
932 p.text(",")
933 p.breakable()
934 p.text(field)
935 p.text("=")
936 p.pretty(getattr(obj, field))
937
938
939def _get_span_comment(
940 p: RepresentationPrinter,
941 arg_labels: ArgLabelsT,
942 key: Any,
943) -> tuple[str, int] | None:
944 """Look up a comment for a span, if not already printed at a higher level."""
945 span_index = arg_labels.get(key)
946 if span_index is not None and span_index in p.span_comments:
947 if span_index not in p._commented_spans:
948 return (p.span_comments[span_index], span_index)
949 return None
950
951
952def _tuple_pprinter(arg_labels: ArgLabelsT) -> PrettyPrintFunction:
953 """Pretty printer for tuples that shows sub-argument comments."""
954
955 def inner(obj: tuple, p: RepresentationPrinter, cycle: bool) -> None:
956 if cycle:
957 return p.text("(...)")
958
959 get = lambda i: _get_span_comment(p, arg_labels, f"arg[{i}]")
960 has_comments = any(get(i) for i in range(len(obj)))
961
962 with p.group(indent=4, open="(", close=""):
963 for idx, x in p._enumerate(obj):
964 p.break_() if has_comments else (p.breakable() if idx else None)
965 p.pretty(x)
966 if has_comments or idx + 1 < len(obj) or len(obj) == 1:
967 p.text(",")
968 if entry := get(idx):
969 p._commented_spans.add(entry[1])
970 p.text(f" # {entry[0]}")
971 if has_comments and obj:
972 p.break_()
973 p.text(")")
974
975 return inner
976
977
978def _fixeddict_pprinter(arg_labels: ArgLabelsT) -> PrettyPrintFunction:
979 """Pretty printer for fixed_dictionaries that shows sub-argument comments."""
980
981 def inner(obj: dict, p: RepresentationPrinter, cycle: bool) -> None:
982 if cycle:
983 return p.text("{...}")
984
985 get = lambda k: _get_span_comment(p, arg_labels, k)
986 # Print in the dict's actual (possibly permuted) iteration order.
987 keys = list(obj)
988 has_comments = any(get(k) for k in keys)
989
990 with p.group(indent=4, open="{", close=""):
991 for idx, key in p._enumerate(keys):
992 p.break_() if has_comments else (p.breakable() if idx else None)
993 p.pretty(key)
994 p.text(": ")
995 p.pretty(obj[key])
996 if has_comments or idx + 1 < len(keys):
997 p.text(",")
998 if entry := get(key):
999 p._commented_spans.add(entry[1])
1000 p.text(f" # {entry[0]}")
1001 if has_comments and obj:
1002 p.break_()
1003 p.text("}")
1004
1005 return inner
1006
1007
1008def _function_pprint(
1009 obj: types.FunctionType | types.BuiltinFunctionType | types.MethodType,
1010 p: RepresentationPrinter,
1011 cycle: bool,
1012) -> None:
1013 """Base pprint for all functions and builtin functions."""
1014 from hypothesis.internal.reflection import get_pretty_function_description
1015
1016 p.text(get_pretty_function_description(obj))
1017
1018
1019def _exception_pprint(
1020 obj: BaseException, p: RepresentationPrinter, cycle: bool
1021) -> None:
1022 """Base pprint for all exceptions."""
1023 name = getattr(obj.__class__, "__qualname__", obj.__class__.__name__)
1024 if obj.__class__.__module__ not in ("exceptions", "builtins"):
1025 name = f"{obj.__class__.__module__}.{name}"
1026 step = len(name) + 1
1027 with p.group(step, name + "(", ")"):
1028 for idx, arg in enumerate(getattr(obj, "args", ())):
1029 if idx:
1030 p.text(",")
1031 p.breakable()
1032 p.pretty(arg)
1033
1034
1035def _repr_integer(obj: int, p: RepresentationPrinter, cycle: bool) -> None:
1036 if abs(obj) < 1_000_000_000:
1037 p.text(repr(obj))
1038 elif abs(obj) < 10**640:
1039 # add underscores for integers over ten decimal digits
1040 p.text(f"{obj:#_d}")
1041 else:
1042 # for very very large integers, use hex because power-of-two bases are cheaper
1043 # https://docs.python.org/3/library/stdtypes.html#integer-string-conversion-length-limitation
1044 p.text(f"{obj:#_x}")
1045
1046
1047def _repr_float_counting_nans(
1048 obj: float, p: RepresentationPrinter, cycle: bool
1049) -> None:
1050 if isnan(obj):
1051 if struct.pack("!d", abs(obj)) != struct.pack("!d", float("nan")):
1052 show = hex(*struct.unpack("Q", struct.pack("d", obj)))
1053 return p.text(f"struct.unpack('d', struct.pack('Q', {show}))[0]")
1054 elif copysign(1.0, obj) == -1.0:
1055 return p.text("-nan")
1056 p.text(repr(obj))
1057
1058
1059#: printers for builtin types
1060_type_pprinters: dict[type, PrettyPrintFunction] = {
1061 int: _repr_integer,
1062 float: _repr_float_counting_nans,
1063 str: _repr_pprint,
1064 tuple: _seq_pprinter_factory("(", ")", tuple),
1065 list: _seq_pprinter_factory("[", "]", list),
1066 dict: _dict_pprinter_factory("{", "}", dict),
1067 set: _set_pprinter_factory("{", "}", set),
1068 frozenset: _set_pprinter_factory("frozenset({", "})", frozenset),
1069 super: _super_pprint,
1070 re.Pattern: _re_pattern_pprint,
1071 type: _type_pprint,
1072 types.FunctionType: _function_pprint,
1073 types.BuiltinFunctionType: _function_pprint,
1074 types.MethodType: _function_pprint,
1075 datetime.datetime: _repr_pprint,
1076 datetime.timedelta: _repr_pprint,
1077 BaseException: _exception_pprint,
1078 slice: _repr_pprint,
1079 range: _repr_pprint,
1080 bytes: _repr_pprint,
1081}
1082
1083#: printers for types specified by name
1084_deferred_type_pprinters: dict[tuple[str, str], PrettyPrintFunction] = {}
1085
1086
1087def for_type_by_name(
1088 type_module: str, type_name: str, func: PrettyPrintFunction
1089) -> PrettyPrintFunction | None:
1090 """Add a pretty printer for a type specified by the module and name of a
1091 type rather than the type object itself."""
1092 key = (type_module, type_name)
1093 oldfunc = _deferred_type_pprinters.get(key)
1094 _deferred_type_pprinters[key] = func
1095 return oldfunc
1096
1097
1098#: printers for the default singletons
1099_singleton_pprinters: dict[int, PrettyPrintFunction] = dict.fromkeys(
1100 map(id, [None, True, False, Ellipsis, NotImplemented]), _repr_pprint
1101)
1102
1103
1104def _defaultdict_pprint(
1105 obj: defaultdict[object, object], p: RepresentationPrinter, cycle: bool
1106) -> None:
1107 name = obj.__class__.__name__
1108 with p.group(len(name) + 1, name + "(", ")"):
1109 if cycle:
1110 p.text("...")
1111 else:
1112 p.pretty(obj.default_factory)
1113 p.text(",")
1114 p.breakable()
1115 p.pretty(dict(obj))
1116
1117
1118def _ordereddict_pprint(
1119 obj: OrderedDict[object, object], p: RepresentationPrinter, cycle: bool
1120) -> None:
1121 name = obj.__class__.__name__
1122 with p.group(len(name) + 1, name + "(", ")"):
1123 if cycle:
1124 p.text("...")
1125 elif obj:
1126 p.pretty(list(obj.items()))
1127
1128
1129def _deque_pprint(obj: deque[object], p: RepresentationPrinter, cycle: bool) -> None:
1130 name = obj.__class__.__name__
1131 with p.group(len(name) + 1, name + "(", ")"):
1132 if cycle:
1133 p.text("...")
1134 else:
1135 p.pretty(list(obj))
1136
1137
1138def _counter_pprint(
1139 obj: Counter[object], p: RepresentationPrinter, cycle: bool
1140) -> None:
1141 name = obj.__class__.__name__
1142 with p.group(len(name) + 1, name + "(", ")"):
1143 if cycle:
1144 p.text("...")
1145 elif obj:
1146 p.pretty(dict(obj))
1147
1148
1149def _repr_dataframe(
1150 obj: object, p: RepresentationPrinter, cycle: bool
1151) -> None: # pragma: no cover
1152 with p.indent(4):
1153 p.break_()
1154 _repr_pprint(obj, p, cycle)
1155 p.break_()
1156
1157
1158def _repr_enum(obj: Enum, p: RepresentationPrinter, cycle: bool) -> None:
1159 tname = get_class_name(type(obj))
1160 if isinstance(obj, Flag):
1161 p.text(
1162 " | ".join(f"{tname}.{x.name}" for x in type(obj) if x & obj == x)
1163 or f"{tname}({obj.value!r})" # if no matching members
1164 )
1165 else:
1166 p.text(f"{tname}.{obj.name}")
1167
1168
1169class _ReprDots:
1170 def __repr__(self) -> str:
1171 return "..."
1172
1173
1174def _repr_partial(obj: partial[Any], p: RepresentationPrinter, cycle: bool) -> None:
1175 args, kw = obj.args, obj.keywords
1176 if cycle:
1177 args, kw = (_ReprDots(),), {}
1178 p.repr_call(pretty(type(obj)), (obj.func, *args), kw)
1179
1180
1181for_type_by_name("collections", "defaultdict", _defaultdict_pprint)
1182for_type_by_name("collections", "OrderedDict", _ordereddict_pprint)
1183for_type_by_name("ordereddict", "OrderedDict", _ordereddict_pprint)
1184for_type_by_name("collections", "deque", _deque_pprint)
1185for_type_by_name("collections", "Counter", _counter_pprint)
1186for_type_by_name("pandas.core.frame", "DataFrame", _repr_dataframe)
1187for_type_by_name("enum", "Enum", _repr_enum)
1188for_type_by_name("functools", "partial", _repr_partial)