214 lines
4.7 KiB
Python
214 lines
4.7 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
"""
|
||
|
jishaku.repl.inspections
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Inspections performable on Python objects.
|
||
|
|
||
|
:copyright: (c) 2019 Devon (Gorialis) R
|
||
|
:license: MIT, see LICENSE for more details.
|
||
|
|
||
|
"""
|
||
|
|
||
|
import collections
|
||
|
import functools
|
||
|
import inspect
|
||
|
import os
|
||
|
|
||
|
INSPECTIONS = []
|
||
|
MethodWrapperType = type((1).__le__)
|
||
|
WrapperDescriptorType = type(int.__le__)
|
||
|
|
||
|
|
||
|
def add_inspection(name):
|
||
|
"""
|
||
|
Add a Jishaku object inspection
|
||
|
"""
|
||
|
|
||
|
# create the real decorator
|
||
|
def inspection_inner(func):
|
||
|
"""
|
||
|
Jishaku inspection decorator
|
||
|
"""
|
||
|
|
||
|
# pylint: disable=inconsistent-return-statements
|
||
|
|
||
|
# create an encapsulated version of the inspection that swallows exceptions
|
||
|
@functools.wraps(func)
|
||
|
def encapsulated(*args, **kwargs):
|
||
|
try:
|
||
|
return func(*args, **kwargs)
|
||
|
except (TypeError, AttributeError, ValueError, OSError):
|
||
|
return
|
||
|
|
||
|
INSPECTIONS.append((name, encapsulated))
|
||
|
return func
|
||
|
return inspection_inner
|
||
|
|
||
|
|
||
|
def all_inspections(obj):
|
||
|
"""
|
||
|
Generator to iterate all current Jishaku inspections.
|
||
|
"""
|
||
|
|
||
|
for name, callback in INSPECTIONS:
|
||
|
result = callback(obj)
|
||
|
if result:
|
||
|
yield name, result
|
||
|
|
||
|
|
||
|
def class_name(obj):
|
||
|
"""
|
||
|
Get the name of an object, including the module name if available.
|
||
|
"""
|
||
|
|
||
|
name = obj.__name__
|
||
|
module = getattr(obj, '__module__')
|
||
|
|
||
|
if module:
|
||
|
name = f'{module}.{name}'
|
||
|
return name
|
||
|
|
||
|
|
||
|
# pylint: disable=missing-docstring
|
||
|
# pylint: disable=inconsistent-return-statements
|
||
|
|
||
|
|
||
|
@add_inspection("Type")
|
||
|
def type_inspection(obj):
|
||
|
return type(obj).__name__
|
||
|
|
||
|
|
||
|
@add_inspection("Object ID")
|
||
|
def id_inspection(obj):
|
||
|
return hex(id(obj))
|
||
|
|
||
|
|
||
|
@add_inspection("Length")
|
||
|
def len_inspection(obj):
|
||
|
return len(obj)
|
||
|
|
||
|
|
||
|
@add_inspection("MRO")
|
||
|
def mro_inspection(obj):
|
||
|
if not inspect.isclass(obj):
|
||
|
return
|
||
|
|
||
|
return ', '.join(class_name(x) for x in inspect.getmro(obj))
|
||
|
|
||
|
|
||
|
@add_inspection("Type MRO")
|
||
|
def type_mro_inspection(obj):
|
||
|
obj_type = type(obj)
|
||
|
if obj_type in (type, object):
|
||
|
return
|
||
|
|
||
|
return ', '.join(class_name(x) for x in inspect.getmro(obj_type))
|
||
|
|
||
|
|
||
|
@add_inspection("Subclasses")
|
||
|
def subclass_inspection(obj):
|
||
|
if isinstance(obj, type) and hasattr(obj, "__subclasses__"):
|
||
|
subclasses = type.__subclasses__(obj)
|
||
|
else:
|
||
|
return
|
||
|
|
||
|
output = ', '.join(class_name(x) for x in subclasses[0:5])
|
||
|
|
||
|
if len(subclasses) > 5:
|
||
|
output += ', ...'
|
||
|
|
||
|
return output
|
||
|
|
||
|
|
||
|
@add_inspection("Module Name")
|
||
|
def module_inspection(obj):
|
||
|
return inspect.getmodule(obj).__name__
|
||
|
|
||
|
|
||
|
@add_inspection("File Location")
|
||
|
def file_loc_inspection(obj):
|
||
|
file_loc = inspect.getfile(obj)
|
||
|
cwd = os.getcwd()
|
||
|
if file_loc.startswith(cwd):
|
||
|
file_loc = "." + file_loc[len(cwd):]
|
||
|
return file_loc
|
||
|
|
||
|
|
||
|
@add_inspection("Line Span")
|
||
|
def line_span_inspection(obj):
|
||
|
source_lines, source_offset = inspect.getsourcelines(obj)
|
||
|
return f"{source_offset}-{source_offset + len(source_lines)}"
|
||
|
|
||
|
|
||
|
@add_inspection("Signature")
|
||
|
def sig_inspection(obj):
|
||
|
return inspect.signature(obj)
|
||
|
|
||
|
|
||
|
@add_inspection("Content Types")
|
||
|
def content_type_inspection(obj):
|
||
|
if not isinstance(obj, (tuple, list, set)):
|
||
|
return
|
||
|
|
||
|
total = len(obj)
|
||
|
types = collections.Counter(type(x) for x in obj)
|
||
|
|
||
|
output = ', '.join(f'{x.__name__} ({y*100/total:.1f}\uFF05)' for x, y in types.most_common(3))
|
||
|
if len(types) > 3:
|
||
|
output += ', ...'
|
||
|
|
||
|
return output
|
||
|
|
||
|
|
||
|
POSSIBLE_OPS = {
|
||
|
'<': 'lt',
|
||
|
'<=': 'le',
|
||
|
'==': 'eq',
|
||
|
'!=': 'ne',
|
||
|
'>': 'gt',
|
||
|
'>=': 'ge',
|
||
|
'+': 'add',
|
||
|
'-': 'sub',
|
||
|
'*': 'mul',
|
||
|
'@': 'matmul',
|
||
|
'/': 'truediv',
|
||
|
'//': 'floordiv',
|
||
|
'\uFF05': 'mod', # fake percent to avoid prolog comment
|
||
|
'**': 'pow',
|
||
|
'<<': 'lshift',
|
||
|
'>>': 'rshift',
|
||
|
'&': 'and',
|
||
|
'^': 'xor',
|
||
|
'|': 'or'
|
||
|
}
|
||
|
|
||
|
|
||
|
def check_not_slot(obj, attr):
|
||
|
"""
|
||
|
Check that a given attribute isn't just an open slot for subclasses
|
||
|
"""
|
||
|
|
||
|
if isinstance(getattr(obj, attr, None), MethodWrapperType):
|
||
|
return not isinstance(getattr(type(obj), attr, None), WrapperDescriptorType)
|
||
|
|
||
|
return not isinstance(getattr(obj, attr, None), WrapperDescriptorType)
|
||
|
|
||
|
|
||
|
@add_inspection("Operations")
|
||
|
def compat_operation_inspection(obj):
|
||
|
this_dict = dir(obj)
|
||
|
operations = []
|
||
|
|
||
|
for operation, member in POSSIBLE_OPS.items():
|
||
|
if f'__{member}__' in this_dict and check_not_slot(obj, f'__{member}__'):
|
||
|
operations.append(operation)
|
||
|
elif f'__r{member}__' in this_dict and check_not_slot(obj, f'r__{member}__'):
|
||
|
operations.append(operation)
|
||
|
|
||
|
if f'__i{member}__' in this_dict and check_not_slot(obj, f'i__{member}__'):
|
||
|
operations.append(f'{operation}=')
|
||
|
|
||
|
return ' '.join(operations)
|