Find currently imported submodules used by a function. Submodules used by a function need to be detected and referenced for the function to work correctly at depickling time. Because submodules can be referenced as attribute of their parent package (``package.submodule``), we need a
(code, top_level_dependencies)
| 336 | |
| 337 | |
| 338 | def _find_imported_submodules(code, top_level_dependencies): |
| 339 | """Find currently imported submodules used by a function. |
| 340 | |
| 341 | Submodules used by a function need to be detected and referenced for the |
| 342 | function to work correctly at depickling time. Because submodules can be |
| 343 | referenced as attribute of their parent package (``package.submodule``), we |
| 344 | need a special introspection technique that does not rely on GLOBAL-related |
| 345 | opcodes to find references of them in a code object. |
| 346 | |
| 347 | Example: |
| 348 | ``` |
| 349 | import concurrent.futures |
| 350 | import cloudpickle |
| 351 | def func(): |
| 352 | x = concurrent.futures.ThreadPoolExecutor |
| 353 | if __name__ == '__main__': |
| 354 | cloudpickle.dumps(func) |
| 355 | ``` |
| 356 | The globals extracted by cloudpickle in the function's state include the |
| 357 | concurrent package, but not its submodule (here, concurrent.futures), which |
| 358 | is the module used by func. Find_imported_submodules will detect the usage |
| 359 | of concurrent.futures. Saving this module alongside with func will ensure |
| 360 | that calling func once depickled does not fail due to concurrent.futures |
| 361 | not being imported |
| 362 | """ |
| 363 | |
| 364 | subimports = [] |
| 365 | # check if any known dependency is an imported package |
| 366 | for x in top_level_dependencies: |
| 367 | if ( |
| 368 | isinstance(x, types.ModuleType) |
| 369 | and hasattr(x, "__package__") |
| 370 | and x.__package__ |
| 371 | ): |
| 372 | # check if the package has any currently loaded sub-imports |
| 373 | prefix = x.__name__ + "." |
| 374 | # A concurrent thread could mutate sys.modules, |
| 375 | # make sure we iterate over a copy to avoid exceptions |
| 376 | for name in list(sys.modules): |
| 377 | # Older versions of pytest will add a "None" module to |
| 378 | # sys.modules. |
| 379 | if name is not None and name.startswith(prefix): |
| 380 | # check whether the function can address the sub-module |
| 381 | tokens = set(name[len(prefix) :].split(".")) |
| 382 | if not tokens - set(code.co_names): |
| 383 | subimports.append(sys.modules[name]) |
| 384 | return subimports |
| 385 | |
| 386 | |
| 387 | # relevant opcodes |
no outgoing calls
no test coverage detected
searching dependent graphs…