Repository: borgbackup/borg

Issue:
review set_defaults (fallback_)func usage
Looks like there are argparse compatibility issues, had a ton of test fails with python 3.4.0 and 3.4.2 due to that change:
```
 +        parser.set_defaults(func=functools.partial(self.do_maincommand_help, parser)) 
```

Added here: https://github.com/borgbackup/borg/pull/3155/commits/c768999a05d27bacd272298e3cd2a14c9deee70b to fix the traceback seen in #3142. 

On such old pythons, with that change, stuff like this does not work any more, it just shows the help:
```
borg create repo::arch input
```

TODO: review all set_defaults calls and add some explanation when to use what. Test on 3.4.0 or 3.4.2.


Discussion:
python 3.4.6 (travis) does not trigger this issue.

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -169,6 +169,18 @@ def parse_storage_quota(storage_quota):
     return parsed
 
 
+def get_func(args):
+    # This works around http://bugs.python.org/issue9351
+    # func is used at the leaf parsers of the argparse parser tree,
+    # fallback_func at next level towards the root,
+    # fallback2_func at the 2nd next level (which is root in our case).
+    for name in 'func', 'fallback_func', 'fallback2_func':
+        func = getattr(args, name, None)
+        if func is not None:
+            return func
+    raise Exception('expected func attributes not found')
+
+
 class Archiver:
 
     def __init__(self, lock_wait=None, prog=None):
@@ -2233,7 +2245,7 @@ def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
 
         parser = argparse.ArgumentParser(prog=self.prog, description='Borg - Deduplicated Backups',
                                          add_help=False)
-        parser.set_defaults(func=functools.partial(self.do_maincommand_help, parser))
+        parser.set_defaults(fallback2_func=functools.partial(self.do_maincommand_help, parser))
         parser.common_options = self.CommonOptions(define_common_options,
                                                    suffix_precedence=('_maincommand', '_midcommand', '_subcommand'))
         parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__,
@@ -3688,8 +3700,7 @@ def parse_args(self, args=None):
         parser = self.build_parser()
         args = parser.parse_args(args or ['-h'])
         parser.common_options.resolve(args)
-        # This works around http://bugs.python.org/issue9351
-        func = getattr(args, 'func', None) or getattr(args, 'fallback_func')
+        func = get_func(args)
         if func == self.do_create and not args.paths:
             # need at least 1 path but args.paths may also be populated from patterns
             parser.error('Need at least one PATH argument.')
@@ -3725,8 +3736,7 @@ def _setup_topic_debugging(self, args):
     def run(self, args):
         os.umask(args.umask)  # early, before opening files
         self.lock_wait = args.lock_wait
-        # This works around http://bugs.python.org/issue9351
-        func = getattr(args, 'func', None) or getattr(args, 'fallback_func')
+        func = get_func(args)
         # do not use loggers before this!
         is_serve = func == self.do_serve
         setup_logging(level=args.log_level, is_serve=is_serve, json=args.log_json)


Test patch: