├── LICENSE ├── README.rst ├── example_arrow_func.py ├── example_conf.py ├── example_funkw.py ├── example_settings.conf ├── imphook.py ├── mod_arrow_func.py ├── mod_conf.py ├── mod_funkw.py ├── mod_funkw_naive.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2021 Paul Sokolovsky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | imphook - Simple and clear import hooks for Python 2 | ================================================== 3 | 4 | The ``imphook`` module allows to easily define per file type import 5 | hooks, i.e. overload or extend import processing for particular file 6 | types, without affecting processing of other file types, and at the 7 | same time, while ensuring that new processing integrates as seamlessly 8 | as possible with normal Python import rules. 9 | 10 | Besides the Python-level API to install import hooks, the module also 11 | provides command-line interface to run an existing Python script or 12 | module with one or more import hooks preloaded (i.e. without modifying 13 | existing script source code). 14 | 15 | Some but not all things you can easily do using ``imphook`` (most 16 | of these require additional modules to do the heavy lifting, 17 | ``imphook`` just allows to plug it seamlessly into the Python import 18 | system): 19 | 20 | * Override importing of (all or some) .py files, to support new 21 | syntax or semantics in them. 22 | * Import files written using a DSL (domain-specific language) 23 | as if they were Python modules. E.g., config or data files. 24 | * Import modules written in other language(s), assuming you have 25 | an interpreter(s) for them. 26 | * Import binary files, e.g. Java or LLVM bytecode. 27 | 28 | ``imphook`` works both with new, lightweight legacy-free Python 29 | API, as promoted by the `Pycopy `_ 30 | Python dialect (the original source of the "easy import hooks" idea), 31 | and CPython (the older, reference Python implementation), and with 32 | other Python implementations which are CPython-compatible. 33 | 34 | Quick Start 35 | ----------- 36 | 37 | Make sure that you already installed ``imphook`` using:: 38 | 39 | pip3 install -U imphook 40 | 41 | Below is a complete example of an import hook module to load 42 | ``key = value`` style config files:: 43 | 44 | import imphook 45 | 46 | def hook(modname, filename): 47 | with open(filename) as f: 48 | # Create a module object which will be the result of import. 49 | mod = type(imphook)(modname) 50 | for l in f: 51 | k, v = [x.strip() for x in l.split("=", 1)] 52 | setattr(mod, k, v) 53 | return mod 54 | 55 | imphook.add_import_hook(hook, (".conf",)) 56 | 57 | Save this as the ``mod_conf.py`` file, and add the two following 58 | files to test it: 59 | 60 | ``example_settings.conf``:: 61 | 62 | var1 = 123 63 | var2 = hello 64 | 65 | ``example_conf.py``:: 66 | 67 | import example_settings as settings 68 | 69 | print(settings.var1) 70 | print(settings.var2) 71 | 72 | Now run:: 73 | 74 | python3 -m imphook -i mod_conf example_conf.py 75 | 76 | As you can see, the ``example_conf.py`` is able to import 77 | ``example_settings.conf`` as if it were a normal Python module. 78 | 79 | Besides copy-pasting the above and other examples, you can also 80 | clone the Git repository of ``imphook``, which contains various 81 | ready-to-use examples:: 82 | 83 | git clone https://github.com/pfalcon/python-imphook 84 | 85 | 86 | API to install hooks and hook structure 87 | --------------------------------------- 88 | 89 | The API of the module consists of one function: 90 | `imphook.add_import_hook(hook, ext_tuple)`. *hook* is a name of 91 | hook function. *ext_tuple* is a tuple of file extensions 92 | the hook function should handle (the leading dot should be included). 93 | More often than not, you will want to handle just one extension, 94 | so don't forget to use the usual Python syntax with a trailing 95 | comma for 1-element tuple, e.g.: ``(".ext",)``. Python modules may 96 | not contain a dot (``"."``) in their names (they are used to separate 97 | subpackages), so the extension you register may contain multiple 98 | dots, e.g. ``".foo.bar"``, with filename ``my_module.foo.bar`` 99 | matching it. 100 | 101 | It is possible to call `imphook.add_import_hook(hook, ext_tuple)` 102 | multiple times to install multiple hooks. The hooks are installed 103 | in the stack-like fashion, the last installed will be called 104 | first. It is possible to install multiple hooks for the same file 105 | extension, and earlier installed hooks may still be called in this 106 | case, because a hook function may skip processing a particular 107 | file, and let other hooks to take a chance, with default processing 108 | happening if no hook handled the import. 109 | 110 | The signature and template of the actual hook function is:: 111 | 112 | def my_hook(modname, filename): 113 | # Return None if you don't want to handle `filename`. 114 | # Otherwise, load `filename`, create a Python module object, 115 | # with name `modname`, populate it based on the loaded file 116 | # contents, and return it. 117 | 118 | The *modname* parameter is a full module name of the module to 119 | import, in the usual dot-separated notation, e.g. ``my_module`` 120 | or ``pkg.subp.mod``. For relative imports originated from within 121 | a package, this name is already resolved to full absolute name. 122 | The *modname* should be used to create a module object with the 123 | given name. 124 | 125 | The *filename* parameter is a full pathname (with extension) of the 126 | file which hook should import. This filename is known to exist, so 127 | you may proceed to open it directly. You may skip processing this 128 | file by returning ``None`` from the hook, then other hooks may be 129 | tried, and default processing happens otherwise (e.g. ``.py`` files 130 | are loaded as usual, or ImportError raised for non-standard 131 | extensions). For package imports, the value of *filename* ends with 132 | ``/__init__.py``, and that is the way to distinguish module vs 133 | package imports. 134 | 135 | If the hook proceeds with the file, it should load it by whatever 136 | means suitable for the file type. File types which are not natively 137 | supported by Python would require installing and using other extension 138 | modules (beyond ``imphook``). After loading the file, the hook should 139 | create an empty Python module object which will be the result of the 140 | import. There are a few ways to do that: 141 | 142 | * The baseline is to call a module type as a constructor. To get 143 | a module type, just apply the usual ``type()`` function to an 144 | existing (imported) module. You'll definitely have ``imphook`` 145 | itself imported, which leads us to:: 146 | 147 | mod = type(imphook)(modname) 148 | 149 | The parameter to constructor is the name of module to create, 150 | as passed to the hook. 151 | * If the above looks too magic for you, you can import symbolic 152 | name for module type from the ``types`` module:: 153 | 154 | from types import ModuleType 155 | mod = ModuleType(modname) 156 | 157 | * Finally, you may use the ``imp`` module, which may be as well 158 | the clearest (to the newbie) way of doing it:: 159 | 160 | import imp 161 | mod = imp.new_module(modname) 162 | 163 | But mind that the ``imp`` module is considered deprecated. 164 | 165 | Of the choices above, the first is the most efficient - no need 166 | to import additional modules, and it's just one line. And once 167 | you saw and were explained what it does, it shouldn't be a problem 168 | to remember and recognize it later. 169 | 170 | Once the module object is created as discussed above, you should 171 | populate it. A way to do that is by using ``setattr()`` builtin 172 | to set a particular attribute of a module to a particular value. 173 | Attributes usually represent variables with data values, but 174 | may be also functions and classes. 175 | 176 | Finally, you just return the populated module object. 177 | 178 | In case you want to perform custom transformation on the Python 179 | source, the process is usually somewhat different, where you 180 | transform a representation of the source, and then execute it 181 | in the context of a new module, which causes it to be populated. 182 | An example of that is provided in the latter section. 183 | 184 | 185 | Using import hooks in your applications 186 | --------------------------------------- 187 | 188 | There are 2 ways to use import hook(s) in you Python programs: 189 | either preloading them before starting your program using ``imphook`` 190 | command-line runner (next section) or load them explicitly at the 191 | startup of your application. Crucial thing to remember that import 192 | hooks apply: a) for imports only; b) for imports appearing after 193 | the hook was installed. 194 | 195 | The main file of our application is normally *not imported*, but 196 | executed directly. This leads to the following pattern in structuring 197 | your application source files: 198 | 199 | * Have a "startup file", which is the one which user will actually 200 | run, so name it appropriately. In that file, you load import hooks 201 | and perform other baseline system-level initialization. 202 | * The main functionality of your application is contained in seperate 203 | module(s). The startup script imports such a main module and 204 | executes it (e.g., by calling a function from it). 205 | 206 | You already grasped how that works - as the "main" module is 207 | *imported*, whatever hooks the "startup" script installed, will 208 | apply to it. 209 | 210 | This pattern is actually officially encoded in the structure of 211 | Python *packages*. And any non-trivial Python application will 212 | likely be a package with a few sub-modules in it, so you can as 213 | well structure your applications this way (as a package), even 214 | if they start simple (only one submodule initially). So, if you 215 | try to "run" a package, what actually gets run is ``__main__`` 216 | submodule in that package. That's exactly the "startup" file 217 | we discussed above. It installs the import hooks, and imports 218 | a submodule with actual application's functionality. 219 | 220 | The actual loading of hooks is very easy: just import them in 221 | your startup script, voila! For ``mod_conf.py`` hook module 222 | shown in the example above that would be:: 223 | 224 | import mod_conf 225 | 226 | You should do that as soon as reasonably possible in your startup 227 | file. Normally, that would be after stdlib imports, and before 228 | imports of your app's modules. Sometimes, you may want to put 229 | hook imports very first, even before the stdlib modules. E.g., 230 | if hooks implement JIT compilation, which may benefit even stdlib 231 | modules (someone yet has to develop such hooks!). All in all, 232 | follow the guidelines above and documentation of the particular 233 | hooks that you use. 234 | 235 | Finally, the pattern described above (of having "startup" and 236 | "main" modules in your app) doesn't work too well in case your 237 | application is a single script file, you would need to turn that 238 | into 2 files to make the import hooks work. But that's exactly 239 | why ``imphook`` provides command-line preloader/runner interface! 240 | 241 | 242 | Command-line interface 243 | ---------------------- 244 | 245 | Where you would normally run a single script like: 246 | 247 | * ``python3 script.py``, or 248 | * ``python3 -m script`` 249 | 250 | you can run the same script/module with some import hooks preloaded 251 | using following commands (changes comparing to the above commands 252 | are shown in italics): 253 | 254 | * ``python3`` *-m imphook -i * ``script.py``, or 255 | * ``python3`` *-m imphook -i * ``-m script`` 256 | 257 | That's exactly how we ran ``example_conf.py`` in the Quick Start 258 | section. You can repeat ``-i`` option multiple times. Alternatively 259 | and more compactly, you can pass to single ``-i`` option a 260 | comma-separated list of hook modules to import, e.g.: 261 | ``-i mod_hook1,mod_hook2,mod_hook3``. If you pass multiple hooks, 262 | the will be handled in the same stack-like fashion as the API 263 | call described above. In the previous example, ``mod_hook3`` will 264 | be called first to process imports, then ``mod_hook2``, then 265 | ``mod_hook1``. Of course, this will be important only if more 266 | than one hook handles the same file extenstion. 267 | 268 | This stack-like order on the command-line is used for consistency 269 | with the API, to avoid confusion between the two. But it's also 270 | the natural order, if you think about it: we start with standard 271 | Python import hooks (yes, Python handles all imports using hooks, 272 | although its hooks are as simple and clear as those we build here 273 | with ``imphook``). Then, there may be some hooks installed in 274 | ``sitecustomize`` module (that's a way to install some "persistent" 275 | hooks for all your projects, which we don't go into, as it should 276 | be known for any advanced user). When we get to the ``imphook`` 277 | command line, we want to be able to override either standard 278 | Python or ``sitecustomize`` hooks, and that's why all hooks are 279 | consistently installed in the stack-like fashion. And you should 280 | keep in mind that if an application explicitly installs any hooks, 281 | they will have higher priority than those passed on the command 282 | line. 283 | 284 | We also should again emphasize the difference between ``script.py`` 285 | and ``-m script`` forms above. In the first case, the script is 286 | *executed directly*, and any import hooks you specified with 287 | ``-i`` **do not** apply to the script itself (but will apply to 288 | imports performed by ``script.py``). Even want hooks to apply 289 | to the script's source itself, you **must** run it using 290 | ``-m script`` notation (which exactly tells "import this script 291 | as a module, don't run it directly"). Pay double attention that 292 | when you use ``-m`` switch, you **must not** use the ``.py`` 293 | extension (if you do, you ask to import the submodule ``py`` of 294 | package ``script``, which rarely what you want or makes sense). 295 | 296 | Last final note is about whitespace in the command-line parameters: 297 | they should be used exactly as shown and described: there should 298 | always be spaces between ``-i`` and ``-m`` options and their 299 | parameters. And vice versa, if you use comma-separated list of 300 | import hooks, there should be no spaces in that list. 301 | 302 | 303 | Example of Python source transformation 304 | --------------------------------------- 305 | 306 | We started this documentation with a quick example of writing an 307 | import hook for a simple DSL. We'd like to finish it with examples 308 | of another expected common use of ``imphook`` - implementing 309 | new syntactic (and semantic!) features for Python. 310 | 311 | A common starting example is just trying to "rename" one of existing 312 | syntactic elements, e.g. use word "function" instead of "lambda". 313 | 314 | So, we'd like to get following source code dialect to run: 315 | 316 | ``example_funkw.py``:: 317 | 318 | my_fun = function: print("imphook's functionality is cool!") 319 | my_fun() 320 | 321 | The simplest way to do that is just to replace every occurance of 322 | "function" in the source with "lambda" (then compile, then execute 323 | it in the module context). We thus will come up with the following 324 | hook implementation: 325 | 326 | ``mod_funkw_naive.py``:: 327 | 328 | import imphook 329 | 330 | def hook(filename): 331 | with open(filename) as f: 332 | source = f.read() 333 | source = source.replace("function", "lambda") 334 | mod = type(imphook)("") 335 | exec(source, vars(mod)) 336 | return mod 337 | 338 | imphook.add_import_hook(hook, (".py",)) 339 | 340 | If you read previous sections carefully, you already know that if 341 | we want the import hook to apply to the script itself, we must 342 | run it as module, using ``-m`` switch:: 343 | 344 | python3 -m imphook -i mod_funkw_naive -m example_funkw 345 | 346 | And we get:: 347 | 348 | imphook's lambdaality is cool! 349 | 350 | Oops! The word "lambdaality" is definitely cool, but that's not what 351 | we expected! It happens because the code just blindly replaces 352 | occurrances everywhere, including within string literals. We could 353 | try to work that around by using regular expression replace and match 354 | whole words, that would help with the case above, but would still 355 | replace lone "function" in the strings. Which makes us conclude: 356 | transforming surface representation of a program (i.e. a sequence 357 | of characters) is never an adequte method. We should operate on 358 | a more suitable program representation, and the baseline such 359 | representation is a sequence (or stream) of tokens. Let's use it: 360 | 361 | mod_funkw.py:: 362 | 363 | import tokenize 364 | import imphook 365 | 366 | def hook(filename): 367 | 368 | def xform(token_stream): 369 | for t in token_stream: 370 | if t[0] == tokenize.NAME and t[1] == "function": 371 | yield (tokenize.NAME, "lambda") + t[2:] 372 | else: 373 | yield t 374 | 375 | with open(filename, "rb") as f: 376 | # Fairly speaking, tokenizing just to convert back to string form 377 | # isn't too efficient, but CPython doesn't offer us a way to parse 378 | # token stream so far, so we have no choice. 379 | source = tokenize.untokenize(xform(tokenize.tokenize(f.readline))) 380 | mod = type(imphook)("") 381 | exec(source, vars(mod)) 382 | return mod 383 | 384 | imphook.add_import_hook(hook, (".py",)) 385 | 386 | 387 | Credits and licensing 388 | --------------------- 389 | 390 | ``imphook`` is (c) `Paul Sokolovsky `_ and 391 | is released under the MIT license. 392 | -------------------------------------------------------------------------------- /example_arrow_func.py: -------------------------------------------------------------------------------- 1 | # Example for "arrow functions". To run this example: 2 | # 3 | # python3 -m imphook -i mod_arrow_func -m example_arrow_func 4 | 5 | f = (a, b) => a + b 6 | print(f(1, 2)) 7 | 8 | res = ((a, b) => a + b)(3, 4) 9 | print(res) 10 | 11 | print(list(map((x) => x * 2, [1, 2, 3, 4]))) 12 | 13 | curry = (a) => (b) => a + b 14 | print(curry(3)(100)) 15 | 16 | # Confirm there's no crashing on bare tuple at the end of file. 17 | (1, 2) 18 | -------------------------------------------------------------------------------- /example_conf.py: -------------------------------------------------------------------------------- 1 | # Example of importing simple "key = value" config file 2 | # (example_settings.conf in this case). To run this example: 3 | # 4 | # python3 -m imphook -i mod_conf example_conf.py 5 | 6 | import example_settings as settings 7 | 8 | 9 | print(settings.var1) 10 | print(settings.var2) 11 | -------------------------------------------------------------------------------- /example_funkw.py: -------------------------------------------------------------------------------- 1 | # Example for mod_funkw_naive.py/mod_funkw.py . To run: 2 | # 3 | # python3 -m imphook -i mod_funkw_naive -m example_funkw 4 | # python3 -m imphook -i mod_funkw -m example_funkw 5 | 6 | my_fun = function: print("imphook's functionality is cool!") 7 | my_fun() 8 | -------------------------------------------------------------------------------- /example_settings.conf: -------------------------------------------------------------------------------- 1 | var1 = 123 2 | var2 = hello 3 | -------------------------------------------------------------------------------- /imphook.py: -------------------------------------------------------------------------------- 1 | # imphook - Simple and clear import hooks for Python 2 | # 3 | # This module is part of Pycopy https://github.com/pfalcon/pycopy 4 | # project. 5 | # 6 | # Copyright (c) 2020 Paul Sokolovsky 7 | # 8 | # The MIT License 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | 28 | import sys 29 | try: 30 | sys.setimphook 31 | has_setimphook = True 32 | except AttributeError: 33 | has_setimphook = False 34 | 35 | __all__ = ("add_import_hook",) 36 | 37 | 38 | if has_setimphook: 39 | 40 | import os.path 41 | 42 | _old_hook = ... 43 | _hooks = [] 44 | 45 | def _hook_dispatch(modname, path): 46 | for hook, exts in _hooks: 47 | for ext in exts: 48 | f = path + ext 49 | if os.path.isfile(f): 50 | mod = hook(modname, f) 51 | if mod is not None: 52 | return mod 53 | if _old_hook: 54 | return _old_hook(path) 55 | 56 | def add_import_hook(hook, exts): 57 | global _old_hook 58 | _hooks.insert(0, (hook, exts)) 59 | old = sys.setimphook(_hook_dispatch, exts) 60 | if _old_hook is ...: 61 | _old_hook = old 62 | 63 | else: 64 | 65 | from collections import defaultdict 66 | import warnings 67 | import importlib 68 | 69 | _imphook_exts = [] 70 | _ext2hook = defaultdict(list) 71 | 72 | class ImphookFileLoader(importlib._bootstrap_external.FileLoader): 73 | 74 | def create_module(self, spec): 75 | #print("create_module", spec) 76 | ext = "." + spec.origin.rsplit(".", 1)[1] 77 | for h in _ext2hook[ext]: 78 | m = h(spec.name, spec.origin) 79 | if m: 80 | return m 81 | 82 | def exec_module(self, mod): 83 | # Module is fully populated in create_module 84 | pass 85 | 86 | 87 | def add_import_hook(hook, exts): 88 | _imphook_exts.extend(exts) 89 | for ext in exts: 90 | _ext2hook[ext].insert(0, hook) 91 | 92 | for i, el in enumerate(sys.path_hooks): 93 | if not isinstance(el, type): 94 | # Assume it's a type wrapped in a closure, 95 | # as is the case for FileFinder. 96 | el = type(el(".")) 97 | if el is importlib._bootstrap_external.FileFinder: 98 | sys.path_hooks.pop(i) 99 | insert_pos = i 100 | break 101 | else: 102 | warnings.warn("Could not find existing FileFinder to replace, installing ours as the first to use") 103 | insert_pos = 0 104 | 105 | # Mirrors what's done by importlib._bootstrap_external._install(importlib._bootstrap) 106 | loaders = [(ImphookFileLoader, _imphook_exts)] + importlib._bootstrap_external._get_supported_file_loaders() 107 | # path_hook closure captures supported_loaders in itself, all instances 108 | # of FileFinder class will be created with it. 109 | sys.path_hooks.insert(insert_pos, importlib._bootstrap_external.FileFinder.path_hook(*loaders)) 110 | sys.path_importer_cache.clear() 111 | 112 | 113 | if __name__ == "__main__": 114 | sys.argv.pop(0) 115 | 116 | while len(sys.argv) >= 2 and sys.argv[0] == "-i": 117 | sys.argv.pop(0) 118 | mod_name = sys.argv.pop(0) 119 | __import__(mod_name) 120 | 121 | if not sys.argv or sys.argv[0].startswith("-") and sys.argv[0] != "-m": 122 | print("""\ 123 | usage: python3 -m imphook (-i )+ (|-m ) * 124 | 125 | Preloads import hook module(s) and executes a Python script/module as usual. 126 | https://github.com/pfalcon/python-imphook 127 | """) 128 | sys.exit(1) 129 | 130 | if sys.argv[0] == "-m": 131 | sys.argv.pop(0) 132 | main_mod = sys.argv[0] 133 | try: 134 | import importlib.util 135 | importlib.util.find_spec 136 | except: 137 | # We don't try to patch sys.argv[0], because native Pycopy's -m 138 | # sets just the module name there. 139 | # Magic __import__ for Pycopy which sets up __main__ name. 140 | __import__(main_mod, None, None, False) 141 | else: 142 | # All this stuff is to set nodule name as __main__ before its 143 | # top-level code gets executed. Otherwise, we could just use 144 | # __import__(sys.argv[0]) 145 | spec = importlib.util.find_spec(main_mod, package=None) 146 | loader = spec.loader 147 | if not loader: 148 | print("imphook: No module named %s" % main_mod, file=sys.stderr) 149 | sys.exit(1) 150 | loader.name = "__main__" 151 | sys.argv[0] = loader.path 152 | mod = loader.create_module(spec) 153 | if not mod: 154 | mod = type(sys)("__main__") 155 | loader.exec_module(mod) 156 | else: 157 | try: 158 | with open(sys.argv[0]) as f: 159 | s = f.read() 160 | except Exception as e: 161 | print("imphook: can't open file: %s" % e, file=sys.stderr) 162 | sys.exit(2) 163 | exec(s) 164 | -------------------------------------------------------------------------------- /mod_arrow_func.py: -------------------------------------------------------------------------------- 1 | # This imphook module implements "arrow functions", similar to JavaScript. 2 | # (a, b) => a + b ---> lambda a, b: a + b 3 | 4 | import tokenize 5 | 6 | import imphook 7 | 8 | 9 | class TokBuf: 10 | def __init__(self): 11 | self.tokens = [] 12 | 13 | def append(self, t): 14 | self.tokens.append(t) 15 | 16 | def clear(self): 17 | self.tokens.clear() 18 | 19 | def empty(self): 20 | return not self.tokens 21 | 22 | def spool(self): 23 | yield from self.tokens 24 | self.clear() 25 | 26 | 27 | def xform(token_stream): 28 | tokbuf = TokBuf() 29 | for t in token_stream: 30 | if t[1] == "(": 31 | # We're interested only in the deepest parens. 32 | if not tokbuf.empty(): 33 | yield from tokbuf.spool() 34 | tokbuf.append(t) 35 | elif t[1] == ")": 36 | nt1 = next(token_stream) 37 | nt2 = next(token_stream) 38 | if nt1[1] == "=" and nt2[1] == ">": 39 | yield (tokenize.NAME, "lambda") 40 | yield from tokbuf.tokens[1:] 41 | tokbuf.clear() 42 | yield (tokenize.OP, ":") 43 | else: 44 | yield from tokbuf.spool() 45 | yield t 46 | yield nt1 47 | yield nt2 48 | elif not tokbuf.empty(): 49 | tokbuf.append(t) 50 | else: 51 | yield t 52 | 53 | 54 | def hook(modname, filename): 55 | with open(filename, "r") as f: 56 | # Fairly speaking, tokenizing just to convert back to string form 57 | # isn't too efficient, but CPython doesn't offer us a way to parse 58 | # token stream so far, so we have no choice. 59 | source = tokenize.untokenize(xform(tokenize.generate_tokens(f.readline))) 60 | mod = type(imphook)(modname) 61 | exec(source, vars(mod)) 62 | return mod 63 | 64 | 65 | imphook.add_import_hook(hook, (".py",)) 66 | -------------------------------------------------------------------------------- /mod_conf.py: -------------------------------------------------------------------------------- 1 | # Import hook to load simple "key = value" config files. Note that value 2 | # is always string in this simple implementation. See example_conf.py 3 | # for the example of using this import hook. 4 | import imphook 5 | 6 | 7 | def hook(modname, filename): 8 | with open(filename) as f: 9 | # Create a module object which will be result of the import. 10 | mod = type(imphook)(modname) 11 | for l in f: 12 | k, v = [x.strip() for x in l.split("=", 1)] 13 | setattr(mod, k, v) 14 | return mod 15 | 16 | 17 | imphook.add_import_hook(hook, (".conf",)) 18 | -------------------------------------------------------------------------------- /mod_funkw.py: -------------------------------------------------------------------------------- 1 | import tokenize 2 | 3 | import imphook 4 | 5 | 6 | def hook(modname, filename): 7 | 8 | def xform(token_stream): 9 | for t in token_stream: 10 | if t[0] == tokenize.NAME and t[1] == "function": 11 | yield (tokenize.NAME, "lambda") + t[2:] 12 | else: 13 | yield t 14 | 15 | with open(filename, "r") as f: 16 | # Fairly speaking, tokenizing just to convert back to string form 17 | # isn't too efficient, but CPython doesn't offer us a way to parse 18 | # token stream so far (from where we would compile it), so we have 19 | # no choice. 20 | source = tokenize.untokenize(xform(tokenize.generate_tokens(f.readline))) 21 | mod = type(imphook)(modname) 22 | exec(source, vars(mod)) 23 | return mod 24 | 25 | 26 | imphook.add_import_hook(hook, (".py",)) 27 | -------------------------------------------------------------------------------- /mod_funkw_naive.py: -------------------------------------------------------------------------------- 1 | import imphook 2 | 3 | 4 | def hook(modname, filename): 5 | with open(filename) as f: 6 | source = f.read() 7 | source = source.replace("function", "lambda") 8 | mod = type(imphook)(modname) 9 | exec(source, vars(mod)) 10 | return mod 11 | 12 | 13 | imphook.add_import_hook(hook, (".py",)) 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from setuptools import setup 3 | 4 | 5 | setup(name="imphook", 6 | version="1.0", 7 | description="""Simple and clear import hooks for Python - import anything as if it were a Python module""", 8 | long_description=open('README.rst').read(), 9 | url="https://github.com/pfalcon/python-imphook", 10 | author="Paul Sokolovsky", 11 | author_email="pfalcon@users.sourceforge.net", 12 | license="MIT", 13 | py_modules=["imphook"]) 14 | --------------------------------------------------------------------------------