update version
This commit is contained in:
parent
b0b7ff0005
commit
26d37fbd72
@ -145,65 +145,99 @@ class ImportExtractor(ast.NodeVisitor):
|
|||||||
if node.module and node.level == 0:
|
if node.module and node.level == 0:
|
||||||
module_name = node.module.split('.')[0];
|
module_name = node.module.split('.')[0];
|
||||||
if module_name: self.imported_modules.add((module_name, self.file_path_str))
|
if module_name: self.imported_modules.add((module_name, self.file_path_str))
|
||||||
|
|
||||||
|
|
||||||
DependencyInfo = Dict[str, Dict[str, Union[Set[str], Optional[str], str]]]
|
DependencyInfo = Dict[str, Dict[str, Union[Set[str], Optional[str], str]]]
|
||||||
|
|
||||||
def find_project_modules_and_dependencies(repo_path: Path) -> Tuple[DependencyInfo, DependencyInfo]:
|
def find_project_modules_and_dependencies(
|
||||||
"""Analyzes Python files, returns info on standard and external dependencies."""
|
repo_path: Path, # Original user-selected path
|
||||||
all_imports_locations: Dict[str, Set[str]] = {}; project_modules: Set[str] = set()
|
scan_path: Path # Path where the actual scanning begins
|
||||||
logger.info(f"Starting analysis: Identifying project modules in '{repo_path}'...")
|
) -> Tuple[DependencyInfo, DependencyInfo]:
|
||||||
try: # Simple project module identification
|
"""
|
||||||
for item in repo_path.rglob('*'):
|
Analyzes Python files starting from scan_path, identifies project modules
|
||||||
if item.is_dir() and (item / '__init__.py').exists():
|
relative to scan_path, and finds standard/external dependencies.
|
||||||
try:
|
Explicitly ignores imports matching the name of the scan_path directory if
|
||||||
rel_dir = item.relative_to(repo_path);
|
scan_path is different from repo_path (assuming it's the main package).
|
||||||
if rel_dir.parts: project_modules.add(rel_dir.parts[0])
|
|
||||||
except ValueError: pass
|
|
||||||
elif item.is_file() and item.suffix == '.py' and item.name != '__init__.py':
|
|
||||||
try:
|
|
||||||
rel_file = item.relative_to(repo_path)
|
|
||||||
if len(rel_file.parts) == 1: project_modules.add(item.stem)
|
|
||||||
elif len(rel_file.parts) > 1 and (item.parent / '__init__.py').exists(): project_modules.add(rel_file.parts[0])
|
|
||||||
except ValueError: pass
|
|
||||||
except Exception as e: logger.error(f"Error identifying project modules: {e}")
|
|
||||||
logger.debug(f"Potential project modules: {project_modules}")
|
|
||||||
|
|
||||||
logger.info(f"Analyzing Python files for imports...")
|
Args:
|
||||||
|
repo_path (Path): The root path selected by the user.
|
||||||
|
scan_path (Path): The directory to actually scan for source code.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[DependencyInfo, DependencyInfo]: std_lib_info, external_deps_info
|
||||||
|
"""
|
||||||
|
all_imports_locations: Dict[str, Set[str]] = {}
|
||||||
|
# project_modules: Set[str] = set() # Identifying project modules is complex, let's rely on scan_path name
|
||||||
|
|
||||||
|
# --- NUOVA LOGICA: Identifica il nome del pacchetto principale (se si scansiona sottocartella) ---
|
||||||
|
main_project_package_name: Optional[str] = None
|
||||||
|
if repo_path != scan_path and scan_path.name == repo_path.name.lower():
|
||||||
|
main_project_package_name = scan_path.name
|
||||||
|
logger.info(f"Assuming '{main_project_package_name}' is the main project package being scanned.")
|
||||||
|
# --- FINE NUOVA LOGICA ---
|
||||||
|
|
||||||
|
|
||||||
|
# Identify potential project modules *within* scan_path can still be useful
|
||||||
|
# but let's simplify the primary check based on main_project_package_name first.
|
||||||
|
# logger.info(f"Analysis target: Identifying project modules within '{scan_path}'...")
|
||||||
|
# ... (previous logic for project_modules identification removed for simplification,
|
||||||
|
# could be added back if needed for more complex internal structures)
|
||||||
|
|
||||||
|
logger.info(f"Analyzing Python files for imports starting from '{scan_path}'...")
|
||||||
excluded_dirs = {'venv', '.venv', 'env', '.env', 'docs', 'tests', 'test', 'site-packages', 'dist-packages', '__pycache__', '.git', '.hg', '.svn', '.tox', '.nox', 'build', 'dist', '*.egg-info'}
|
excluded_dirs = {'venv', '.venv', 'env', '.env', 'docs', 'tests', 'test', 'site-packages', 'dist-packages', '__pycache__', '.git', '.hg', '.svn', '.tox', '.nox', 'build', 'dist', '*.egg-info'}
|
||||||
file_count = 0
|
file_count = 0
|
||||||
for root, dirs, files in os.walk(repo_path, topdown=True):
|
for root, dirs, files in os.walk(scan_path, topdown=True):
|
||||||
dirs[:] = [d for d in dirs if d not in excluded_dirs and not d.startswith('.')]
|
dirs[:] = [d for d in dirs if d not in excluded_dirs and not d.startswith('.')]
|
||||||
|
current_root_path = Path(root)
|
||||||
for file_name in files:
|
for file_name in files:
|
||||||
if file_name.endswith(".py"):
|
if file_name.endswith(".py"):
|
||||||
file_path = Path(root) / file_name; file_count += 1
|
file_path_obj = current_root_path / file_name; file_count += 1
|
||||||
try: rel_path_str = str(file_path.relative_to(repo_path))
|
try: report_rel_path_str = str(file_path_obj.relative_to(repo_path))
|
||||||
except ValueError: rel_path_str = str(file_path); logger.warning(f"Path not relative: {file_path}")
|
except ValueError: report_rel_path_str = str(file_path_obj); logger.warning(f"Path not relative: {file_path_obj}")
|
||||||
|
logger.debug(f"Parsing: {report_rel_path_str}")
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: source = f.read()
|
with open(file_path_obj, 'r', encoding='utf-8', errors='ignore') as f: source = f.read()
|
||||||
tree = ast.parse(source, filename=str(file_path))
|
tree = ast.parse(source, filename=str(file_path_obj))
|
||||||
extractor = ImportExtractor(file_path_str=rel_path_str); extractor.visit(tree)
|
extractor = ImportExtractor(file_path_str=report_rel_path_str)
|
||||||
for module, rel_path in extractor.imported_modules:
|
extractor.visit(tree)
|
||||||
if module: all_imports_locations.setdefault(module, set()).add(rel_path)
|
for module, report_rel_path in extractor.imported_modules:
|
||||||
except SyntaxError as e: logger.warning(f"Syntax error in '{rel_path_str}': {e}. Skipping.")
|
if module: all_imports_locations.setdefault(module, set()).add(report_rel_path)
|
||||||
except Exception as e: logger.exception(f"Error processing file '{rel_path_str}': {e}")
|
except SyntaxError as e: logger.warning(f"Syntax error in '{report_rel_path_str}': {e}. Skipping.")
|
||||||
logger.info(f"Analyzed {file_count} Python files. Found {len(all_imports_locations)} unique top-level imports.")
|
except Exception as e: logger.exception(f"Error processing file '{report_rel_path_str}': {e}")
|
||||||
|
|
||||||
|
logger.info(f"Analyzed {file_count} Python files. Found {len(all_imports_locations)} unique top-level imports.")
|
||||||
logger.info("Classifying imports and fetching versions...")
|
logger.info("Classifying imports and fetching versions...")
|
||||||
std_libs: DependencyInfo = {}; external_deps: DependencyInfo = {}
|
std_libs: DependencyInfo = {}; external_deps: DependencyInfo = {}
|
||||||
|
|
||||||
for imp_module, locs in all_imports_locations.items():
|
for imp_module, locs in all_imports_locations.items():
|
||||||
if imp_module in project_modules: logger.debug(f"Skipping project module: '{imp_module}'"); continue
|
# --- MODIFIED CHECK: Ignore if it matches the main project package name ---
|
||||||
|
if main_project_package_name and imp_module == main_project_package_name:
|
||||||
|
logger.info(f"Skipping '{imp_module}' as it matches the main project package name being scanned.")
|
||||||
|
continue
|
||||||
|
# --- FINE MODIFIED CHECK ---
|
||||||
|
|
||||||
|
# Remove the check against the complex 'project_modules' set for now
|
||||||
|
# if imp_module in project_modules: logger.debug(f"Skipping project module: '{imp_module}'"); continue
|
||||||
|
|
||||||
if imp_module in FALSE_POSITIVE_EXTERNAL_MODULES: logger.info(f"Skipping known false positive: '{imp_module}'"); continue
|
if imp_module in FALSE_POSITIVE_EXTERNAL_MODULES: logger.info(f"Skipping known false positive: '{imp_module}'"); continue
|
||||||
if _is_standard_library(imp_module): logger.debug(f"'{imp_module}' is standard."); std_libs[imp_module] = {'locations': locs, 'version': None}
|
|
||||||
|
if _is_standard_library(imp_module):
|
||||||
|
logger.debug(f"'{imp_module}' is standard library.")
|
||||||
|
std_libs[imp_module] = {'locations': locs, 'version': None}
|
||||||
else:
|
else:
|
||||||
|
# External dependency processing (mapping, version check)
|
||||||
pypi_name = MODULE_NAME_TO_PACKAGE_NAME_MAP.get(imp_module, imp_module)
|
pypi_name = MODULE_NAME_TO_PACKAGE_NAME_MAP.get(imp_module, imp_module)
|
||||||
orig_imp = imp_module if pypi_name != imp_module else None
|
orig_imp = imp_module if pypi_name != imp_module else None
|
||||||
logger.debug(f"'{imp_module}' (PyPI: '{pypi_name}') is external. Fetching version...")
|
logger.debug(f"'{imp_module}' (PyPI: '{pypi_name}') is external. Fetching version...")
|
||||||
version: Optional[str] = None
|
version: Optional[str] = None
|
||||||
try: version = importlib.metadata.version(pypi_name)
|
try: version = importlib.metadata.version(pypi_name)
|
||||||
except: logger.warning(f"Version for '{pypi_name}' not found.")
|
except: logger.warning(f"Version for '{pypi_name}' not found.")
|
||||||
|
|
||||||
dep_data = external_deps.setdefault(pypi_name, {'locations': set(), 'version': version, 'original_import_name': None})
|
dep_data = external_deps.setdefault(pypi_name, {'locations': set(), 'version': version, 'original_import_name': None})
|
||||||
dep_data['locations'].update(locs); # type: ignore
|
dep_data['locations'].update(locs); # type: ignore
|
||||||
if orig_imp and dep_data.get('original_import_name') is None: dep_data['original_import_name'] = orig_imp
|
if orig_imp and dep_data.get('original_import_name') is None: dep_data['original_import_name'] = orig_imp
|
||||||
if dep_data.get('version') is None and version is not None: dep_data['version'] = version
|
if dep_data.get('version') is None and version is not None: dep_data['version'] = version
|
||||||
|
|
||||||
logger.info(f"Classification complete: {len(std_libs)} stdlib used, {len(external_deps)} unique external dependencies.")
|
logger.info(f"Classification complete: {len(std_libs)} stdlib used, {len(external_deps)} unique external dependencies.")
|
||||||
return std_libs, external_deps
|
return std_libs, external_deps
|
||||||
|
|
||||||
|
|||||||
@ -262,22 +262,59 @@ class DependencyAnalyzerApp(tk.Frame):
|
|||||||
|
|
||||||
# --- Analysis and Requirements Generation ---
|
# --- Analysis and Requirements Generation ---
|
||||||
def _analyze_and_generate_reqs_threaded(self) -> None:
|
def _analyze_and_generate_reqs_threaded(self) -> None:
|
||||||
|
"""Starts the background thread for analysis and requirements generation."""
|
||||||
if not self.selected_repository_path:
|
if not self.selected_repository_path:
|
||||||
self._log_message("Please select a repository first.", "WARNING") # User feedback
|
self._log_message("Please select a repository first.", "WARNING") # User feedback
|
||||||
return
|
return
|
||||||
|
# Start the background task
|
||||||
self._run_long_task_threaded(
|
self._run_long_task_threaded(
|
||||||
self._perform_analysis_and_generation,
|
self._perform_analysis_and_generation, # Function to run in thread
|
||||||
callback_success=self._analysis_and_generation_callback # keyword
|
# No specific arguments needed here for the task function itself
|
||||||
|
callback_success=self._analysis_and_generation_callback # Keyword arg
|
||||||
)
|
)
|
||||||
|
|
||||||
def _perform_analysis_and_generation(self) -> Tuple[Path, core.DependencyInfo, core.DependencyInfo]:
|
def _perform_analysis_and_generation(self) -> Tuple[Path, core.DependencyInfo, core.DependencyInfo]:
|
||||||
"""Actual analysis and file generation logic (runs in thread)."""
|
"""
|
||||||
|
Determines scan path, performs analysis via core function, generates file.
|
||||||
|
This method runs in the background thread.
|
||||||
|
Uses self.selected_repository_path set by the GUI.
|
||||||
|
"""
|
||||||
if not self.selected_repository_path:
|
if not self.selected_repository_path:
|
||||||
raise ValueError("Repository path not selected.")
|
# This should ideally not be reached if called correctly, but added safeguard
|
||||||
# core functions log internally using their logger (which propagates to root)
|
raise ValueError("Repository path is not selected when analysis task started.")
|
||||||
core.find_main_script(self.selected_repository_path)
|
|
||||||
std_lib_info, external_info = core.find_project_modules_and_dependencies(self.selected_repository_path)
|
repo_path = self.selected_repository_path
|
||||||
req_file_path = core.generate_requirements_file(self.selected_repository_path, external_info, std_lib_info)
|
repo_name_lower = repo_path.name.lower()
|
||||||
|
potential_sub_package_path = repo_path / repo_name_lower
|
||||||
|
|
||||||
|
scan_path: Path # Define the path to start scanning from
|
||||||
|
|
||||||
|
# Check if a sub-directory with the lowercased repo name exists
|
||||||
|
if potential_sub_package_path.is_dir():
|
||||||
|
logging.info(f"Found sub-directory '{repo_name_lower}', scanning within it.")
|
||||||
|
scan_path = potential_sub_package_path
|
||||||
|
else:
|
||||||
|
logging.info(f"Sub-directory '{repo_name_lower}' not found, scanning the selected repository root '{repo_path}'.")
|
||||||
|
scan_path = repo_path
|
||||||
|
|
||||||
|
# Optional: Find main script (less critical now but kept for potential info)
|
||||||
|
# This logic might also need adjustment based on scan_path vs repo_path if needed elsewhere
|
||||||
|
core.find_main_script(repo_path) # Still checks relative to repo_path
|
||||||
|
|
||||||
|
# Call the core analysis function with both repo_path and scan_path
|
||||||
|
std_lib_info, external_info = core.find_project_modules_and_dependencies(
|
||||||
|
repo_path=repo_path, # Pass original root for relative paths
|
||||||
|
scan_path=scan_path # Pass the determined path to actually scan
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate requirements file (uses repo_path for the output file location)
|
||||||
|
req_file_path = core.generate_requirements_file(
|
||||||
|
repo_path, # Output in the root selected by user
|
||||||
|
external_info,
|
||||||
|
std_lib_info
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return all necessary results for the callback
|
||||||
return req_file_path, std_lib_info, external_info
|
return req_file_path, std_lib_info, external_info
|
||||||
|
|
||||||
def _analysis_and_generation_callback(self, result: Tuple[Path, core.DependencyInfo, core.DependencyInfo]) -> None:
|
def _analysis_and_generation_callback(self, result: Tuple[Path, core.DependencyInfo, core.DependencyInfo]) -> None:
|
||||||
@ -287,7 +324,7 @@ class DependencyAnalyzerApp(tk.Frame):
|
|||||||
self.std_lib_deps_info = std_lib_info
|
self.std_lib_deps_info = std_lib_info
|
||||||
self.external_deps_info = external_info
|
self.external_deps_info = external_info
|
||||||
self.extracted_dependencies_names = set(self.external_deps_info.keys())
|
self.extracted_dependencies_names = set(self.external_deps_info.keys())
|
||||||
logging.info(f"Analysis and requirements.txt generation complete. File: {self.requirements_file_path}") # Log via root
|
logging.info(f"Analysis and requirements generation complete. File: {self.requirements_file_path}") # Log via root
|
||||||
self._populate_modules_tree()
|
self._populate_modules_tree()
|
||||||
|
|
||||||
def _populate_modules_tree(self) -> None:
|
def _populate_modules_tree(self) -> None:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user