Try to find the source code for a module. Returns ('filename', 'source'). One of it can be None if no filename or source found
(modname: str)
| 29 | |
| 30 | @staticmethod |
| 31 | def get_module_source(modname: str) -> tuple[_StrPath | None, str | None]: |
| 32 | """Try to find the source code for a module. |
| 33 | |
| 34 | Returns ('filename', 'source'). One of it can be None if |
| 35 | no filename or source found |
| 36 | """ |
| 37 | try: |
| 38 | mod = import_module(modname) |
| 39 | except Exception as err: |
| 40 | raise PycodeError('error importing %r' % modname, err) from err |
| 41 | loader = getattr(mod, '__loader__', None) |
| 42 | filename: str | None = getattr(mod, '__file__', None) |
| 43 | if loader and getattr(loader, 'get_source', None): |
| 44 | # prefer Native loader, as it respects #coding directive |
| 45 | try: |
| 46 | source = loader.get_source(modname) |
| 47 | if source: |
| 48 | mod_path = None if filename is None else _StrPath(filename) |
| 49 | # no exception and not None - it must be module source |
| 50 | return mod_path, source |
| 51 | except ImportError: |
| 52 | pass # Try other "source-mining" methods |
| 53 | if filename is None and loader and getattr(loader, 'get_filename', None): |
| 54 | # have loader, but no filename |
| 55 | try: |
| 56 | filename = loader.get_filename(modname) |
| 57 | except ImportError as err: |
| 58 | raise PycodeError( |
| 59 | 'error getting filename for %r' % modname, err |
| 60 | ) from err |
| 61 | if filename is None: |
| 62 | # all methods for getting filename failed, so raise... |
| 63 | raise PycodeError('no source found for module %r' % modname) |
| 64 | mod_path = _StrPath(filename).resolve() |
| 65 | if mod_path.suffix in {'.pyo', '.pyc'}: |
| 66 | mod_path_pyw = mod_path.with_suffix('.pyw') |
| 67 | if not mod_path.is_file() and mod_path_pyw.is_file(): |
| 68 | mod_path = mod_path_pyw |
| 69 | else: |
| 70 | mod_path = mod_path.with_suffix('.py') |
| 71 | elif mod_path.suffix not in {'.py', '.pyw'}: |
| 72 | msg = f'source is not a .py file: {mod_path!r}' |
| 73 | raise PycodeError(msg) |
| 74 | |
| 75 | if not mod_path.is_file(): |
| 76 | msg = f'source file is not present: {mod_path!r}' |
| 77 | raise PycodeError(msg) |
| 78 | return mod_path, None |
| 79 | |
| 80 | @classmethod |
| 81 | def for_string( |