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