From 62eccdac73d852d3ab9df06497bc8c9063e3d283 Mon Sep 17 00:00:00 2001 From: Eric Lin Date: Wed, 5 Aug 2020 15:08:37 -0400 Subject: Verify that a completer function is defined in a CommandSet before passing it a CommandSet instance. Search for a CommandSet instance that matches the completer's parent class type.` Resolves Issue #967 Renamed isolated_tests directory to tests_isolated for better visual grouping. Added some exception documentation --- cmd2/utils.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'cmd2/utils.py') diff --git a/cmd2/utils.py b/cmd2/utils.py index 5a4fdbf7..39dc6e2b 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -3,7 +3,9 @@ import collections import collections.abc as collections_abc +import functools import glob +import inspect import os import re import subprocess @@ -11,7 +13,7 @@ import sys import threading import unicodedata from enum import Enum -from typing import Any, Callable, Dict, Iterable, List, Optional, TextIO, Union +from typing import Any, Callable, Dict, Iterable, List, Optional, TextIO, Type, Union from . import constants @@ -1037,3 +1039,30 @@ def categorize(func: Union[Callable, Iterable[Callable]], category: str) -> None setattr(item, constants.CMD_ATTR_HELP_CATEGORY, category) else: setattr(func, constants.CMD_ATTR_HELP_CATEGORY, category) + + +def get_defining_class(meth: Callable) -> Optional[Type]: + """ + Attempts to resolve the class that defined a method. + + Inspired by implementation published here: + https://stackoverflow.com/a/25959545/1956611 + + :param meth: method to inspect + :return: class type in which the supplied method was defined. None if it couldn't be resolved. + """ + if isinstance(meth, functools.partial): + return get_defining_class(meth.func) + if inspect.ismethod(meth) or (inspect.isbuiltin(meth) + and getattr(meth, '__self__') is not None + and getattr(meth.__self__, '__class__')): + for cls in inspect.getmro(meth.__self__.__class__): + if meth.__name__ in cls.__dict__: + return cls + meth = getattr(meth, '__func__', meth) # fallback to __qualname__ parsing + if inspect.isfunction(meth): + cls = getattr(inspect.getmodule(meth), + meth.__qualname__.split('.', 1)[0].rsplit('.', 1)[0]) + if isinstance(cls, type): + return cls + return getattr(meth, '__objclass__', None) # handle special descriptor objects -- cgit v1.2.1