| import keyword |
| |
| from pycharm_generator_utils.util_methods import * |
| from pycharm_generator_utils.constants import * |
| |
| |
| class emptylistdict(dict): |
| """defaultdict not available before 2.5; simplest reimplementation using [] as default""" |
| |
| def __getitem__(self, item): |
| if item in self: |
| return dict.__getitem__(self, item) |
| else: |
| it = [] |
| self.__setitem__(item, it) |
| return it |
| |
| class Buf(object): |
| """Buffers data in a list, can write to a file. Indentation is provided externally.""" |
| |
| def __init__(self, indenter): |
| self.data = [] |
| self.indenter = indenter |
| |
| def put(self, data): |
| if data: |
| self.data.append(ensureUnicode(data)) |
| |
| def out(self, indent, *what): |
| """Output the arguments, indenting as needed, and adding an eol""" |
| self.put(self.indenter.indent(indent)) |
| for item in what: |
| self.put(item) |
| self.put("\n") |
| |
| def flush_bytes(self, outfile): |
| for data in self.data: |
| outfile.write(data.encode(OUT_ENCODING, "replace")) |
| |
| def flush_str(self, outfile): |
| for data in self.data: |
| outfile.write(data) |
| |
| if version[0] < 3: |
| flush = flush_bytes |
| else: |
| flush = flush_str |
| |
| def isEmpty(self): |
| return len(self.data) == 0 |
| |
| |
| class ClassBuf(Buf): |
| def __init__(self, name, indenter): |
| super(ClassBuf, self).__init__(indenter) |
| self.name = name |
| |
| #noinspection PyUnresolvedReferences,PyBroadException |
| class ModuleRedeclarator(object): |
| def __init__(self, module, outfile, mod_filename, indent_size=4, doing_builtins=False): |
| """ |
| Create new instance. |
| @param module module to restore. |
| @param outfile output file, must be open and writable. |
| @param mod_filename filename of binary module (the .dll or .so) |
| @param indent_size amount of space characters per indent |
| """ |
| self.module = module |
| self.outfile = outfile # where we finally write |
| self.mod_filename = mod_filename |
| # we write things into buffers out-of-order |
| self.header_buf = Buf(self) |
| self.imports_buf = Buf(self) |
| self.functions_buf = Buf(self) |
| self.classes_buf = Buf(self) |
| self.classes_buffs = list() |
| self.footer_buf = Buf(self) |
| self.indent_size = indent_size |
| self._indent_step = " " * self.indent_size |
| self.split_modules = False |
| # |
| self.imported_modules = {"": the_builtins} # explicit module imports: {"name": module} |
| self.hidden_imports = {} # {'real_mod_name': 'alias'}; we alias names with "__" since we don't want them exported |
| # ^ used for things that we don't re-export but need to import, e.g. certain base classes in gnome. |
| self._defined = {} # stores True for every name defined so far, to break circular refs in values |
| self.doing_builtins = doing_builtins |
| self.ret_type_cache = {} |
| self.used_imports = emptylistdict() # qual_mod_name -> [imported_names,..]: actually used imported names |
| |
| def _initializeQApp4(self): |
| try: # QtGui should be imported _before_ QtCore package. |
| # This is done for the QWidget references from QtCore (such as QSignalMapper). Known bug in PyQt 4.7+ |
| # Causes "TypeError: C++ type 'QWidget*' is not supported as a native Qt signal type" |
| import PyQt4.QtGui |
| except ImportError: |
| pass |
| |
| # manually instantiate and keep reference to singleton QCoreApplication (we don't want it to be deleted during the introspection) |
| # use QCoreApplication instead of QApplication to avoid blinking app in Dock on Mac OS |
| try: |
| from PyQt4.QtCore import QCoreApplication |
| self.app = QCoreApplication([]) |
| return |
| except ImportError: |
| pass |
| |
| def _initializeQApp5(self): |
| try: |
| from PyQt5.QtCore import QCoreApplication |
| self.app = QCoreApplication([]) |
| return |
| except ImportError: |
| pass |
| |
| def indent(self, level): |
| """Return indentation whitespace for given level.""" |
| return self._indent_step * level |
| |
| def flush(self): |
| init = None |
| try: |
| if self.split_modules: |
| mod_path = module_to_package_name(self.outfile) |
| |
| fname = build_output_name(mod_path, "__init__") |
| init = fopen(fname, "w") |
| for buf in (self.header_buf, self.imports_buf, self.functions_buf, self.classes_buf): |
| buf.flush(init) |
| |
| data = "" |
| for buf in self.classes_buffs: |
| fname = build_output_name(mod_path, buf.name) |
| dummy = fopen(fname, "w") |
| self.header_buf.flush(dummy) |
| self.imports_buf.flush(dummy) |
| buf.flush(dummy) |
| data += self.create_local_import(buf.name) |
| dummy.close() |
| |
| init.write(data) |
| self.footer_buf.flush(init) |
| else: |
| init = fopen(self.outfile, "w") |
| for buf in (self.header_buf, self.imports_buf, self.functions_buf, self.classes_buf): |
| buf.flush(init) |
| |
| for buf in self.classes_buffs: |
| buf.flush(init) |
| |
| self.footer_buf.flush(init) |
| |
| finally: |
| if init is not None and not init.closed: |
| init.close() |
| |
| # Some builtin classes effectively change __init__ signature without overriding it. |
| # This callable serves as a placeholder to be replaced via REDEFINED_BUILTIN_SIGS |
| def fake_builtin_init(self): |
| pass # just a callable, sig doesn't matter |
| |
| fake_builtin_init.__doc__ = object.__init__.__doc__ # this forces class's doc to be used instead |
| |
| def create_local_import(self, name): |
| if len(name.split(".")) > 1: return "" |
| data = "from " |
| if version[0] >= 3: |
| data += "." |
| data += name + " import " + name + "\n" |
| return data |
| |
| def find_imported_name(self, item): |
| """ |
| Finds out how the item is represented in imported modules. |
| @param item what to check |
| @return qualified name (like "sys.stdin") or None |
| """ |
| # TODO: return a pair, not a glued string |
| if not isinstance(item, SIMPLEST_TYPES): |
| for mname in self.imported_modules: |
| m = self.imported_modules[mname] |
| for inner_name in m.__dict__: |
| suspect = getattr(m, inner_name) |
| if suspect is item: |
| if mname: |
| mname += "." |
| elif self.module is the_builtins: # don't short-circuit builtins |
| return None |
| return mname + inner_name |
| return None |
| |
| _initializers = ( |
| (dict, "{}"), |
| (tuple, "()"), |
| (list, "[]"), |
| ) |
| |
| def invent_initializer(self, a_type): |
| """ |
| Returns an innocuous initializer expression for a_type, or "None" |
| """ |
| for initializer_type, r in self._initializers: |
| if initializer_type == a_type: |
| return r |
| # NOTE: here we could handle things like defaultdict, sets, etc if we wanted |
| return "None" |
| |
| |
| def fmt_value(self, out, p_value, indent, prefix="", postfix="", as_name=None, seen_values=None): |
| """ |
| Formats and outputs value (it occupies an entire line or several lines). |
| @param out function that does output (a Buf.out) |
| @param p_value the value. |
| @param indent indent level. |
| @param prefix text to print before the value |
| @param postfix text to print after the value |
| @param as_name hints which name are we trying to print; helps with circular refs. |
| @param seen_values a list of keys we've seen if we're processing a dict |
| """ |
| SELF_VALUE = "<value is a self-reference, replaced by this string>" |
| ERR_VALUE = "<failed to retrieve the value>" |
| if isinstance(p_value, SIMPLEST_TYPES): |
| out(indent, prefix, reliable_repr(p_value), postfix) |
| else: |
| if sys.platform == "cli": |
| imported_name = None |
| else: |
| imported_name = self.find_imported_name(p_value) |
| if imported_name: |
| out(indent, prefix, imported_name, postfix) |
| # TODO: kind of self.used_imports[imported_name].append(p_value) but split imported_name |
| # else we could potentially return smth we did not otherwise import. but not likely. |
| else: |
| if isinstance(p_value, (list, tuple)): |
| if not seen_values: |
| seen_values = [p_value] |
| if len(p_value) == 0: |
| out(indent, prefix, repr(p_value), postfix) |
| else: |
| if isinstance(p_value, list): |
| lpar, rpar = "[", "]" |
| else: |
| lpar, rpar = "(", ")" |
| out(indent, prefix, lpar) |
| for value in p_value: |
| if value in seen_values: |
| value = SELF_VALUE |
| elif not isinstance(value, SIMPLEST_TYPES): |
| seen_values.append(value) |
| self.fmt_value(out, value, indent + 1, postfix=",", seen_values=seen_values) |
| out(indent, rpar, postfix) |
| elif isinstance(p_value, dict): |
| if len(p_value) == 0: |
| out(indent, prefix, repr(p_value), postfix) |
| else: |
| if not seen_values: |
| seen_values = [p_value] |
| out(indent, prefix, "{") |
| keys = list(p_value.keys()) |
| try: |
| keys.sort() |
| except TypeError: |
| pass # unsortable keys happen, e,g, in py3k _ctypes |
| for k in keys: |
| value = p_value[k] |
| |
| try: |
| is_seen = value in seen_values |
| except: |
| is_seen = False |
| value = ERR_VALUE |
| |
| if is_seen: |
| value = SELF_VALUE |
| elif not isinstance(value, SIMPLEST_TYPES): |
| seen_values.append(value) |
| if isinstance(k, SIMPLEST_TYPES): |
| self.fmt_value(out, value, indent + 1, prefix=repr(k) + ": ", postfix=",", |
| seen_values=seen_values) |
| else: |
| # both key and value need fancy formatting |
| self.fmt_value(out, k, indent + 1, postfix=": ", seen_values=seen_values) |
| self.fmt_value(out, value, indent + 2, seen_values=seen_values) |
| out(indent + 1, ",") |
| out(indent, "}", postfix) |
| else: # something else, maybe representable |
| # look up this value in the module. |
| if sys.platform == "cli": |
| out(indent, prefix, "None", postfix) |
| return |
| found_name = "" |
| for inner_name in self.module.__dict__: |
| if self.module.__dict__[inner_name] is p_value: |
| found_name = inner_name |
| break |
| if self._defined.get(found_name, False): |
| out(indent, prefix, found_name, postfix) |
| else: |
| # a forward / circular declaration happens |
| notice = "" |
| try: |
| representation = repr(p_value) |
| except Exception: |
| import traceback |
| traceback.print_exc(file=sys.stderr) |
| return |
| real_value = cleanup(representation) |
| if found_name: |
| if found_name == as_name: |
| notice = " # (!) real value is %r" % real_value |
| real_value = "None" |
| else: |
| notice = " # (!) forward: %s, real value is %r" % (found_name, real_value) |
| if SANE_REPR_RE.match(real_value): |
| out(indent, prefix, real_value, postfix, notice) |
| else: |
| if not found_name: |
| notice = " # (!) real value is %r" % real_value |
| out(indent, prefix, "None", postfix, notice) |
| |
| def get_ret_type(self, attr): |
| """ |
| Returns a return type string as given by T_RETURN in tokens, or None |
| """ |
| if attr: |
| ret_type = RET_TYPE.get(attr, None) |
| if ret_type: |
| return ret_type |
| thing = getattr(self.module, attr, None) |
| if thing: |
| if not isinstance(thing, type) and is_callable(thing): # a function |
| return None # TODO: maybe divinate a return type; see pygame.mixer.Channel |
| return attr |
| # adds no noticeable slowdown, I did measure. dch. |
| for im_name, im_module in self.imported_modules.items(): |
| cache_key = (im_name, attr) |
| cached = self.ret_type_cache.get(cache_key, None) |
| if cached: |
| return cached |
| ret_type = getattr(im_module, attr, None) |
| if ret_type: |
| if isinstance(ret_type, type): |
| # detect a constructor |
| constr_args = detect_constructor(ret_type) |
| if constr_args is None: |
| constr_args = "*(), **{}" # a silly catch-all constructor |
| reference = "%s(%s)" % (attr, constr_args) |
| elif is_callable(ret_type): # a function, classes are ruled out above |
| return None |
| else: |
| reference = attr |
| if im_name: |
| result = "%s.%s" % (im_name, reference) |
| else: # built-in |
| result = reference |
| self.ret_type_cache[cache_key] = result |
| return result |
| # TODO: handle things like "[a, b,..] and (foo,..)" |
| return None |
| |
| |
| SIG_DOC_NOTE = "restored from __doc__" |
| SIG_DOC_UNRELIABLY = "NOTE: unreliably restored from __doc__ " |
| |
| def restore_by_docstring(self, signature_string, class_name, deco=None, ret_hint=None): |
| """ |
| @param signature_string: parameter list extracted from the doc string. |
| @param class_name: name of the containing class, or None |
| @param deco: decorator to use |
| @param ret_hint: return type hint, if available |
| @return (reconstructed_spec, return_type, note) or (None, _, _) if failed. |
| """ |
| action("restoring func %r of class %r", signature_string, class_name) |
| # parse |
| parsing_failed = False |
| ret_type = None |
| try: |
| # strict parsing |
| tokens = paramSeqAndRest.parseString(signature_string, True) |
| ret_name = None |
| if tokens: |
| ret_t = tokens[-1] |
| if ret_t[0] is T_RETURN: |
| ret_name = ret_t[1] |
| ret_type = self.get_ret_type(ret_name) or self.get_ret_type(ret_hint) |
| except ParseException: |
| # it did not parse completely; scavenge what we can |
| parsing_failed = True |
| tokens = [] |
| try: |
| # most unrestrictive parsing |
| tokens = paramSeq.parseString(signature_string, False) |
| except ParseException: |
| pass |
| # |
| seq = transform_seq(tokens) |
| |
| # add safe defaults for unparsed |
| if parsing_failed: |
| doc_node = self.SIG_DOC_UNRELIABLY |
| starred = None |
| double_starred = None |
| for one in seq: |
| if type(one) is str: |
| if one.startswith("**"): |
| double_starred = one |
| elif one.startswith("*"): |
| starred = one |
| if not starred: |
| seq.append("*args") |
| if not double_starred: |
| seq.append("**kwargs") |
| else: |
| doc_node = self.SIG_DOC_NOTE |
| |
| # add 'self' if needed YYY |
| if class_name and (not seq or seq[0] != 'self'): |
| first_param = propose_first_param(deco) |
| if first_param: |
| seq.insert(0, first_param) |
| seq = make_names_unique(seq) |
| return (seq, ret_type, doc_node) |
| |
| def parse_func_doc(self, func_doc, func_id, func_name, class_name, deco=None, sip_generated=False): |
| """ |
| @param func_doc: __doc__ of the function. |
| @param func_id: name to look for as identifier of the function in docstring |
| @param func_name: name of the function. |
| @param class_name: name of the containing class, or None |
| @param deco: decorator to use |
| @return (reconstructed_spec, return_literal, note) or (None, _, _) if failed. |
| """ |
| if sip_generated: |
| overloads = [] |
| for part in func_doc.split('\n'): |
| signature = func_id + '(' |
| i = part.find(signature) |
| if i >= 0: |
| overloads.append(part[i + len(signature):]) |
| if len(overloads) > 1: |
| docstring_results = [self.restore_by_docstring(overload, class_name, deco) for overload in overloads] |
| ret_types = [] |
| for result in docstring_results: |
| rt = result[1] |
| if rt and rt not in ret_types: |
| ret_types.append(rt) |
| if ret_types: |
| ret_literal = " or ".join(ret_types) |
| else: |
| ret_literal = None |
| param_lists = [result[0] for result in docstring_results] |
| spec = build_signature(func_name, restore_parameters_for_overloads(param_lists)) |
| return (spec, ret_literal, "restored from __doc__ with multiple overloads") |
| |
| # find the first thing to look like a definition |
| prefix_re = re.compile("\s*(?:(\w+)[ \\t]+)?" + func_id + "\s*\(") # "foo(..." or "int foo(..." |
| match = prefix_re.search(func_doc) # Note: this and previous line may consume up to 35% of time |
| # parse the part that looks right |
| if match: |
| ret_hint = match.group(1) |
| params, ret_literal, doc_note = self.restore_by_docstring(func_doc[match.end():], class_name, deco, ret_hint) |
| spec = func_name + flatten(params) |
| return (spec, ret_literal, doc_note) |
| else: |
| return (None, None, None) |
| |
| |
| def is_predefined_builtin(self, module_name, class_name, func_name): |
| return self.doing_builtins and module_name == BUILTIN_MOD_NAME and ( |
| class_name, func_name) in PREDEFINED_BUILTIN_SIGS |
| |
| |
| def redo_function(self, out, p_func, p_name, indent, p_class=None, p_modname=None, classname=None, seen=None): |
| """ |
| Restore function argument list as best we can. |
| @param out output function of a Buf |
| @param p_func function or method object |
| @param p_name function name as known to owner |
| @param indent indentation level |
| @param p_class the class that contains this function as a method |
| @param p_modname module name |
| @param seen {id(func): name} map of functions already seen in the same namespace; |
| id() because *some* functions are unhashable (eg _elementtree.Comment in py2.7) |
| """ |
| action("redoing func %r of class %r", p_name, p_class) |
| if seen is not None: |
| other_func = seen.get(id(p_func), None) |
| if other_func and getattr(other_func, "__doc__", None) is getattr(p_func, "__doc__", None): |
| # _bisect.bisect == _bisect.bisect_right in py31, but docs differ |
| out(indent, p_name, " = ", seen[id(p_func)]) |
| out(indent, "") |
| return |
| else: |
| seen[id(p_func)] = p_name |
| # real work |
| if classname is None: |
| classname = p_class and p_class.__name__ or None |
| if p_class and hasattr(p_class, '__mro__'): |
| sip_generated = [base_t for base_t in p_class.__mro__ if 'sip.simplewrapper' in str(base_t)] |
| else: |
| sip_generated = False |
| deco = None |
| deco_comment = "" |
| mod_class_method_tuple = (p_modname, classname, p_name) |
| ret_literal = None |
| is_init = False |
| # any decorators? |
| action("redoing decos of func %r of class %r", p_name, p_class) |
| if self.doing_builtins and p_modname == BUILTIN_MOD_NAME: |
| deco = KNOWN_DECORATORS.get((classname, p_name), None) |
| if deco: |
| deco_comment = " # known case" |
| elif p_class and p_name in p_class.__dict__: |
| # detect native methods declared with METH_CLASS flag |
| descriptor = p_class.__dict__[p_name] |
| if p_name != "__new__" and type(descriptor).__name__.startswith('classmethod'): |
| # 'classmethod_descriptor' in Python 2.x and 3.x, 'classmethod' in Jython |
| deco = "classmethod" |
| elif type(p_func).__name__.startswith('staticmethod'): |
| deco = "staticmethod" |
| if p_name == "__new__": |
| deco = "staticmethod" |
| deco_comment = " # known case of __new__" |
| |
| action("redoing innards of func %r of class %r", p_name, p_class) |
| if deco and HAS_DECORATORS: |
| out(indent, "@", deco, deco_comment) |
| if inspect and inspect.isfunction(p_func): |
| out(indent, "def ", p_name, restore_by_inspect(p_func), ": # reliably restored by inspect", ) |
| out_doc_attr(out, p_func, indent + 1, p_class) |
| elif self.is_predefined_builtin(*mod_class_method_tuple): |
| spec, sig_note = restore_predefined_builtin(classname, p_name) |
| out(indent, "def ", spec, ": # ", sig_note) |
| out_doc_attr(out, p_func, indent + 1, p_class) |
| elif sys.platform == 'cli' and is_clr_type(p_class): |
| is_static, spec, sig_note = restore_clr(p_name, p_class) |
| if is_static: |
| out(indent, "@staticmethod") |
| if not spec: return |
| if sig_note: |
| out(indent, "def ", spec, ": #", sig_note) |
| else: |
| out(indent, "def ", spec, ":") |
| if not p_name in ['__gt__', '__ge__', '__lt__', '__le__', '__ne__', '__reduce_ex__', '__str__']: |
| out_doc_attr(out, p_func, indent + 1, p_class) |
| elif mod_class_method_tuple in PREDEFINED_MOD_CLASS_SIGS: |
| sig, ret_literal = PREDEFINED_MOD_CLASS_SIGS[mod_class_method_tuple] |
| if classname: |
| ofwhat = "%s.%s.%s" % mod_class_method_tuple |
| else: |
| ofwhat = "%s.%s" % (p_modname, p_name) |
| out(indent, "def ", p_name, sig, ": # known case of ", ofwhat) |
| out_doc_attr(out, p_func, indent + 1, p_class) |
| else: |
| # __doc__ is our best source of arglist |
| sig_note = "real signature unknown" |
| spec = "" |
| is_init = (p_name == "__init__" and p_class is not None) |
| funcdoc = None |
| if is_init and hasattr(p_class, "__doc__"): |
| if hasattr(p_func, "__doc__"): |
| funcdoc = p_func.__doc__ |
| if funcdoc == object.__init__.__doc__: |
| funcdoc = p_class.__doc__ |
| elif hasattr(p_func, "__doc__"): |
| funcdoc = p_func.__doc__ |
| sig_restored = False |
| action("parsing doc of func %r of class %r", p_name, p_class) |
| if isinstance(funcdoc, STR_TYPES): |
| (spec, ret_literal, more_notes) = self.parse_func_doc(funcdoc, p_name, p_name, classname, deco, |
| sip_generated) |
| if spec is None and p_name == '__init__' and classname: |
| (spec, ret_literal, more_notes) = self.parse_func_doc(funcdoc, classname, p_name, classname, deco, |
| sip_generated) |
| sig_restored = spec is not None |
| if more_notes: |
| if sig_note: |
| sig_note += "; " |
| sig_note += more_notes |
| if not sig_restored: |
| # use an allow-all declaration |
| decl = [] |
| if p_class: |
| first_param = propose_first_param(deco) |
| if first_param: |
| decl.append(first_param) |
| decl.append("*args") |
| decl.append("**kwargs") |
| spec = p_name + "(" + ", ".join(decl) + ")" |
| out(indent, "def ", spec, ": # ", sig_note) |
| # to reduce size of stubs, don't output same docstring twice for class and its __init__ method |
| if not is_init or funcdoc != p_class.__doc__: |
| out_docstring(out, funcdoc, indent + 1) |
| # body |
| if ret_literal and not is_init: |
| out(indent + 1, "return ", ret_literal) |
| else: |
| out(indent + 1, "pass") |
| if deco and not HAS_DECORATORS: |
| out(indent, p_name, " = ", deco, "(", p_name, ")", deco_comment) |
| out(0, "") # empty line after each item |
| |
| |
| def redo_class(self, out, p_class, p_name, indent, p_modname=None, seen=None, inspect_dir=False): |
| """ |
| Restores a class definition. |
| @param out output function of a relevant buf |
| @param p_class the class object |
| @param p_name class name as known to owner |
| @param indent indentation level |
| @param p_modname name of module |
| @param seen {class: name} map of classes already seen in the same namespace |
| """ |
| action("redoing class %r of module %r", p_name, p_modname) |
| if seen is not None: |
| if p_class in seen: |
| out(indent, p_name, " = ", seen[p_class]) |
| out(indent, "") |
| return |
| else: |
| seen[p_class] = p_name |
| bases = get_bases(p_class) |
| base_def = "" |
| skipped_bases = [] |
| if bases: |
| skip_qualifiers = [p_modname, BUILTIN_MOD_NAME, 'exceptions'] |
| skip_qualifiers.extend(KNOWN_FAKE_REEXPORTERS.get(p_modname, ())) |
| bases_list = [] # what we'll render in the class decl |
| for base in bases: |
| if [1 for (cls, mdl) in KNOWN_FAKE_BASES if cls == base and mdl != self.module]: |
| # our base is a wrapper and our module is not its defining module |
| skipped_bases.append(str(base)) |
| continue |
| # somehow import every base class |
| base_name = base.__name__ |
| qual_module_name = qualifier_of(base, skip_qualifiers) |
| got_existing_import = False |
| if qual_module_name: |
| if qual_module_name in self.used_imports: |
| import_list = self.used_imports[qual_module_name] |
| if base in import_list: |
| bases_list.append(base_name) # unqualified: already set to import |
| got_existing_import = True |
| if not got_existing_import: |
| mangled_qualifier = "__" + qual_module_name.replace('.', '_') # foo.bar -> __foo_bar |
| bases_list.append(mangled_qualifier + "." + base_name) |
| self.hidden_imports[qual_module_name] = mangled_qualifier |
| else: |
| bases_list.append(base_name) |
| base_def = "(" + ", ".join(bases_list) + ")" |
| |
| for base in bases_list: |
| local_import = self.create_local_import(base) |
| if local_import: |
| out(indent, local_import) |
| out(indent, "class ", p_name, base_def, ":", |
| skipped_bases and " # skipped bases: " + ", ".join(skipped_bases) or "") |
| out_doc_attr(out, p_class, indent + 1) |
| # inner parts |
| methods = {} |
| properties = {} |
| others = {} |
| we_are_the_base_class = p_modname == BUILTIN_MOD_NAME and p_name == "object" |
| field_source = {} |
| try: |
| if hasattr(p_class, "__dict__") and not inspect_dir: |
| field_source = p_class.__dict__ |
| field_keys = field_source.keys() # Jython 2.5.1 _codecs fail here |
| else: |
| field_keys = dir(p_class) # this includes unwanted inherited methods, but no dict + inheritance is rare |
| except: |
| field_keys = () |
| for item_name in field_keys: |
| if item_name in ("__doc__", "__module__"): |
| if we_are_the_base_class: |
| item = "" # must be declared in base types |
| else: |
| continue # in all other cases must be skipped |
| elif keyword.iskeyword(item_name): # for example, PyQt4 contains definitions of methods named 'exec' |
| continue |
| else: |
| try: |
| item = getattr(p_class, item_name) # let getters do the magic |
| except AttributeError: |
| item = field_source[item_name] # have it raw |
| except Exception: |
| continue |
| if is_callable(item) and not isinstance(item, type): |
| methods[item_name] = item |
| elif is_property(item): |
| properties[item_name] = item |
| else: |
| others[item_name] = item |
| # |
| if we_are_the_base_class: |
| others["__dict__"] = {} # force-feed it, for __dict__ does not contain a reference to itself :) |
| # add fake __init__s to have the right sig |
| if p_class in FAKE_BUILTIN_INITS: |
| methods["__init__"] = self.fake_builtin_init |
| note("Faking init of %s", p_name) |
| elif '__init__' not in methods: |
| init_method = getattr(p_class, '__init__', None) |
| if init_method: |
| methods['__init__'] = init_method |
| |
| # |
| seen_funcs = {} |
| for item_name in sorted_no_case(methods.keys()): |
| item = methods[item_name] |
| try: |
| self.redo_function(out, item, item_name, indent + 1, p_class, p_modname, classname=p_name, seen=seen_funcs) |
| except: |
| handle_error_func(item_name, out) |
| # |
| known_props = KNOWN_PROPS.get(p_modname, {}) |
| a_setter = "lambda self, v: None" |
| a_deleter = "lambda self: None" |
| for item_name in sorted_no_case(properties.keys()): |
| item = properties[item_name] |
| prop_docstring = getattr(item, '__doc__', None) |
| prop_key = (p_name, item_name) |
| if prop_key in known_props: |
| prop_descr = known_props.get(prop_key, None) |
| if prop_descr is None: |
| continue # explicitly omitted |
| acc_line, getter_and_type = prop_descr |
| if getter_and_type: |
| getter, prop_type = getter_and_type |
| else: |
| getter, prop_type = None, None |
| out(indent + 1, item_name, |
| " = property(", format_accessors(acc_line, getter, a_setter, a_deleter), ")" |
| ) |
| if prop_type: |
| if prop_docstring: |
| out(indent + 1, '"""', prop_docstring) |
| out(0, "") |
| out(indent + 1, ':type: ', prop_type) |
| out(indent + 1, '"""') |
| else: |
| out(indent + 1, '""":type: ', prop_type, '"""') |
| out(0, "") |
| else: |
| out(indent + 1, item_name, " = property(lambda self: object(), lambda self, v: None, lambda self: None) # default") |
| if prop_docstring: |
| out(indent + 1, '"""', prop_docstring, '"""') |
| out(0, "") |
| if properties: |
| out(0, "") # empty line after the block |
| # |
| for item_name in sorted_no_case(others.keys()): |
| item = others[item_name] |
| self.fmt_value(out, item, indent + 1, prefix=item_name + " = ") |
| if p_name == "object": |
| out(indent + 1, "__module__ = ''") |
| if others: |
| out(0, "") # empty line after the block |
| # |
| if not methods and not properties and not others: |
| out(indent + 1, "pass") |
| |
| |
| |
| def redo_simple_header(self, p_name): |
| """Puts boilerplate code on the top""" |
| out = self.header_buf.out # 1st class methods rule :) |
| out(0, "# encoding: %s" % OUT_ENCODING) # line 1 |
| # NOTE: maybe encoding should be selectable |
| if hasattr(self.module, "__name__"): |
| self_name = self.module.__name__ |
| if self_name != p_name: |
| mod_name = " calls itself " + self_name |
| else: |
| mod_name = "" |
| else: |
| mod_name = " does not know its name" |
| out(0, "# module ", p_name, mod_name) # line 2 |
| |
| BUILT_IN_HEADER = "(built-in)" |
| if self.mod_filename: |
| filename = self.mod_filename |
| elif p_name in sys.builtin_module_names: |
| filename = BUILT_IN_HEADER |
| else: |
| filename = getattr(self.module, "__file__", BUILT_IN_HEADER) |
| |
| out(0, "# from %s" % filename) # line 3 |
| out(0, "# by generator %s" % VERSION) # line 4 |
| if p_name == BUILTIN_MOD_NAME and version[0] == 2 and version[1] >= 6: |
| out(0, "from __future__ import print_function") |
| out_doc_attr(out, self.module, 0) |
| |
| |
| def redo_imports(self): |
| module_type = type(sys) |
| for item_name in self.module.__dict__.keys(): |
| try: |
| item = self.module.__dict__[item_name] |
| except: |
| continue |
| if type(item) is module_type: # not isinstance, py2.7 + PyQt4.QtCore on windows have a bug here |
| self.imported_modules[item_name] = item |
| self.add_import_header_if_needed() |
| ref_notice = getattr(item, "__file__", str(item)) |
| if hasattr(item, "__name__"): |
| self.imports_buf.out(0, "import ", item.__name__, " as ", item_name, " # ", ref_notice) |
| else: |
| self.imports_buf.out(0, item_name, " = None # ??? name unknown; ", ref_notice) |
| |
| def add_import_header_if_needed(self): |
| if self.imports_buf.isEmpty(): |
| self.imports_buf.out(0, "") |
| self.imports_buf.out(0, "# imports") |
| |
| |
| def redo(self, p_name, inspect_dir): |
| """ |
| Restores module declarations. |
| Intended for built-in modules and thus does not handle import statements. |
| @param p_name name of module |
| """ |
| action("redoing header of module %r %r", p_name, str(self.module)) |
| |
| if "pyqt4" in p_name.lower(): # qt4 specific patch |
| self._initializeQApp4() |
| elif "pyqt5" in p_name.lower(): # qt5 specific patch |
| self._initializeQApp5() |
| |
| self.redo_simple_header(p_name) |
| |
| # find whatever other self.imported_modules the module knows; effectively these are imports |
| action("redoing imports of module %r %r", p_name, str(self.module)) |
| try: |
| self.redo_imports() |
| except: |
| pass |
| |
| action("redoing innards of module %r %r", p_name, str(self.module)) |
| |
| module_type = type(sys) |
| # group what we have into buckets |
| vars_simple = {} |
| vars_complex = {} |
| funcs = {} |
| classes = {} |
| module_dict = self.module.__dict__ |
| if inspect_dir: |
| module_dict = dir(self.module) |
| for item_name in module_dict: |
| note("looking at %s", item_name) |
| if item_name in ( |
| "__dict__", "__doc__", "__module__", "__file__", "__name__", "__builtins__", "__package__"): |
| continue # handled otherwise |
| try: |
| item = getattr(self.module, item_name) # let getters do the magic |
| except AttributeError: |
| if not item_name in self.module.__dict__: continue |
| item = self.module.__dict__[item_name] # have it raw |
| # check if it has percolated from an imported module |
| except NotImplementedError: |
| if not item_name in self.module.__dict__: continue |
| item = self.module.__dict__[item_name] # have it raw |
| |
| # unless we're adamantly positive that the name was imported, we assume it is defined here |
| mod_name = None # module from which p_name might have been imported |
| # IronPython has non-trivial reexports in System module, but not in others: |
| skip_modname = sys.platform == "cli" and p_name != "System" |
| surely_not_imported_mods = KNOWN_FAKE_REEXPORTERS.get(p_name, ()) |
| ## can't figure weirdness in some modules, assume no reexports: |
| #skip_modname = skip_modname or p_name in self.KNOWN_FAKE_REEXPORTERS |
| if not skip_modname: |
| try: |
| mod_name = getattr(item, '__module__', None) |
| except: |
| pass |
| # we assume that module foo.bar never imports foo; foo may import foo.bar. (see pygame and pygame.rect) |
| maybe_import_mod_name = mod_name or "" |
| import_is_from_top = len(p_name) > len(maybe_import_mod_name) and p_name.startswith(maybe_import_mod_name) |
| note("mod_name = %s, prospective = %s, from top = %s", mod_name, maybe_import_mod_name, import_is_from_top) |
| want_to_import = False |
| if (mod_name |
| and mod_name != BUILTIN_MOD_NAME |
| and mod_name != p_name |
| and mod_name not in surely_not_imported_mods |
| and not import_is_from_top |
| ): |
| # import looks valid, but maybe it's a .py file? we're certain not to import from .py |
| # e.g. this rules out _collections import collections and builtins import site. |
| try: |
| imported = __import__(mod_name) # ok to repeat, Python caches for us |
| if imported: |
| qualifiers = mod_name.split(".")[1:] |
| for qual in qualifiers: |
| imported = getattr(imported, qual, None) |
| if not imported: |
| break |
| imported_path = (getattr(imported, '__file__', False) or "").lower() |
| want_to_import = not (imported_path.endswith('.py') or imported_path.endswith('.pyc')) |
| note("path of %r is %r, want? %s", mod_name, imported_path, want_to_import) |
| except ImportError: |
| want_to_import = False |
| # NOTE: if we fail to import, we define 'imported' names here lest we lose them at all |
| if want_to_import: |
| import_list = self.used_imports[mod_name] |
| if item_name not in import_list: |
| import_list.append(item_name) |
| if not want_to_import: |
| if isinstance(item, type) or type(item).__name__ == 'classobj': |
| classes[item_name] = item |
| elif is_callable(item): # some classes are callable, check them before functions |
| funcs[item_name] = item |
| elif isinstance(item, module_type): |
| continue # self.imported_modules handled above already |
| else: |
| if isinstance(item, SIMPLEST_TYPES): |
| vars_simple[item_name] = item |
| else: |
| vars_complex[item_name] = item |
| |
| # sort and output every bucket |
| action("outputting innards of module %r %r", p_name, str(self.module)) |
| # |
| omitted_names = OMIT_NAME_IN_MODULE.get(p_name, []) |
| if vars_simple: |
| out = self.functions_buf.out |
| prefix = "" # try to group variables by common prefix |
| PREFIX_LEN = 2 # default prefix length if we can't guess better |
| out(0, "# Variables with simple values") |
| for item_name in sorted_no_case(vars_simple.keys()): |
| if item_name in omitted_names: |
| out(0, "# definition of " + item_name + " omitted") |
| continue |
| item = vars_simple[item_name] |
| # track the prefix |
| if len(item_name) >= PREFIX_LEN: |
| prefix_pos = string.rfind(item_name, "_") # most prefixes end in an underscore |
| if prefix_pos < 1: |
| prefix_pos = PREFIX_LEN |
| beg = item_name[0:prefix_pos] |
| if prefix != beg: |
| out(0, "") # space out from other prefix |
| prefix = beg |
| else: |
| prefix = "" |
| # output |
| replacement = REPLACE_MODULE_VALUES.get((p_name, item_name), None) |
| if replacement is not None: |
| out(0, item_name, " = ", replacement, " # real value of type ", str(type(item)), " replaced") |
| elif is_skipped_in_module(p_name, item_name): |
| t_item = type(item) |
| out(0, item_name, " = ", self.invent_initializer(t_item), " # real value of type ", str(t_item), |
| " skipped") |
| else: |
| self.fmt_value(out, item, 0, prefix=item_name + " = ") |
| self._defined[item_name] = True |
| out(0, "") # empty line after vars |
| # |
| if funcs: |
| out = self.functions_buf.out |
| out(0, "# functions") |
| out(0, "") |
| seen_funcs = {} |
| for item_name in sorted_no_case(funcs.keys()): |
| if item_name in omitted_names: |
| out(0, "# definition of ", item_name, " omitted") |
| continue |
| item = funcs[item_name] |
| try: |
| self.redo_function(out, item, item_name, 0, p_modname=p_name, seen=seen_funcs) |
| except: |
| handle_error_func(item_name, out) |
| else: |
| self.functions_buf.out(0, "# no functions") |
| # |
| if classes: |
| self.classes_buf.out(0, "# classes") |
| self.classes_buf.out(0, "") |
| seen_classes = {} |
| # sort classes so that inheritance order is preserved |
| cls_list = [] # items are (class_name, mro_tuple) |
| for cls_name in sorted_no_case(classes.keys()): |
| cls = classes[cls_name] |
| ins_index = len(cls_list) |
| for i in range(ins_index): |
| maybe_child_bases = cls_list[i][1] |
| if cls in maybe_child_bases: |
| ins_index = i # we could not go farther than current ins_index |
| break # ...and need not go fartehr than first known child |
| cls_list.insert(ins_index, (cls_name, get_mro(cls))) |
| self.split_modules = self.mod_filename and len(cls_list) >= 30 |
| for item_name in [cls_item[0] for cls_item in cls_list]: |
| buf = ClassBuf(item_name, self) |
| self.classes_buffs.append(buf) |
| out = buf.out |
| if item_name in omitted_names: |
| out(0, "# definition of ", item_name, " omitted") |
| continue |
| item = classes[item_name] |
| self.redo_class(out, item, item_name, 0, p_modname=p_name, seen=seen_classes, inspect_dir=inspect_dir) |
| self._defined[item_name] = True |
| out(0, "") # empty line after each item |
| |
| if self.doing_builtins and p_name == BUILTIN_MOD_NAME and version[0] < 3: |
| # classobj still supported |
| txt = classobj_txt |
| self.classes_buf.out(0, txt) |
| |
| if self.doing_builtins and p_name == BUILTIN_MOD_NAME: |
| txt = create_generator() |
| self.classes_buf.out(0, txt) |
| txt = create_function() |
| self.classes_buf.out(0, txt) |
| txt = create_method() |
| self.classes_buf.out(0, txt) |
| |
| # Fake <type 'namedtuple'> |
| if version[0] >= 3 or (version[0] == 2 and version[1] >= 6): |
| namedtuple_text = create_named_tuple() |
| self.classes_buf.out(0, namedtuple_text) |
| |
| else: |
| self.classes_buf.out(0, "# no classes") |
| # |
| if vars_complex: |
| out = self.footer_buf.out |
| out(0, "# variables with complex values") |
| out(0, "") |
| for item_name in sorted_no_case(vars_complex.keys()): |
| if item_name in omitted_names: |
| out(0, "# definition of " + item_name + " omitted") |
| continue |
| item = vars_complex[item_name] |
| if str(type(item)) == "<type 'namespace#'>": |
| continue # this is an IronPython submodule, we mustn't generate a reference for it in the base module |
| replacement = REPLACE_MODULE_VALUES.get((p_name, item_name), None) |
| if replacement is not None: |
| out(0, item_name + " = " + replacement + " # real value of type " + str(type(item)) + " replaced") |
| elif is_skipped_in_module(p_name, item_name): |
| t_item = type(item) |
| out(0, item_name + " = " + self.invent_initializer(t_item) + " # real value of type " + str( |
| t_item) + " skipped") |
| else: |
| self.fmt_value(out, item, 0, prefix=item_name + " = ", as_name=item_name) |
| self._defined[item_name] = True |
| out(0, "") # empty line after each item |
| values_to_add = ADD_VALUE_IN_MODULE.get(p_name, None) |
| if values_to_add: |
| self.footer_buf.out(0, "# intermittent names") |
| for value in values_to_add: |
| self.footer_buf.out(0, value) |
| # imports: last, because previous parts could alter used_imports or hidden_imports |
| self.output_import_froms() |
| if self.imports_buf.isEmpty(): |
| self.imports_buf.out(0, "# no imports") |
| self.imports_buf.out(0, "") # empty line after imports |
| |
| def output_import_froms(self): |
| """Mention all imported names known within the module, wrapping as per PEP.""" |
| out = self.imports_buf.out |
| if self.used_imports: |
| self.add_import_header_if_needed() |
| for mod_name in sorted_no_case(self.used_imports.keys()): |
| import_names = self.used_imports[mod_name] |
| if import_names: |
| self._defined[mod_name] = True |
| right_pos = 0 # tracks width of list to fold it at right margin |
| import_heading = "from % s import (" % mod_name |
| right_pos += len(import_heading) |
| names_pack = [import_heading] |
| indent_level = 0 |
| import_names = list(import_names) |
| import_names.sort() |
| for n in import_names: |
| self._defined[n] = True |
| len_n = len(n) |
| if right_pos + len_n >= 78: |
| out(indent_level, *names_pack) |
| names_pack = [n, ", "] |
| if indent_level == 0: |
| indent_level = 1 # all but first line is indented |
| right_pos = self.indent_size + len_n + 2 |
| else: |
| names_pack.append(n) |
| names_pack.append(", ") |
| right_pos += (len_n + 2) |
| # last line is... |
| if indent_level == 0: # one line |
| names_pack[0] = names_pack[0][:-1] # cut off lpar |
| names_pack[-1] = "" # cut last comma |
| else: # last line of multiline |
| names_pack[-1] = ")" # last comma -> rpar |
| out(indent_level, *names_pack) |
| |
| out(0, "") # empty line after group |
| |
| if self.hidden_imports: |
| self.add_import_header_if_needed() |
| for mod_name in sorted_no_case(self.hidden_imports.keys()): |
| out(0, 'import ', mod_name, ' as ', self.hidden_imports[mod_name]) |
| out(0, "") # empty line after group |
| |
| |
| def module_to_package_name(module_name): |
| return re.sub(r"(.*)\.py$", r"\1", module_name) |