from __future__ import absolute_import
import inspect
from inspect import cleandoc , getdoc , getfile , isclass , ismodule , signature
from typing import Any , Collection , Iterable , Optional , Tuple , Type , Union
from . console import Group , RenderableType
from . control import escape_control_codes
from . highlighter import ReprHighlighter
from . jupyter import JupyterMixin
from . panel import Panel
from . pretty import Pretty
from . table import Table
from . text import Text , TextType
def _first_paragraph ( doc : str ) - > str :
""" Get the first paragraph from a docstring. """
paragraph , _ , _ = doc . partition ( " \n \n " )
return paragraph
class Inspect ( JupyterMixin ) :
""" A renderable to inspect any Python Object.
Args :
obj ( Any ) : An object to inspect .
title ( str , optional ) : Title to display over inspect result , or None use type . Defaults to None .
help ( bool , optional ) : Show full help text rather than just first paragraph . Defaults to False .
methods ( bool , optional ) : Enable inspection of callables . Defaults to False .
docs ( bool , optional ) : Also render doc strings . Defaults to True .
private ( bool , optional ) : Show private attributes ( beginning with underscore ) . Defaults to False .
dunder ( bool , optional ) : Show attributes starting with double underscore . Defaults to False .
sort ( bool , optional ) : Sort attributes alphabetically . Defaults to True .
all ( bool , optional ) : Show all attributes . Defaults to False .
value ( bool , optional ) : Pretty print value of object . Defaults to True .
"""
def __init__ (
self ,
obj : Any ,
* ,
title : Optional [ TextType ] = None ,
help : bool = False ,
methods : bool = False ,
docs : bool = True ,
private : bool = False ,
dunder : bool = False ,
sort : bool = True ,
all : bool = True ,
value : bool = True ,
) - > None :
self . highlighter = ReprHighlighter ( )
self . obj = obj
self . title = title or self . _make_title ( obj )
if all :
methods = private = dunder = True
self . help = help
self . methods = methods
self . docs = docs or help
self . private = private or dunder
self . dunder = dunder
self . sort = sort
self . value = value
def _make_title ( self , obj : Any ) - > Text :
""" Make a default title. """
title_str = (
str ( obj )
if ( isclass ( obj ) or callable ( obj ) or ismodule ( obj ) )
else str ( type ( obj ) )
)
title_text = self . highlighter ( title_str )
return title_text
def __rich__ ( self ) - > Panel :
return Panel . fit (
Group ( * self . _render ( ) ) ,
title = self . title ,
border_style = " scope.border " ,
padding = ( 0 , 1 ) ,
)
def _get_signature ( self , name : str , obj : Any ) - > Optional [ Text ] :
""" Get a signature for a callable. """
try :
_signature = str ( signature ( obj ) ) + " : "
except ValueError :
_signature = " (...) "
except TypeError :
return None
source_filename : Optional [ str ] = None
try :
source_filename = getfile ( obj )
except ( OSError , TypeError ) :
# OSError is raised if obj has no source file, e.g. when defined in REPL.
pass
callable_name = Text ( name , style = " inspect.callable " )
if source_filename :
callable_name . stylize ( f " link file:// { source_filename } " )
signature_text = self . highlighter ( _signature )
qualname = name or getattr ( obj , " __qualname__ " , name )
# If obj is a module, there may be classes (which are callable) to display
if inspect . isclass ( obj ) :
prefix = " class "
elif inspect . iscoroutinefunction ( obj ) :
prefix = " async def "
else :
prefix = " def "
qual_signature = Text . assemble (
( f " { prefix } " , f " inspect. { prefix . replace ( ' ' , ' _ ' ) } " ) ,
( qualname , " inspect.callable " ) ,
signature_text ,
)
return qual_signature
def _render ( self ) - > Iterable [ RenderableType ] :
""" Render object. """
def sort_items ( item : Tuple [ str , Any ] ) - > Tuple [ bool , str ] :
key , ( _error , value ) = item
return ( callable ( value ) , key . strip ( " _ " ) . lower ( ) )
def safe_getattr ( attr_name : str ) - > Tuple [ Any , Any ] :
""" Get attribute or any exception. """
try :
return ( None , getattr ( obj , attr_name ) )
except Exception as error :
return ( error , None )
obj = self . obj
keys = dir ( obj )
total_items = len ( keys )
if not self . dunder :
keys = [ key for key in keys if not key . startswith ( " __ " ) ]
if not self . private :
keys = [ key for key in keys if not key . startswith ( " _ " ) ]
not_shown_count = total_items - len ( keys )
items = [ ( key , safe_getattr ( key ) ) for key in keys ]
if self . sort :
items . sort ( key = sort_items )
items_table = Table . grid ( padding = ( 0 , 1 ) , expand = False )
items_table . add_column ( justify = " right " )
add_row = items_table . add_row
highlighter = self . highlighter
if callable ( obj ) :
signature = self . _get_signature ( " " , obj )
if signature is not None :
yield signature
yield " "
if self . docs :
_doc = self . _get_formatted_doc ( obj )
if _doc is not None :
doc_text = Text ( _doc , style = " inspect.help " )
doc_text = highlighter ( doc_text )
yield doc_text
yield " "
if self . value and not ( isclass ( obj ) or callable ( obj ) or ismodule ( obj ) ) :
yield Panel (
Pretty ( obj , indent_guides = True , max_length = 10 , max_string = 60 ) ,
border_style = " inspect.value.border " ,
)
yield " "
for key , ( error , value ) in items :
key_text = Text . assemble (
(
key ,
" inspect.attr.dunder " if key . startswith ( " __ " ) else " inspect.attr " ,
) ,
( " = " , " inspect.equals " ) ,
)
if error is not None :
warning = key_text . copy ( )
warning . stylize ( " inspect.error " )
add_row ( warning , highlighter ( repr ( error ) ) )
continue
if callable ( value ) :
if not self . methods :
continue
_signature_text = self . _get_signature ( key , value )
if _signature_text is None :
add_row ( key_text , Pretty ( value , highlighter = highlighter ) )
else :
if self . docs :
docs = self . _get_formatted_doc ( value )
if docs is not None :
_signature_text . append ( " \n " if " \n " in docs else " " )
doc = highlighter ( docs )
doc . stylize ( " inspect.doc " )
_signature_text . append ( doc )
add_row ( key_text , _signature_text )
else :
add_row ( key_text , Pretty ( value , highlighter = highlighter ) )
if items_table . row_count :
yield items_table
elif not_shown_count :
yield Text . from_markup (
f " [b cyan] { not_shown_count } [/][i] attribute(s) not shown.[/i] "
f " Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options. "
)
def _get_formatted_doc ( self , object_ : Any ) - > Optional [ str ] :
"""
Extract the docstring of an object , process it and returns it .
The processing consists in cleaning up the doctring ' s indentation,
taking only its 1 st paragraph if ` self . help ` is not True ,
and escape its control codes .
Args :
object_ ( Any ) : the object to get the docstring from .
Returns :
Optional [ str ] : the processed docstring , or None if no docstring was found .
"""
docs = getdoc ( object_ )
if docs is None :
return None
docs = cleandoc ( docs ) . strip ( )
if not self . help :
docs = _first_paragraph ( docs )
return escape_control_codes ( docs )
def get_object_types_mro ( obj : Union [ object , Type [ Any ] ] ) - > Tuple [ type , . . . ] :
""" Returns the MRO of an object ' s class, or of the object itself if it ' s a class. """
if not hasattr ( obj , " __mro__ " ) :
# N.B. we cannot use `if type(obj) is type` here because it doesn't work with
# some types of classes, such as the ones that use abc.ABCMeta.
obj = type ( obj )
return getattr ( obj , " __mro__ " , ( ) )
def get_object_types_mro_as_strings ( obj : object ) - > Collection [ str ] :
"""
Returns the MRO of an object ' s class as full qualified names, or of the object itself if it ' s a class .
Examples :
` object_types_mro_as_strings ( JSONDecoder ) ` will return ` [ ' json.decoder.JSONDecoder ' , ' builtins.object ' ] `
"""
return [
f ' { getattr ( type_ , " __module__ " , " " ) } . { getattr ( type_ , " __qualname__ " , " " ) } '
for type_ in get_object_types_mro ( obj )
]
def is_object_one_of_types (
obj : object , fully_qualified_types_names : Collection [ str ]
) - > bool :
"""
Returns ` True ` if the given object ' s class (or the object itself, if it ' s a class ) has one of the
fully qualified names in its MRO .
"""
for type_name in get_object_types_mro_as_strings ( obj ) :
if type_name in fully_qualified_types_names :
return True
return False