1+ #!/usr/bin/env python3
2+ # Generated by Claude Code (Sonnet 4)
3+
4+ """
5+ Script to test all modules in ansible_base/lib for pure Python compatibility.
6+ This script ensures that modules in the lib folder can be imported without
7+ requiring Django apps to be initialized.
8+ """
9+
10+ import os
11+ import sys
12+ import subprocess
13+ import importlib .util
14+ from pathlib import Path
15+
16+ def find_python_modules (lib_path ):
17+ """Find all Python modules in the lib directory."""
18+ modules = []
19+ lib_path = Path (lib_path )
20+
21+ for py_file in lib_path .rglob ("*.py" ):
22+ if py_file .name == "__init__.py" :
23+ # Convert path to module name (including ansible_base prefix)
24+ relative_path = py_file .parent .relative_to (lib_path .parent .parent )
25+ module_name = str (relative_path ).replace (os .sep , "." )
26+ modules .append (module_name )
27+ else :
28+ # Convert path to module name (including ansible_base prefix)
29+ relative_path = py_file .relative_to (lib_path .parent .parent )
30+ module_name = str (relative_path )[:- 3 ].replace (os .sep , "." ) # Remove .py extension
31+ modules .append (module_name )
32+
33+ return sorted (modules )
34+
35+ def test_pure_python_import (module_name , base_path ):
36+ """Test if a module can be imported with pure Python (no Django setup)."""
37+ test_script = f'''
38+ import sys
39+ sys.path.insert(0, "{ base_path } ")
40+
41+ try:
42+ import { module_name }
43+ print("SUCCESS")
44+ except Exception as e:
45+ print(f"ERROR: {{e}}")
46+ '''
47+
48+ # Run in a clean environment without Django setup
49+ env = os .environ .copy ()
50+ if 'DJANGO_SETTINGS_MODULE' in env :
51+ del env ['DJANGO_SETTINGS_MODULE' ]
52+
53+ try :
54+ result = subprocess .run (
55+ ['python' , '-c' , test_script ],
56+ capture_output = True ,
57+ text = True ,
58+ env = env ,
59+ timeout = 30
60+ )
61+
62+ if result .returncode == 0 and "SUCCESS" in result .stdout :
63+ return True , None
64+ else :
65+ error_msg = result .stderr .strip () or result .stdout .strip ()
66+ return False , error_msg
67+ except subprocess .TimeoutExpired :
68+ return False , "Import test timed out"
69+ except Exception as e :
70+ return False , f"Failed to run test: { e } "
71+
72+ def main ():
73+ """Main function to test all modules in ansible_base/lib."""
74+ script_dir = Path (__file__ ).parent
75+ lib_path = script_dir / "ansible_base" / "lib"
76+
77+ if not lib_path .exists ():
78+ print (f"ERROR: { lib_path } does not exist" )
79+ sys .exit (1 )
80+
81+ print ("Testing pure Python import compatibility for ansible_base/lib modules..." )
82+ print ("=" * 70 )
83+
84+ modules = find_python_modules (lib_path )
85+
86+ # Modules that are allowed to fail because they inherently require Django
87+ allowed_failures = {
88+ # Abstract models inherently require Django to be initialized
89+ 'ansible_base.lib.abstract_models' ,
90+ 'ansible_base.lib.abstract_models.common' ,
91+ 'ansible_base.lib.abstract_models.immutable' ,
92+ 'ansible_base.lib.abstract_models.organization' ,
93+ 'ansible_base.lib.abstract_models.team' ,
94+ 'ansible_base.lib.abstract_models.user' ,
95+ # View classes require Django REST framework
96+ 'ansible_base.lib.utils.views.ansible_base' ,
97+ 'ansible_base.lib.utils.views.django_app_api' ,
98+ 'ansible_base.lib.utils.views.permissions' ,
99+ 'ansible_base.lib.utils.views.urls' ,
100+ # Router classes require Django REST framework
101+ 'ansible_base.lib.routers' ,
102+ 'ansible_base.lib.routers.association_resource_router' ,
103+ # These modules require Django settings to be configured at import time
104+ 'ansible_base.lib.backends.prefixed_user_auth' ,
105+ 'ansible_base.lib.dynamic_config.dynamic_urls' ,
106+ 'ansible_base.lib.serializers.common' ,
107+ 'ansible_base.lib.testing.fixtures' ,
108+ 'ansible_base.lib.testing.util' ,
109+ 'ansible_base.lib.utils.auth' ,
110+ }
111+
112+ failed_modules = []
113+ passed_modules = []
114+ expected_failures = []
115+
116+ for module_name in modules :
117+ print (f"Testing { module_name } ... " , end = "" , flush = True )
118+ success , error = test_pure_python_import (module_name , str (script_dir ))
119+
120+ if success :
121+ print ("✓ PASS" )
122+ passed_modules .append (module_name )
123+ else :
124+ if module_name in allowed_failures :
125+ print ("✗ EXPECTED FAIL" )
126+ expected_failures .append ((module_name , error ))
127+ else :
128+ print ("✗ UNEXPECTED FAIL" )
129+ print (f" Error: { error } " )
130+ failed_modules .append ((module_name , error ))
131+
132+ print ("\n " + "=" * 70 )
133+ print (f"Results: { len (passed_modules )} passed, { len (failed_modules )} unexpected failures, { len (expected_failures )} expected failures" )
134+
135+ if failed_modules :
136+ print ("\n Unexpected failures:" )
137+ for module_name , error in failed_modules :
138+ print (f" - { module_name } : { error } " )
139+
140+ print ("\n These modules should be importable with pure Python." )
141+ print ("Consider moving Django-specific imports inline within functions." )
142+ sys .exit (1 )
143+ else :
144+ print ("\n All importable modules passed! ✓" )
145+ if expected_failures :
146+ print (f"({ len (expected_failures )} modules have expected failures due to Django dependencies)" )
147+ sys .exit (0 )
148+
149+ if __name__ == "__main__" :
150+ main ()
0 commit comments