├── .gitignore ├── .travis.yml ├── CHANGES.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── setup.py └── src ├── __init__.py └── tests ├── __init__.py ├── test_core.py └── test_issues.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyo 2 | *.pyc 3 | *.swp 4 | /*.egg-info 5 | /.eggs 6 | /build 7 | /dist 8 | /MANIFEST 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | - "3.7" 8 | install: 9 | - "python setup.py install" 10 | script: 11 | - "python setup.py test" 12 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | py-demandimport Changelog 2 | ************************* 3 | 4 | 0.3.5 (unreleased) 5 | ================== 6 | 7 | - Add collections.abc to default ignore list. 8 | 9 | 10 | 0.3.4 (2017-06-08) 11 | ================== 12 | 13 | - Python 3.6 14 | - Add sip to the default ignore list. #6 15 | 16 | 17 | 0.3.3 (2016-10-20) 18 | ================== 19 | 20 | - Add ``is_loaded`` and ``is_proxy``. 21 | Thanks-to: github.com/poke1024 22 | 23 | 24 | 0.3.2 (2015-12-22) 25 | ================== 26 | 27 | - Fixed issue #2: ``import a.b.c`` will incorrectly try to import ``b.c`` 28 | 29 | 30 | 0.3.1 (2015-12-21) 31 | ================== 32 | 33 | - Relicense GPL version 2 or later (GPLv2+) 34 | 35 | 36 | 0.3.0 (2015-12-21) 37 | ================== 38 | 39 | - Do not delay ImportError in a special case. 40 | - Add optional logging (for debugging) 41 | - Fixed issue #1: ``import a.b`` in a module ``a.c`` was incorrectly executed 42 | as a relative ``import c``. 43 | - Improve thread safety 44 | 45 | 46 | 0.2.2 (2015-12-05) 47 | ================== 48 | 49 | - Moved to zest.releaser 50 | - Add some basic unittests 51 | - Python 3 support 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include CHANGES.rst 3 | include LICENSE 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | demandimport 2 | ************ 3 | 4 | Delays loading of modules until they are actually used. Perfect for Python 5 | apps that need to be snappy like command-line utils. Source-code derived 6 | from mercurial. 7 | 8 | To enable, write 9 | 10 | .. code:: python 11 | 12 | import demandimport; demandimport.enable() 13 | 14 | Imports of the following form will be delayed 15 | 16 | .. code:: python 17 | 18 | import a, b.c 19 | import a.b as c 20 | from a import b, c # a will be loaded immediately, though 21 | 22 | These imports with not be delayed 23 | 24 | .. code:: python 25 | 26 | from a import * 27 | b = __import__(a) 28 | 29 | Delayed loading will confuse some third-party modules. In that case you 30 | can disable the delay for just that module. For example 31 | 32 | .. code:: python 33 | 34 | demandimport.ignore('Crypto.PublicKey._fastmath') 35 | 36 | There are also versions that can be used with ``with`` 37 | 38 | .. code:: python 39 | 40 | with demandimport.enabled(): 41 | # do something 42 | with demandimport.disabled(): 43 | import troublesome.module 44 | with demandimport.ignored('test'): 45 | import other.troublemaker 46 | 47 | Installation 48 | ============ 49 | 50 | To install ``demandimport``, simply run:: 51 | 52 | pip install demandimport 53 | 54 | Attribution 55 | =========== 56 | 57 | Olivia Mackall is the original author of the module in 58 | Mercurial on which this module is based. Bas Westerbaan 59 | maintains it now. 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | from setuptools import setup 5 | 6 | setup( 7 | name='demandimport', 8 | version='0.3.5.dev0', 9 | description='On-demand imports, taken from mercurial', 10 | long_description="{0:s}\n{1:s}". format( 11 | open('README.rst').read(), 12 | open('CHANGES.rst').read()), 13 | author='Bas Westerbaan', 14 | author_email='bas@westerbaan.name', 15 | url='http://github.com/bwesterb/py-demandimport/', 16 | packages=['demandimport', 'demandimport.tests'], 17 | package_dir={'demandimport': 'src'}, 18 | test_suite='demandimport.tests', 19 | license='GPLv2+', 20 | install_requires=[], 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 24 | 'Operating System :: OS Independent', 25 | 'Programming Language :: Python :: 2.7', 26 | 'Programming Language :: Python :: 3.4', 27 | 'Programming Language :: Python :: 3.5', 28 | 'Programming Language :: Python :: 3.6', 29 | 'Programming Language :: Python :: 3.7', 30 | 'Topic :: Software Development :: Libraries', 31 | ] 32 | ), 33 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | # demandimport.py - global demand-loading of modules for Mercurial 2 | # 3 | # Copyright 2006, 2007 Olivia Mackall 4 | # 2013, 2015 Bas Westerbaan 5 | # 6 | # This software may be used and distributed according to the terms of the 7 | # GNU General Public License version 2 or any later version. 8 | 9 | ''' 10 | demandimport - automatic demandloading of modules 11 | 12 | To enable this module, do: 13 | 14 | import demandimport; demandimport.enable() 15 | 16 | Imports of the following forms will be demand-loaded: 17 | 18 | import a, b.c 19 | import a.b as c 20 | from a import b,c # a will be loaded immediately 21 | 22 | These imports will not be delayed: 23 | 24 | from a import * 25 | b = __import__(a) 26 | ''' 27 | 28 | try: 29 | import __builtin__ as builtins 30 | except ImportError: 31 | import builtins 32 | 33 | import imp 34 | from threading import RLock 35 | 36 | _origimport = __import__ 37 | lock = RLock() 38 | 39 | class _demandmod(object): 40 | """module demand-loader and proxy""" 41 | def __init__(self, name, globals, locals, level=-1, parent_path=None): 42 | global _ignore 43 | if '.' in name: 44 | head, rest = name.split('.', 1) 45 | after = [rest] 46 | else: 47 | head = name 48 | after = [] 49 | object.__setattr__(self, "_data", (head, globals, locals, after, level, 50 | parent_path)) 51 | object.__setattr__(self, "_module", None) 52 | object.__setattr__(self, "_ignore", set(_ignore)) 53 | def _extend(self, name): 54 | """add to the list of submodules to load""" 55 | self._data[3].append(name) 56 | def _load(self): 57 | global _ignore, lock 58 | with lock: 59 | if self._module: 60 | return 61 | head, globals, locals, after, level, parent_path = self._data 62 | old_ignore, _ignore = _ignore, self._ignore 63 | path = parent_path + '.' + head if parent_path else head 64 | if _log: 65 | if after: 66 | _log('Triggered to import %s and setup lazy submodules %s '+ 67 | 'for %s', path, after, globals.get('__name__', '?') 68 | if globals else '?') 69 | else: 70 | _log('Triggered to import %s for %s', path, 71 | globals.get('__name__', '?') if globals else '?') 72 | # If we are given a parent_path, we will ask __import__ to 73 | # import parent.path.head. By default it returns the loaded 74 | # module `parent'. However, we are interested in `head'. 75 | # By passing a barely-non-trivial fromlist, __import__ returns 76 | # the right-most module instead of the left-most. 77 | fromlist = ['__name__'] if parent_path else [] 78 | if level == -1: 79 | mod = _origimport(path, globals, locals, fromlist) 80 | else: 81 | mod = _origimport(path, globals, locals, fromlist, level) 82 | assert not isinstance(mod, _demandmod) 83 | _ignore = old_ignore 84 | # load submodules 85 | def subload(mod, modp, p): 86 | h, t = p, None 87 | if '.' in p: 88 | h, t = p.split('.', 1) 89 | if not hasattr(mod, h): 90 | if _log: 91 | _log('Delaying import of %s for %s as %s situation #4', 92 | p, mod.__dict__.get('__name__', '?'), h) 93 | setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__, 94 | parent_path=modp)) 95 | elif t: 96 | subload(getattr(mod, h), modp+'.'+h, t) 97 | 98 | for x in after: 99 | subload(mod, path, x) 100 | 101 | # are we in the locals dictionary still? 102 | if locals and locals.get(head) is self: 103 | locals[head] = mod 104 | 105 | object.__setattr__(self, "_module", mod) 106 | 107 | def __repr__(self): 108 | if self._module: 109 | return "" % self._data[0] 110 | return "" % self._data[0] 111 | def __call__(self, *args, **kwargs): 112 | raise TypeError("%s object is not callable" % repr(self)) 113 | def __getattribute__(self, attr): 114 | if attr in ('_data', '_extend', '_load', '_module', '_ignore'): 115 | return object.__getattribute__(self, attr) 116 | self._load() 117 | return getattr(self._module, attr) 118 | def __setattr__(self, attr, val): 119 | self._load() 120 | setattr(self._module, attr, val) 121 | 122 | def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1): 123 | global lock 124 | with lock: 125 | if not locals or name in _ignore or fromlist == ('*',): 126 | # these cases we can't really delay 127 | if level == -1: 128 | return _origimport(name, globals, locals, fromlist) 129 | else: 130 | return _origimport(name, globals, locals, fromlist, level) 131 | elif not fromlist: 132 | # import a [as b] 133 | if '.' in name: # a.b 134 | base, rest = name.split('.', 1) 135 | # email.__init__ loading email.mime 136 | if globals and globals.get('__name__') == base: 137 | if level != -1: 138 | return _origimport(name, globals, locals, fromlist, 139 | level) 140 | else: 141 | return _origimport(name, globals, locals, fromlist) 142 | # if a is already demand-loaded, add b to its submodule list 143 | if base in locals: 144 | if isinstance(locals[base], _demandmod): 145 | if _log: 146 | _log('Adding %s to submodule list of %s', rest, 147 | base) 148 | locals[base]._extend(rest) 149 | return locals[base] 150 | else: # '.' not in name 151 | # For an absolute import of an unnested module, we can check 152 | # whether the module exists without loading anything. 153 | # So lets do that. 154 | if level == 0: # abs. import 155 | imp.find_module(name) 156 | if _log: 157 | _log('Delaying import of %s for %s (level %s) situation #1', 158 | name, globals.get('__name__', '?') if globals else '?', 159 | level) 160 | return _demandmod(name, globals, locals, level) 161 | else: 162 | if level != -1: 163 | # from . import b,c,d or from .a import b,c,d 164 | return _origimport(name, globals, locals, fromlist, level) 165 | # from a import b,c,d 166 | mod = _origimport(name, globals, locals) 167 | # recurse down the module chain 168 | for comp in name.split('.')[1:]: 169 | if not hasattr(mod, comp): 170 | if _log: 171 | _log('Delaying import of %s for %s situation #2', 172 | comp, mod.__dict__.get('__name__', '?')) 173 | setattr(mod, comp, _demandmod(comp, mod.__dict__, 174 | mod.__dict__)) 175 | mod = getattr(mod, comp) 176 | for x in fromlist: 177 | # set requested submodules for demand load 178 | if not hasattr(mod, x): 179 | if _log: 180 | _log('Delaying import of %s for %s situation #3', x, 181 | mod.__dict__.get('__name__', '?')) 182 | setattr(mod, x, _demandmod(x, mod.__dict__, locals)) 183 | # This ensures 184 | # 185 | # with demandimport.ignored('a.b.c'): 186 | # from a.b import c 187 | # 188 | # behaves as expected. 189 | # TODO we should skip the `_demandmod'. 190 | if name + '.' + x in _ignore: 191 | getattr(mod, x)._load() 192 | return mod 193 | 194 | _ignore = set([ 195 | '__future__', 196 | '_hashlib', 197 | '_xmlplus', 198 | 'fcntl', 199 | 'win32com.gen_py', 200 | '_winreg', # 2.7 mimetypes needs immediate ImportError 201 | 'pythoncom', 202 | # imported by tarfile, not available under Windows 203 | 'pwd', 204 | 'grp', 205 | # imported by profile, itself imported by hotshot.stats, 206 | # not available under Windows 207 | 'resource', 208 | # this trips up many extension authors 209 | 'gtk', 210 | 'sip', 211 | 'collections.abc', 212 | # setuptools' pkg_resources.py expects "from __main__ import x" to 213 | # raise ImportError if x not defined 214 | '__main__', 215 | '_ssl', # conditional imports in the stdlib 216 | 'typing.abc', # issue #8 217 | ]) 218 | 219 | is_enabled = False 220 | _log = None 221 | 222 | def ignore(module_name): 223 | global _ignore 224 | _ignore.add(module_name) 225 | 226 | class ignored(object): 227 | def __init__(self, module_name): 228 | self.module_name = module_name 229 | def __enter__(self): 230 | global _ignore 231 | self.added = self.module_name not in _ignore 232 | if self.added: 233 | _ignore.add(self.module_name) 234 | def __exit__(self, *args): 235 | global _ignore 236 | if self.added: 237 | _ignore.remove(self.module_name) 238 | 239 | def enable(): 240 | "enable global demand-loading of modules" 241 | global is_enabled 242 | if not is_enabled: 243 | builtins.__import__ = _demandimport 244 | is_enabled = True 245 | 246 | def disable(): 247 | "disable global demand-loading of modules" 248 | global is_enabled 249 | if is_enabled: 250 | builtins.__import__ = _origimport 251 | is_enabled = False 252 | 253 | class disabled(object): 254 | def __enter__(self): 255 | global is_enabled 256 | self.old = is_enabled 257 | if is_enabled: 258 | disable() 259 | def __exit__(self, *args): 260 | if self.old: 261 | enable() 262 | 263 | class enabled(object): 264 | def __enter__(self): 265 | global is_enabled 266 | self.old = is_enabled 267 | if not is_enabled: 268 | enable() 269 | def __exit__(self, *args): 270 | if not self.old: 271 | disable() 272 | 273 | def is_proxy(module): 274 | """ Checks whether the given module is a demandimport proxy object. """ 275 | return isinstance(module, _demandmod) 276 | 277 | def is_loaded(module): 278 | """ Checks whether the given module has been loaded. 279 | 280 | Note that the object might still be a proxy object. Check this case 281 | with the `is_proxy` function. """ 282 | if not is_proxy(module): 283 | return True 284 | return bool(module._module) 285 | 286 | def set_logfunc(logfunc): 287 | """ Sets a logger to which demandimport will report all of its actions. 288 | 289 | Useful to debug problems with third-party modules. """ 290 | global _log 291 | _log = logfunc 292 | -------------------------------------------------------------------------------- /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | import random 5 | import os.path 6 | import textwrap 7 | import tempfile 8 | 9 | LOADED = set() 10 | 11 | class TestModule(object): 12 | def __enter__(self): 13 | self.tempdir = tempfile.mkdtemp() 14 | self.name = 'testmodule{0}'.format(random.randint(0,2**128)) 15 | sys.path.append(self.tempdir) 16 | self.path = os.path.join(self.tempdir, self.name) 17 | os.mkdir(self.path) 18 | with open(os.path.join(self.path, '__init__.py'), 'w') as f: 19 | f.write(textwrap.dedent(''' 20 | import demandimport.tests 21 | name = {} 22 | demandimport.tests.LOADED.add(name) 23 | ''').format(repr(self.name))) 24 | return self 25 | 26 | @property 27 | def loaded(self): 28 | return self.name in LOADED 29 | 30 | def __exit__(self, *args): 31 | sys.path.remove(self.tempdir) 32 | shutil.rmtree(self.tempdir) 33 | -------------------------------------------------------------------------------- /src/tests/test_core.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import demandimport 4 | from demandimport.tests import TestModule 5 | 6 | class TestCore(unittest.TestCase): 7 | def test_enabled_property(self): 8 | self.assertFalse(demandimport.is_enabled) 9 | try: 10 | demandimport.enable() 11 | self.assertTrue(demandimport.is_enabled) 12 | finally: 13 | demandimport.disable() 14 | self.assertFalse(demandimport.is_enabled) 15 | 16 | def test_enabled_context(self): 17 | self.assertFalse(demandimport.is_enabled) 18 | with demandimport.enabled(): 19 | self.assertTrue(demandimport.is_enabled) 20 | self.assertFalse(demandimport.is_enabled) 21 | 22 | def test_disabled_context(self): 23 | self.assertFalse(demandimport.is_enabled) 24 | with demandimport.enabled(): 25 | self.assertTrue(demandimport.is_enabled) 26 | with demandimport.disabled(): 27 | self.assertFalse(demandimport.is_enabled) 28 | self.assertTrue(demandimport.is_enabled) 29 | self.assertFalse(demandimport.is_enabled) 30 | 31 | def test_testmodule(self): 32 | with TestModule() as m: 33 | self.assertFalse(m.loaded) 34 | __import__(m.name) 35 | self.assertTrue(m.loaded) 36 | 37 | def test_ignoring__import__(self): 38 | with TestModule() as m: 39 | self.assertFalse(m.loaded) 40 | with demandimport.enabled(): 41 | __import__(m.name) 42 | self.assertTrue(m.loaded) 43 | 44 | def test_simple(self): 45 | with TestModule() as m: 46 | self.assertFalse(m.loaded) 47 | with demandimport.enabled(): 48 | lm = __import__(m.name, locals={'foo': 'bar'}) 49 | self.assertFalse(m.loaded) 50 | self.assertEqual(lm.name, m.name) 51 | self.assertTrue(m.loaded) 52 | 53 | def test_ignoring_star(self): 54 | with TestModule() as m: 55 | self.assertFalse(m.loaded) 56 | with demandimport.enabled(): 57 | __import__(m.name, locals={'foo': 'bar'}, fromlist=('*',)) 58 | self.assertTrue(m.loaded) 59 | 60 | def test_ignore(self): 61 | with TestModule() as m: 62 | self.assertFalse(m.loaded) 63 | with demandimport.enabled(): 64 | demandimport.ignore(m.name) 65 | lm = __import__(m.name, locals={'foo': 'bar'}) 66 | self.assertTrue(m.loaded) 67 | 68 | def test_ignored(self): 69 | with TestModule() as m: 70 | self.assertFalse(m.loaded) 71 | with demandimport.enabled(): 72 | with demandimport.ignored(m.name): 73 | lm = __import__(m.name, locals={'foo': 'bar'}) 74 | self.assertTrue(m.loaded) 75 | 76 | def test_is_proxy_and_loaded(self): 77 | with TestModule() as m: 78 | lm = __import__(m.name) 79 | self.assertFalse(demandimport.is_proxy(lm)) 80 | self.assertTrue(m.loaded) 81 | self.assertTrue(demandimport.is_loaded(lm)) 82 | with TestModule() as m: 83 | with demandimport.enabled(): 84 | lm = __import__(m.name, locals={'foo': 'bar'}) 85 | self.assertTrue(demandimport.is_proxy(lm)) 86 | self.assertFalse(demandimport.is_loaded(lm)) 87 | self.assertFalse(m.loaded) 88 | self.assertEqual(lm.name, m.name) 89 | self.assertTrue(demandimport.is_loaded(lm)) 90 | self.assertTrue(m.loaded) 91 | self.assertTrue(demandimport.is_proxy(lm)) 92 | 93 | if __name__ == '__main__': 94 | unittest.main() 95 | -------------------------------------------------------------------------------- /src/tests/test_issues.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import textwrap 3 | import os.path 4 | import sys 5 | 6 | import demandimport 7 | from demandimport.tests import TestModule 8 | 9 | # Test-cases for bugs we encountered 10 | 11 | class TestIssues(unittest.TestCase): 12 | def test_issue1(self): 13 | with TestModule() as m: 14 | with demandimport.enabled(): 15 | with open(os.path.join(m.path, 'a.py'), 'w') as f: 16 | f.write(textwrap.dedent(""" 17 | import {0}.b 18 | {0}.b.__name__ 19 | """).format(m.name)) 20 | with open(os.path.join(m.path, 'b.py'), 'w') as f: 21 | pass 22 | __import__(m.name+'.a', locals={'foo': 'bar'}).a.__name__ 23 | 24 | def test_issue2(self): 25 | with TestModule() as m: 26 | with demandimport.enabled(): 27 | os.mkdir(os.path.join(m.path, 'a')) 28 | with open(os.path.join(m.path, 'a', '__init__.py'), 'w') as f: 29 | pass 30 | with open(os.path.join(m.path, 'a', 'b.py'), 'w') as f: 31 | pass 32 | __import__(m.name+'.a.b', locals={'foo': 'bar'}).a.b.__name__ 33 | 34 | def test_issue3(self): 35 | if sys.version_info[0] >= 3: 36 | return 37 | with TestModule() as m: 38 | with demandimport.enabled(): 39 | os.mkdir(os.path.join(m.path, 'a')) 40 | with open(os.path.join(m.path, 'a', '__init__.py'), 'w') as f: 41 | pass 42 | with open(os.path.join(m.path, 'a', 'b.py'), 'w') as f: 43 | pass 44 | with open(os.path.join(m.path, 'a', 'c.py'), 'w') as f: 45 | f.write("from b import *") 46 | __import__(m.name+'.a.c', locals={'foo': 'bar'}).a.c.__name__ 47 | 48 | if __name__ == '__main__': 49 | def log(msg, *args): 50 | print(msg % args) 51 | demandimport.set_logfunc(log) 52 | unittest.main() 53 | --------------------------------------------------------------------------------