1# coding=utf-8
2"""
3Exports data from optparse or argparse based manage.py commands and reports it to _xml.XmlDumper.
4This module encapsulates Django semi-public API knowledge, and not very stable because of it.
5Optional env var ``_PYCHARM_DJANGO_DEFAULT_TIMEOUT`` sets timeout (in seconds) to wait for each command to be
6fetched.
7"""
8import sys
9import threading
10from _jb_utils import VersionAgnosticUtils
11from django.core.management import ManagementUtility, get_commands
12from django_manage_commands_provider._parser import _optparse, _argparse
13import os
14
15__author__ = 'Ilya.Kazakevich'
16
17
18class _Fetcher(threading.Thread):
19    def __init__(self, utility, command_name):
20        super(_Fetcher, self).__init__()
21        self.result = None
22        self.__utility = utility
23        self.__command_name = command_name
24        self.command_lead_to_exception = False
25
26    def run(self):
27        try:
28            self.result = self.__utility.fetch_command(self.__command_name)
29        except Exception as e:
30            sys.stderr.write("Error fetching command '{0}': {1}\n".format(self.__command_name, e))
31            self.command_lead_to_exception = True
32
33
34def report_data(dumper, commands_to_skip):
35    """
36    Fetches data from management commands and reports it to dumper.
37
38    :type dumper _xml.XmlDumper
39    :type commands_to_skip list
40    :param commands_to_skip list of commands to skip
41    :param dumper: destination to report
42    """
43    utility = ManagementUtility()
44    for command_name in get_commands().keys():
45
46        if command_name in commands_to_skip:
47            sys.stderr.write("Skipping command '{0}' due to config\n".format(command_name))
48            continue
49
50        fetcher = _Fetcher(utility, command_name)
51        fetcher.daemon = True
52        fetcher.start()
53        fetcher.join(int(os.getenv("_PYCHARM_DJANGO_DEFAULT_TIMEOUT", "2")))
54        command = fetcher.result
55        if not command:
56            if fetcher.command_lead_to_exception:
57                sys.stderr.write("Command '{0}' skipped\n".format(command_name))
58                continue
59            else:
60                sys.stderr.write(
61                    "Command '{0}' took too long and may freeze everything. Consider adding it to 'skip commands' list\n".format(
62                        command_name))
63                sys.exit(1)
64
65        use_argparse = False
66        # There is no optparse in 1.10
67        if _is_django_10():
68            use_argparse = True
69        else:
70            try:
71                use_argparse = command.use_argparse
72            except AttributeError:
73                pass
74
75        try:
76            parser = command.create_parser("", command_name)
77        except Exception as e:
78            sys.stderr.write("Error parsing command {0}: {1}\n".format(command_name, e))
79            continue
80
81        try:  # and there is no "usage()" since 1.10
82            usage = command.usage("")
83        except AttributeError:
84            usage = command.help
85
86        dumper.start_command(command_name=command_name,
87                             command_help_text=VersionAgnosticUtils().to_unicode(usage).replace("%prog",
88                                                                                                command_name))
89        module_to_use = _argparse if use_argparse else _optparse  # Choose appropriate module: argparse, optparse
90        module_to_use.process_command(dumper, command, parser)
91        dumper.close_command()
92
93
94def _is_django_10():
95    """
96
97    :return: is Django >= 1.10
98    """
99    try:
100        from distutils.version import StrictVersion, LooseVersion
101        import django
102        try:
103            return StrictVersion(django.get_version()) >= StrictVersion("1.10")
104        except ValueError:
105            return LooseVersion(django.get_version()) >= LooseVersion("1.10")
106    except (ImportError, AttributeError):
107        return False
108