1# Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com 2# 3# Part of "Nuitka", an optimizing Python compiler that is compatible and 4# integrates with CPython, but also works on its own. 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18""" Module/Package nodes 19 20The top of the tree. Packages are also modules. Modules are what hold a program 21together and cross-module optimizations are the most difficult to tackle. 22""" 23 24import os 25 26from nuitka import Options, Variables 27from nuitka.containers.oset import OrderedSet 28from nuitka.importing.Importing import ( 29 findModule, 30 getModuleNameAndKindFromFilename, 31) 32from nuitka.importing.Recursion import decideRecursion, recurseTo 33from nuitka.ModuleRegistry import getModuleByName, getOwnerFromCodeName 34from nuitka.optimizations.TraceCollections import TraceCollectionModule 35from nuitka.PythonVersions import python_version 36from nuitka.SourceCodeReferences import fromFilename 37from nuitka.tree.SourceReading import readSourceCodeFromFilename 38from nuitka.utils.CStrings import encodePythonIdentifierToC 39from nuitka.utils.FileOperations import getFileContentByLine, relpath 40from nuitka.utils.ModuleNames import ModuleName 41 42from .Checkers import checkStatementsSequenceOrNone 43from .FutureSpecs import fromFlags 44from .IndicatorMixins import EntryPointMixin, MarkNeedsAnnotationsMixin 45from .LocalsScopes import getLocalsDictHandle 46from .NodeBases import ( 47 ChildrenHavingMixin, 48 ClosureGiverNodeMixin, 49 NodeBase, 50 extractKindAndArgsFromXML, 51 fromXML, 52) 53 54 55class PythonModuleBase(NodeBase): 56 # Base classes can be abstract, pylint: disable=abstract-method 57 58 __slots__ = ("module_name",) 59 60 def __init__(self, module_name, source_ref): 61 assert type(module_name) is ModuleName, module_name 62 63 NodeBase.__init__(self, source_ref=source_ref) 64 65 self.module_name = module_name 66 67 def getDetails(self): 68 return {"module_name": self.module_name} 69 70 def getFullName(self): 71 return self.module_name 72 73 @staticmethod 74 def isMainModule(): 75 return False 76 77 @staticmethod 78 def isTopModule(): 79 return False 80 81 def attemptRecursion(self): 82 # Make sure the package is recursed to if any 83 package_name = self.module_name.getPackageName() 84 if package_name is None: 85 return () 86 87 # Return the list of newly added modules. 88 89 package = getModuleByName(package_name) 90 91 if package_name is not None and package is None: 92 package_package, package_filename, finding = findModule( 93 importing=self, 94 module_name=package_name, 95 parent_package=None, 96 level=1, 97 warn=python_version < 0x300, 98 ) 99 100 # TODO: Temporary, if we can't find the package for Python3.3 that 101 # is semi-OK, maybe. 102 if python_version >= 0x300 and not package_filename: 103 return () 104 105 if package_name == "uniconvertor.app.modules": 106 return () 107 108 assert package_filename is not None, (package_name, finding) 109 110 _package_name, package_kind = getModuleNameAndKindFromFilename( 111 package_filename 112 ) 113 # assert _package_name == self.package_name, (package_filename, _package_name, self.package_name) 114 115 decision, _reason = decideRecursion( 116 module_filename=package_filename, 117 module_name=package_name, 118 module_kind=package_kind, 119 ) 120 121 if decision is not None: 122 package = recurseTo( 123 signal_change=self.trace_collection.signalChange 124 if hasattr(self, "trace_collection") 125 else None, 126 module_package=package_package, 127 module_filename=package_filename, 128 module_relpath=relpath(package_filename), 129 module_kind="py", 130 reason="Containing package of '%s'." % self.getFullName(), 131 ) 132 133 if package: 134 from nuitka.ModuleRegistry import addUsedModule 135 136 addUsedModule(package) 137 138 def getCodeName(self): 139 # Abstract method, pylint: disable=no-self-use 140 return None 141 142 def getCompileTimeFilename(self): 143 """The compile time filename for the module. 144 145 Returns: 146 Full path to module file at compile time. 147 Notes: 148 We are getting the absolute path here, since we do 149 not want to have to deal with resolving paths at 150 all. 151 152 """ 153 return os.path.abspath(self.source_ref.getFilename()) 154 155 def getCompileTimeDirectory(self): 156 """The compile time directory for the module. 157 158 Returns: 159 Full path to module directory at compile time. 160 Notes: 161 For packages, we let the package directory be 162 the result, otherwise the containing directory 163 is the result. 164 Notes: 165 Use this to find files nearby a module, mainly 166 in plugin code. 167 """ 168 result = self.getCompileTimeFilename() 169 if not os.path.isdir(result): 170 result = os.path.dirname(result) 171 return result 172 173 def getRunTimeFilename(self): 174 reference_mode = Options.getFileReferenceMode() 175 176 if reference_mode == "original": 177 return self.getCompileTimeFilename() 178 elif reference_mode == "frozen": 179 return "<frozen %s>" % self.getFullName() 180 else: 181 filename = self.getCompileTimeFilename() 182 183 full_name = self.getFullName() 184 185 result = os.path.basename(filename) 186 current = filename 187 188 levels = full_name.count(".") 189 if self.isCompiledPythonPackage(): 190 levels += 1 191 192 for _i in range(levels): 193 current = os.path.dirname(current) 194 195 result = os.path.join(os.path.basename(current), result) 196 197 return result 198 199 200class CompiledPythonModule( 201 ChildrenHavingMixin, 202 ClosureGiverNodeMixin, 203 MarkNeedsAnnotationsMixin, 204 EntryPointMixin, 205 PythonModuleBase, 206): 207 """Compiled Python Module""" 208 209 # This one has a few indicators, pylint: disable=too-many-instance-attributes 210 211 kind = "COMPILED_PYTHON_MODULE" 212 213 __slots__ = ( 214 "is_top", 215 "name", 216 "code_prefix", 217 "code_name", 218 "uids", 219 "temp_variables", 220 "temp_scopes", 221 "preserver_id", 222 "needs_annotations_dict", 223 "trace_collection", 224 "mode", 225 "variables", 226 "active_functions", 227 "visited_functions", 228 "cross_used_functions", 229 "used_modules", 230 "future_spec", 231 "source_code", 232 "module_dict_name", 233 "locals_scope", 234 ) 235 236 named_children = ("body", "functions") 237 238 checkers = {"body": checkStatementsSequenceOrNone} 239 240 def __init__(self, module_name, is_top, mode, future_spec, source_ref): 241 PythonModuleBase.__init__(self, module_name=module_name, source_ref=source_ref) 242 243 ClosureGiverNodeMixin.__init__( 244 self, name=module_name.getBasename(), code_prefix="module" 245 ) 246 247 ChildrenHavingMixin.__init__( 248 self, values={"body": None, "functions": ()} # delayed 249 ) 250 251 MarkNeedsAnnotationsMixin.__init__(self) 252 253 EntryPointMixin.__init__(self) 254 255 self.is_top = is_top 256 257 self.mode = mode 258 259 self.variables = {} 260 261 # Functions that have been used. 262 self.active_functions = OrderedSet() 263 264 # Functions that should be visited again. 265 self.visited_functions = set() 266 267 self.cross_used_functions = OrderedSet() 268 269 self.used_modules = OrderedSet() 270 271 # Often "None" until tree building finishes its part. 272 self.future_spec = future_spec 273 274 # The source code of the module if changed or not from disk. 275 self.source_code = None 276 277 self.module_dict_name = "globals_%s" % (self.getCodeName(),) 278 279 self.locals_scope = getLocalsDictHandle( 280 self.module_dict_name, "module_dict", self 281 ) 282 283 @staticmethod 284 def isCompiledPythonModule(): 285 return True 286 287 def getDetails(self): 288 return { 289 "filename": self.source_ref.getFilename(), 290 "module_name": self.module_name, 291 } 292 293 def getDetailsForDisplay(self): 294 result = self.getDetails() 295 296 if self.future_spec is not None: 297 result["code_flags"] = ",".join(self.future_spec.asFlags()) 298 299 return result 300 301 def getCompilationMode(self): 302 return self.mode 303 304 @classmethod 305 def fromXML(cls, provider, source_ref, **args): 306 # Modules are not having any provider, must not be used, 307 assert False 308 309 def getFutureSpec(self): 310 return self.future_spec 311 312 def setFutureSpec(self, future_spec): 313 self.future_spec = future_spec 314 315 def isTopModule(self): 316 return self.is_top 317 318 def asGraph(self, graph, desc): 319 graph = graph.add_subgraph( 320 name="cluster_%s" % desc, comment="Graph for %s" % self.getName() 321 ) 322 323 # graph.body.append("style=filled") 324 # graph.body.append("color=lightgrey") 325 # graph.body.append("label=Iteration_%d" % desc) 326 327 def makeTraceNodeName(variable, version, variable_trace): 328 return "%s/ %s %s %s" % ( 329 desc, 330 variable.getName(), 331 version, 332 variable_trace.__class__.__name__, 333 ) 334 335 for function_body in self.active_functions: 336 trace_collection = function_body.trace_collection 337 338 node_names = {} 339 340 for ( 341 (variable, version), 342 variable_trace, 343 ) in trace_collection.getVariableTracesAll().items(): 344 node_name = makeTraceNodeName(variable, version, variable_trace) 345 346 node_names[variable_trace] = node_name 347 348 for ( 349 (variable, version), 350 variable_trace, 351 ) in trace_collection.getVariableTracesAll().items(): 352 node_name = node_names[variable_trace] 353 354 previous = variable_trace.getPrevious() 355 356 attrs = {"style": "filled"} 357 358 if variable_trace.getUsageCount(): 359 attrs["color"] = "blue" 360 else: 361 attrs["color"] = "red" 362 363 graph.add_node(node_name, **attrs) 364 365 if type(previous) is tuple: 366 for prev_trace in previous: 367 graph.add_edge(node_names[prev_trace], node_name) 368 369 assert prev_trace is not variable_trace 370 371 elif previous is not None: 372 assert previous is not variable_trace 373 graph.add_edge(node_names[previous], node_name) 374 375 return graph 376 377 def getSourceCode(self): 378 if self.source_code is not None: 379 return self.source_code 380 else: 381 return readSourceCodeFromFilename( 382 module_name=self.getFullName(), 383 source_filename=self.getCompileTimeFilename(), 384 ) 385 386 def setSourceCode(self, code): 387 self.source_code = code 388 389 def getParent(self): 390 # We have never have a parent 391 return None 392 393 def getParentVariableProvider(self): 394 # We have never have a provider 395 return None 396 397 def hasVariableName(self, variable_name): 398 return variable_name in self.variables or variable_name in self.temp_variables 399 400 def getProvidedVariables(self): 401 return self.variables.values() 402 403 def getFilename(self): 404 return self.source_ref.getFilename() 405 406 def getVariableForAssignment(self, variable_name): 407 return self.getProvidedVariable(variable_name) 408 409 def getVariableForReference(self, variable_name): 410 return self.getProvidedVariable(variable_name) 411 412 def getVariableForClosure(self, variable_name): 413 return self.getProvidedVariable(variable_name=variable_name) 414 415 def createProvidedVariable(self, variable_name): 416 assert variable_name not in self.variables 417 418 result = Variables.ModuleVariable(module=self, variable_name=variable_name) 419 420 self.variables[variable_name] = result 421 422 return result 423 424 @staticmethod 425 def getContainingClassDictCreation(): 426 return None 427 428 @staticmethod 429 def isEarlyClosure(): 430 # Modules should immediately closure variables on use. 431 return True 432 433 def getEntryPoint(self): 434 return self 435 436 def getCodeName(self): 437 # For code name of modules, we need to translate to C identifiers, 438 # removing characters illegal for that. 439 440 return encodePythonIdentifierToC(self.getFullName()) 441 442 def addFunction(self, function_body): 443 functions = self.subnode_functions 444 assert function_body not in functions 445 functions += (function_body,) 446 self.setChild("functions", functions) 447 448 def startTraversal(self): 449 self.used_modules = OrderedSet() 450 self.active_functions = OrderedSet() 451 452 def restartTraversal(self): 453 self.visited_functions = set() 454 455 def addUsedModule(self, key): 456 self.used_modules.add(key) 457 458 def getUsedModules(self): 459 return self.used_modules 460 461 def addUsedFunction(self, function_body): 462 assert function_body in self.subnode_functions, function_body 463 464 assert ( 465 function_body.isExpressionFunctionBody() 466 or function_body.isExpressionClassBody() 467 or function_body.isExpressionGeneratorObjectBody() 468 or function_body.isExpressionCoroutineObjectBody() 469 or function_body.isExpressionAsyncgenObjectBody() 470 ) 471 472 self.active_functions.add(function_body) 473 474 result = function_body not in self.visited_functions 475 self.visited_functions.add(function_body) 476 477 return result 478 479 def getUsedFunctions(self): 480 return self.active_functions 481 482 def getUnusedFunctions(self): 483 for function in self.subnode_functions: 484 if function not in self.active_functions: 485 yield function 486 487 def addCrossUsedFunction(self, function_body): 488 if function_body not in self.cross_used_functions: 489 self.cross_used_functions.add(function_body) 490 491 def getCrossUsedFunctions(self): 492 return self.cross_used_functions 493 494 def getFunctionFromCodeName(self, code_name): 495 for function in self.subnode_functions: 496 if function.getCodeName() == code_name: 497 return function 498 499 def getOutputFilename(self): 500 main_filename = self.getFilename() 501 502 if main_filename.endswith(".py"): 503 result = main_filename[:-3] 504 elif main_filename.endswith(".pyw"): 505 result = main_filename[:-4] 506 else: 507 result = main_filename 508 509 # There are some characters that somehow are passed to shell, by 510 # Scons or unknown, so lets avoid them for now. 511 return result.replace(")", "").replace("(", "") 512 513 def computeModule(self): 514 self.restartTraversal() 515 516 old_collection = self.trace_collection 517 518 self.trace_collection = TraceCollectionModule(self) 519 520 module_body = self.subnode_body 521 522 if module_body is not None: 523 result = module_body.computeStatementsSequence( 524 trace_collection=self.trace_collection 525 ) 526 527 if result is not module_body: 528 self.setChild("body", result) 529 530 self.attemptRecursion() 531 532 # Finalize locals scopes previously determined for removal in last pass. 533 self.trace_collection.updateVariablesFromCollection( 534 old_collection, self.source_ref 535 ) 536 537 # Indicate if this is pass 1 for the module as return value. 538 was_complete = not self.locals_scope.complete 539 540 def markAsComplete(body, trace_collection): 541 if ( 542 body.locals_scope is not None 543 and body.locals_scope.isMarkedForPropagation() 544 ): 545 body.locals_scope = None 546 547 if body.locals_scope is not None: 548 body.locals_scope.markAsComplete(trace_collection) 549 550 def markEntryPointAsComplete(body): 551 markAsComplete(body, body.trace_collection) 552 553 outline_bodies = body.trace_collection.getOutlineFunctions() 554 555 if outline_bodies is not None: 556 for outline_body in outline_bodies: 557 markAsComplete(outline_body, body.trace_collection) 558 559 body.optimizeUnusedTempVariables() 560 561 markEntryPointAsComplete(self) 562 563 for function_body in self.getUsedFunctions(): 564 markEntryPointAsComplete(function_body) 565 566 function_body.optimizeUnusedClosureVariables() 567 function_body.optimizeVariableReleases() 568 569 return was_complete 570 571 def getTraceCollections(self): 572 yield self.trace_collection 573 574 for function in self.getUsedFunctions(): 575 yield function.trace_collection 576 577 def isUnoptimized(self): 578 # Modules don't do this, pylint: disable=no-self-use 579 return False 580 581 def getLocalVariables(self): 582 # Modules don't do this, pylint: disable=no-self-use 583 return () 584 585 def getUserLocalVariables(self): 586 # Modules don't do this, pylint: disable=no-self-use 587 return () 588 589 @staticmethod 590 def getFunctionVariablesWithAutoReleases(): 591 """Return the list of function variables that should be released at exit.""" 592 return () 593 594 def getOutlineLocalVariables(self): 595 outlines = self.getTraceCollection().getOutlineFunctions() 596 597 if outlines is None: 598 return () 599 600 result = [] 601 602 for outline in outlines: 603 result.extend(outline.getUserLocalVariables()) 604 605 return result 606 607 def hasClosureVariable(self, variable): 608 # Modules don't do this, pylint: disable=no-self-use,unused-argument 609 return False 610 611 def removeUserVariable(self, variable): 612 outlines = self.getTraceCollection().getOutlineFunctions() 613 614 for outline in outlines: 615 user_locals = outline.getUserLocalVariables() 616 617 if variable in user_locals: 618 outline.removeUserVariable(variable) 619 break 620 621 def getLocalsScope(self): 622 return self.locals_scope 623 624 625class CompiledPythonPackage(CompiledPythonModule): 626 kind = "COMPILED_PYTHON_PACKAGE" 627 628 def __init__(self, module_name, is_top, mode, future_spec, source_ref): 629 CompiledPythonModule.__init__( 630 self, 631 module_name=module_name, 632 is_top=is_top, 633 mode=mode, 634 future_spec=future_spec, 635 source_ref=source_ref, 636 ) 637 638 def getOutputFilename(self): 639 result = self.getFilename() 640 641 if os.path.isdir(result): 642 return result 643 else: 644 return os.path.dirname(result) 645 646 @staticmethod 647 def canHaveExternalImports(): 648 return True 649 650 651def makeUncompiledPythonModule( 652 module_name, filename, bytecode, is_package, user_provided, technical 653): 654 source_ref = fromFilename(filename) 655 656 if is_package: 657 return UncompiledPythonPackage( 658 module_name=module_name, 659 bytecode=bytecode, 660 filename=filename, 661 user_provided=user_provided, 662 technical=technical, 663 source_ref=source_ref, 664 ) 665 else: 666 return UncompiledPythonModule( 667 module_name=module_name, 668 bytecode=bytecode, 669 filename=filename, 670 user_provided=user_provided, 671 technical=technical, 672 source_ref=source_ref, 673 ) 674 675 676class UncompiledPythonModule(PythonModuleBase): 677 """Compiled Python Module""" 678 679 kind = "UNCOMPILED_PYTHON_MODULE" 680 681 __slots__ = "bytecode", "filename", "user_provided", "technical", "used_modules" 682 683 def __init__( 684 self, module_name, bytecode, filename, user_provided, technical, source_ref 685 ): 686 PythonModuleBase.__init__(self, module_name=module_name, source_ref=source_ref) 687 688 self.bytecode = bytecode 689 self.filename = filename 690 691 self.user_provided = user_provided 692 self.technical = technical 693 694 self.used_modules = () 695 696 def finalize(self): 697 del self.used_modules 698 del self.bytecode 699 700 @staticmethod 701 def isUncompiledPythonModule(): 702 return True 703 704 def isUserProvided(self): 705 return self.user_provided 706 707 def isTechnical(self): 708 """Must be bytecode as it's used in CPython library initialization.""" 709 return self.technical 710 711 def getByteCode(self): 712 return self.bytecode 713 714 def getFilename(self): 715 return self.filename 716 717 def getUsedModules(self): 718 return self.used_modules 719 720 def setUsedModules(self, used_modules): 721 self.used_modules = used_modules 722 723 def startTraversal(self): 724 pass 725 726 727class UncompiledPythonPackage(UncompiledPythonModule): 728 kind = "UNCOMPILED_PYTHON_PACKAGE" 729 730 731class PythonMainModule(CompiledPythonModule): 732 kind = "PYTHON_MAIN_MODULE" 733 734 __slots__ = ("main_added", "early_modules") 735 736 def __init__(self, main_added, mode, future_spec, source_ref): 737 # Is this one from a "__main__.py" file. 738 self.main_added = main_added 739 740 CompiledPythonModule.__init__( 741 self, 742 module_name=ModuleName("__main__"), 743 is_top=True, 744 mode=mode, 745 future_spec=future_spec, 746 source_ref=source_ref, 747 ) 748 749 self.early_modules = () 750 751 def getDetails(self): 752 return { 753 "filename": self.source_ref.getFilename(), 754 "main_added": self.main_added, 755 "mode": self.mode, 756 } 757 758 @classmethod 759 def fromXML(cls, provider, source_ref, **args): 760 if "code_flags" in args: 761 future_spec = fromFlags(args["code_flags"]) 762 763 result = cls( 764 main_added=args["main_added"] == "True", 765 mode=args["mode"], 766 future_spec=future_spec, 767 source_ref=source_ref, 768 ) 769 770 from nuitka.ModuleRegistry import addRootModule 771 772 addRootModule(result) 773 774 function_work = [] 775 776 for xml in args["functions"]: 777 _kind, node_class, func_args, source_ref = extractKindAndArgsFromXML( 778 xml, source_ref 779 ) 780 781 if "provider" in func_args: 782 func_args["provider"] = getOwnerFromCodeName(func_args["provider"]) 783 else: 784 func_args["provider"] = result 785 786 if "flags" in args: 787 func_args["flags"] = set(func_args["flags"].split(",")) 788 789 if "doc" not in args: 790 func_args["doc"] = None 791 792 function = node_class.fromXML(source_ref=source_ref, **func_args) 793 794 # Could do more checks for look up of body here, but so what... 795 function_work.append((function, iter(iter(xml).next()).next())) 796 797 for function, xml in function_work: 798 function.setChild( 799 "body", 800 fromXML( 801 provider=function, xml=xml, source_ref=function.getSourceReference() 802 ), 803 ) 804 805 result.setChild( 806 "body", fromXML(provider=result, xml=args["body"][0], source_ref=source_ref) 807 ) 808 809 return result 810 811 @staticmethod 812 def isMainModule(): 813 return True 814 815 def getOutputFilename(self): 816 if self.main_added: 817 return os.path.dirname(self.getFilename()) 818 else: 819 return CompiledPythonModule.getOutputFilename(self) 820 821 def setEarlyModules(self, early_modules): 822 self.early_modules = early_modules 823 824 def getEarlyModules(self): 825 return self.early_modules 826 827 def computeModule(self): 828 CompiledPythonModule.computeModule(self) 829 830 from nuitka.ModuleRegistry import addUsedModule 831 832 for early_module in self.early_modules: 833 addUsedModule(early_module) 834 835 836class PythonShlibModule(PythonModuleBase): 837 kind = "PYTHON_SHLIB_MODULE" 838 839 __slots__ = ("used_modules",) 840 841 avoid_duplicates = set() 842 843 def __init__(self, module_name, source_ref): 844 PythonModuleBase.__init__(self, module_name=module_name, source_ref=source_ref) 845 846 # That would be a mistake we just made. 847 assert os.path.basename(source_ref.getFilename()) != "<frozen>" 848 849 # That is too likely a bug. 850 assert module_name != "__main__" 851 852 # Duplicates should be avoided by us caching elsewhere before creating 853 # the object. 854 assert self.getFullName() not in self.avoid_duplicates, self.getFullName() 855 self.avoid_duplicates.add(self.getFullName()) 856 857 self.used_modules = None 858 859 def finalize(self): 860 del self.used_modules 861 862 def getFilename(self): 863 return self.source_ref.getFilename() 864 865 def startTraversal(self): 866 pass 867 868 def getPyIFilename(self): 869 """Get Python type description filename.""" 870 871 path = self.getFilename() 872 filename = os.path.basename(path) 873 dirname = os.path.dirname(path) 874 875 return os.path.join(dirname, filename.split(".")[0]) + ".pyi" 876 877 def _readPyPIFile(self): 878 """Read the .pyi file if present and scan for dependencies.""" 879 880 # Complex stuff, pylint: disable=too-many-branches,too-many-statements 881 882 if self.used_modules is None: 883 pyi_filename = self.getPyIFilename() 884 885 if os.path.exists(pyi_filename): 886 pyi_deps = OrderedSet() 887 888 # Flag signalling multiline import handling 889 in_import = False 890 in_import_part = "" 891 892 for line in getFileContentByLine(pyi_filename): 893 line = line.strip() 894 895 if not in_import: 896 if line.startswith("import "): 897 imported = line[7:] 898 899 pyi_deps.add(imported) 900 elif line.startswith("from "): 901 parts = line.split(None, 3) 902 assert parts[0] == "from" 903 assert parts[2] == "import" 904 905 origin_name = parts[1] 906 907 if origin_name == "typing": 908 continue 909 910 if origin_name == ".": 911 origin_name = self.getFullName() 912 913 # TODO: Might want to add full relative import handling. 914 915 if origin_name != self.getFullName(): 916 pyi_deps.add(origin_name) 917 918 imported = parts[3] 919 if imported.startswith("("): 920 # Handle multiline imports 921 if not imported.endswith(")"): 922 in_import = True 923 imported = imported[1:] 924 in_import_part = origin_name 925 assert in_import_part, ( 926 "Multiline part in file %s cannot be empty" 927 % pyi_filename 928 ) 929 else: 930 in_import = False 931 imported = imported[1:-1] 932 assert imported 933 934 if imported == "*": 935 continue 936 937 for name in imported.split(","): 938 if name: 939 name = name.strip() 940 pyi_deps.add(origin_name + "." + name) 941 942 else: # In import 943 imported = line 944 if imported.endswith(")"): 945 imported = imported[0:-1] 946 in_import = False 947 948 for name in imported.split(","): 949 name = name.strip() 950 if name: 951 pyi_deps.add(in_import_part + "." + name) 952 953 if "typing" in pyi_deps: 954 pyi_deps.discard("typing") 955 if "__future__" in pyi_deps: 956 pyi_deps.discard("__future__") 957 958 if self.getFullName() in pyi_deps: 959 pyi_deps.discard(self.getFullName()) 960 if self.getFullName().getPackageName() in pyi_deps: 961 pyi_deps.discard(self.getFullName().getPackageName()) 962 963 self.used_modules = tuple((pyi_dep, None) for pyi_dep in pyi_deps) 964 else: 965 self.used_modules = () 966 967 def getUsedModules(self): 968 self._readPyPIFile() 969 970 assert "." not in self.used_modules, self 971 972 return self.used_modules 973 974 def getParentModule(self): 975 return self 976