diff --git a/fire/core.py b/fire/core.py index 8e23e76b..35104fb4 100644 --- a/fire/core.py +++ b/fire/core.py @@ -456,6 +456,11 @@ def _Fire(component, args, parsed_flag_args, context, name=None): is_callable = inspect.isclass(component) or inspect.isroutine(component) is_callable_object = callable(component) and not is_callable + if (inspect.isclass(component) + and _IsClassOrStaticMethodAccess(component, remaining_args)): + # Classmethods and staticmethods don't need an instance, so skip __init__ + # and let the object handler below access the method on the class. + is_callable = False is_sequence = isinstance(component, (list, tuple)) is_map = isinstance(component, dict) or inspectutils.IsNamedTuple(component) @@ -619,6 +624,19 @@ def _Fire(component, args, parsed_flag_args, context, name=None): return component_trace +def _IsClassOrStaticMethodAccess(cls, args): + """Returns whether the next arg names a classmethod or staticmethod of cls.""" + if not args: + return False + for name in (args[0], args[0].replace('-', '_')): + try: + attr = inspect.getattr_static(cls, name) + except AttributeError: + continue + return isinstance(attr, (classmethod, staticmethod)) + return False + + def _GetMember(component, args): """Returns a subcomponent of component by consuming an arg from args. diff --git a/fire/core_test.py b/fire/core_test.py index f48d6e2d..3cb7a7b3 100644 --- a/fire/core_test.py +++ b/fire/core_test.py @@ -189,6 +189,12 @@ def testClassMethod(self): 7, ) + def testStaticAndClassMethodsSkipConstructor(self): + component = tc.ErrorInConstructorWithStaticAndClassMethods + self.assertEqual(core.Fire(component, command=['static_fn', 'alpha']), + 'alpha') + self.assertEqual(core.Fire(component, command=['class-fn', '6']), 7) + def testCustomSerialize(self): def serialize(x): if isinstance(x, list): diff --git a/fire/test_components.py b/fire/test_components.py index 887a0dc6..8ce4b629 100644 --- a/fire/test_components.py +++ b/fire/test_components.py @@ -64,6 +64,20 @@ def __init__(self, value='value'): raise ValueError('Error in constructor') +class ErrorInConstructorWithStaticAndClassMethods: + + def __init__(self): + raise ValueError('Error in constructor') + + @staticmethod + def static_fn(args): + return args + + @classmethod + def class_fn(cls, args): + return args + 1 + + class WithHelpArg: """Test class for testing when class has a help= arg."""