mirror of
https://github.com/vladmandic/automatic
synced 2026-08-26 15:16:01 +02:00
35aa8c6422
Signed-off-by: Vladimir Mandic <mandic00@live.com>
185 lines
6.8 KiB
Python
185 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import io
|
|
import os
|
|
import sys
|
|
import ast
|
|
import logging
|
|
import contextlib
|
|
import py_compile
|
|
import importlib.util
|
|
import importlib.machinery
|
|
from pathlib import Path
|
|
|
|
|
|
includes = [
|
|
{ 'folder': '.', 'recursive': False, 'includes': True },
|
|
{ 'folder': 'pipelines', 'recursive': True, 'includes': True },
|
|
{ 'folder': 'modules', 'recursive': True, 'includes': False },
|
|
{ 'folder': 'scripts', 'recursive': True, 'local': True, 'includes': False },
|
|
]
|
|
excludes = [
|
|
'node_modules',
|
|
'__pycache__',
|
|
]
|
|
ignores = [
|
|
'torch_directml',
|
|
'intel_extension_for_pytorch',
|
|
'torch_xla.core.xla_model',
|
|
'flash_attn',
|
|
'flash_attn_interface',
|
|
'openai',
|
|
'rembg',
|
|
'controlnet_aux',
|
|
'image_gen_aux',
|
|
'torchsde',
|
|
'ligo.segments',
|
|
'torchdiffeq',
|
|
'insightface',
|
|
'pynvml',
|
|
]
|
|
output = '/tmp/pycompile'
|
|
root = Path('.')
|
|
|
|
|
|
def test_compile(folder: str, recursive: bool):
|
|
stats = { 'ok': [], 'failed': [], 'errors': [] }
|
|
for entry in os.scandir(folder):
|
|
if not any(exclude in entry.path for exclude in excludes) and not entry.name.startswith('.'):
|
|
if entry.is_file() and entry.name.endswith('.py'):
|
|
try:
|
|
cfile = os.path.join(output, os.path.relpath(entry.path, start='.')) + 'c'
|
|
py_compile.compile(entry.path, cfile=cfile, doraise=True)
|
|
stats['ok'].append(entry.path)
|
|
except Exception as e:
|
|
print(f'fail: file={entry.path} error={e}')
|
|
stats['failed'].append(entry.path)
|
|
stats['errors'].append(str(e))
|
|
elif entry.is_dir() and recursive:
|
|
nested_stats = test_compile(entry.path, recursive)
|
|
stats['ok'].extend(nested_stats['ok'])
|
|
stats['failed'].extend(nested_stats['failed'])
|
|
stats['errors'].extend(nested_stats['errors'])
|
|
if len(stats["ok"]) > 0 or len(stats["failed"]) > 0:
|
|
print(f'Compile: folder={folder} ok={len(stats["ok"])} failed={len(stats["failed"])}')
|
|
return stats
|
|
|
|
|
|
def list_imports(path: Path):
|
|
imports = []
|
|
tree = ast.parse(path.read_text(encoding='utf-8'), filename=str(path))
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Import):
|
|
for alias in node.names:
|
|
imports.append((alias.name, 0, path))
|
|
elif isinstance(node, ast.ImportFrom):
|
|
module = node.module
|
|
if node.level > 0:
|
|
if module:
|
|
imports.append((module, node.level, path))
|
|
else:
|
|
for alias in node.names:
|
|
if alias.name != '*':
|
|
imports.append((alias.name, node.level, path))
|
|
elif module:
|
|
imports.append((module, 0, path))
|
|
return imports
|
|
|
|
|
|
def find_import(module: str | None, level: int, path: Path):
|
|
package_parts = path.relative_to(root).with_suffix('').parts
|
|
package_parts = package_parts[:-1]
|
|
if level > len(package_parts) + 1:
|
|
return None
|
|
if level == 0:
|
|
base = list(package_parts)
|
|
else:
|
|
base = list(package_parts[: -level + 1]) if level > 1 else list(package_parts)
|
|
if module:
|
|
base.extend(module.split('.'))
|
|
return '.'.join(base) if base else None
|
|
|
|
|
|
def local_import(path: Path) -> bool: # modules that modify sys.path to allow local imports
|
|
text = path.read_text(encoding='utf-8')
|
|
return 'sys.path.append' in text or 'sys.path.insert' in text
|
|
|
|
|
|
def install_import(path: Path, module_name: str): # modules that install packages at runtime and import them
|
|
text = path.read_text(encoding='utf-8')
|
|
if f'install("{module_name}' in text or f'install(\'{module_name}' in text:
|
|
return True
|
|
return False
|
|
|
|
|
|
def test_import(module: str | None, level: int, path: Path, local: bool = False):
|
|
module_name = find_import(module, level, path) if level > 0 else module
|
|
if not module_name:
|
|
return True
|
|
old_disable = logging.root.manager.disable
|
|
error = None
|
|
try:
|
|
logging.disable(logging.CRITICAL)
|
|
if install_import(path, module_name):
|
|
return True
|
|
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
|
|
try:
|
|
spec = importlib.util.find_spec(module_name)
|
|
except ModuleNotFoundError as e:
|
|
spec = None
|
|
error = e
|
|
if spec is not None:
|
|
return True
|
|
if local or local_import(path):
|
|
for extra in [str(path.parent.parent), str(path.parent)]:
|
|
spec = importlib.machinery.PathFinder.find_spec(module_name, [extra] + sys.path)
|
|
if spec is not None:
|
|
return True
|
|
if error is not None and any(ignore in str(error) for ignore in ignores):
|
|
return True
|
|
return False
|
|
except Exception as e:
|
|
if any(ignore in str(e) for ignore in ignores):
|
|
return True
|
|
return False
|
|
finally:
|
|
logging.disable(old_disable)
|
|
|
|
|
|
def verify_imports(folder: str, recursive: bool, local: bool = False):
|
|
stats = { 'ok': [], 'failed': [] }
|
|
for entry in os.scandir(folder):
|
|
if not any(exclude in entry.path for exclude in excludes) and not entry.name.startswith('.'):
|
|
if entry.is_file() and entry.name.endswith('.py'):
|
|
file_path = Path(entry.path)
|
|
has_failure = False
|
|
for module, level, _path in list_imports(file_path):
|
|
if module in ignores:
|
|
continue
|
|
if not test_import(module, level, file_path, local):
|
|
stats['failed'].append(f'{entry.path}: module={module} level={level}')
|
|
has_failure = True
|
|
if not has_failure:
|
|
stats['ok'].append(entry.path)
|
|
elif entry.is_dir() and recursive:
|
|
nested_stats = verify_imports(entry.path, recursive)
|
|
stats['ok'].extend(nested_stats['ok'])
|
|
stats['failed'].extend(nested_stats['failed'])
|
|
if len(stats['ok']) > 0 or len(stats['failed']) > 0:
|
|
print(f'Imports: folder={folder} ok={len(stats["ok"])} failed={len(stats["failed"])}')
|
|
if len(stats['failed']) > 0:
|
|
for fail in stats['failed']:
|
|
print(f' {fail}')
|
|
return stats
|
|
|
|
|
|
if __name__ == '__main__':
|
|
os.makedirs(output, exist_ok=True)
|
|
sys.path.insert(0, str(root))
|
|
sys.path.insert(0, str(root / 'modules' / 'control'))
|
|
for item in includes:
|
|
print(f"Test {item['folder']}")
|
|
test_compile(item['folder'], item['recursive'])
|
|
if item.get('includes', False):
|
|
verify_imports(item['folder'], item.get('recursive', False), item.get('local', False))
|