async def infer_python_dependencies( request: InferPythonDependencies, python_inference: PythonInference ) -> InferredDependencies: if not python_inference.imports: return InferredDependencies() stripped_sources = await Get(StrippedSourceFiles, SourceFilesRequest([request.sources_field])) modules = tuple( PythonModule.create_from_stripped_path(PurePath(fp)) for fp in stripped_sources.snapshot.files ) digest_contents = await Get(DigestContents, Digest, stripped_sources.snapshot.digest) imports_per_file = tuple( find_python_imports(file_content.content.decode(), module_name=module.module) for file_content, module in zip(digest_contents, modules) ) owner_per_import = await MultiGet( Get(PythonModuleOwner, PythonModule(imported_module)) for file_imports in imports_per_file for imported_module in file_imports.explicit_imports if imported_module not in combined_stdlib ) return InferredDependencies( owner.address for owner in owner_per_import if ( owner.address and owner.address.maybe_convert_to_base_target() != request.sources_field.address ) )
async def infer_python_dependencies( request: InferPythonDependencies, python_inference: PythonInference) -> InferredDependencies: if not python_inference.imports: return InferredDependencies([], sibling_dependencies_inferrable=False) stripped_sources = await Get(StrippedSourceFiles, SourceFilesRequest([request.sources_field])) modules = tuple( PythonModule.create_from_stripped_path(PurePath(fp)) for fp in stripped_sources.snapshot.files) digest_contents = await Get(DigestContents, Digest, stripped_sources.snapshot.digest) owner_requests: List[Get[PythonModuleOwner, PythonModule]] = [] for file_content, module in zip(digest_contents, modules): file_imports_obj = find_python_imports(file_content.content.decode(), module_name=module.module) detected_imports = (file_imports_obj.all_imports if python_inference.string_imports else file_imports_obj.explicit_imports) owner_requests.extend( Get(PythonModuleOwner, PythonModule(imported_module)) for imported_module in detected_imports if imported_module not in combined_stdlib) owner_per_import = await MultiGet(owner_requests) result = ( owner.address for owner in owner_per_import if owner.address and owner.address != request.sources_field.address) return InferredDependencies(result, sibling_dependencies_inferrable=True)
def test_module_possible_paths() -> None: assert set(PythonModule("typing").possible_stripped_paths()) == { PurePath("typing.py"), PurePath("typing") / "__init__.py", } assert set(PythonModule("typing.List").possible_stripped_paths()) == { PurePath("typing") / "List.py", PurePath("typing") / "List" / "__init__.py", PurePath("typing.py"), PurePath("typing") / "__init__.py", }
def test_map_module_to_targets(self) -> None: options_bootstrapper = create_options_bootstrapper(args=[ "--source-root-patterns=['source_root1', 'source_root2', '/']" ]) # First check that we can map 3rd-party modules. self.add_to_build_file( "3rdparty/python", dedent("""\ python_requirement_library( name='ansicolors', requirements=[python_requirement('ansicolors==1.21', modules=['colors'])], ) """), ) result = self.request_single_product( PythonModuleOwners, Params(PythonModule("colors.red"), options_bootstrapper)) assert result == PythonModuleOwners( [Address.parse("3rdparty/python:ansicolors")]) # We set up the same module in two source roots to confirm we properly handle source roots. # The first example uses a normal module path, whereas the second uses a package path. self.create_file("source_root1/project/app.py") self.add_to_build_file("source_root1/project", "python_library()") self.create_file("source_root2/project/app/__init__.py") self.add_to_build_file("source_root2/project/app", "python_library()") result = self.request_single_product( PythonModuleOwners, Params(PythonModule("project.app"), options_bootstrapper)) assert result == PythonModuleOwners([ Address.parse("source_root1/project"), Address.parse("source_root2/project/app") ]) # Test a module with no owner (stdlib). This also sanity checks that we can handle when # there is no parent module. result = self.request_single_product( PythonModuleOwners, Params(PythonModule("typing"), options_bootstrapper)) assert not result # Test a module with a single owner with a top-level source root of ".". Also confirm we # can handle when the module includes a symbol (like a class name) at the end. self.create_file("script.py") self.add_to_build_file("", "python_library(name='script')") result = self.request_single_product( PythonModuleOwners, Params(PythonModule("script.Demo"), options_bootstrapper)) assert result == PythonModuleOwners([Address.parse("//:script")])
async def inject_pex_binary_entry_point_dependency( request: InjectPexBinaryEntryPointDependency, python_infer_subsystem: PythonInferSubsystem) -> InjectedDependencies: if not python_infer_subsystem.entry_points: return InjectedDependencies() original_tgt = await Get(WrappedTarget, Address, request.dependencies_field.address) explicitly_provided_deps, entry_point = await MultiGet( Get(ExplicitlyProvidedDependencies, DependenciesRequest(original_tgt.target[Dependencies])), Get( ResolvedPexEntryPoint, ResolvePexEntryPointRequest( original_tgt.target[PexEntryPointField]), ), ) if entry_point.val is None: return InjectedDependencies() owners = await Get(PythonModuleOwners, PythonModule(entry_point.val.module)) address = original_tgt.target.address explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( owners.ambiguous, address, import_reference="module", context= (f"The pex_binary target {address} has the field " f"`entry_point={repr(original_tgt.target[PexEntryPointField].value.spec)}`, which " f"maps to the Python module `{entry_point.val.module}`"), ) maybe_disambiguated = explicitly_provided_deps.disambiguated_via_ignores( owners.ambiguous) unambiguous_owners = owners.unambiguous or ( (maybe_disambiguated, ) if maybe_disambiguated else ()) return InjectedDependencies(unambiguous_owners)
def assert_owners(module: str, expected: list[Address], expected_ambiguous: list[Address] | None = None) -> None: owners = rule_runner.request(PythonModuleOwners, [PythonModule(module)]) assert list(owners.unambiguous) == expected assert list(owners.ambiguous) == (expected_ambiguous or [])
async def inject_lambda_handler_dependency( request: InjectPythonLambdaHandlerDependency, python_infer_subsystem: PythonInferSubsystem) -> InjectedDependencies: if not python_infer_subsystem.entry_points: return InjectedDependencies() original_tgt = await Get(WrappedTarget, Address, request.dependencies_field.address) explicitly_provided_deps, handler = await MultiGet( Get(ExplicitlyProvidedDependencies, DependenciesRequest(original_tgt.target[Dependencies])), Get( ResolvedPythonAwsHandler, ResolvePythonAwsHandlerRequest( original_tgt.target[PythonAwsLambdaHandlerField]), ), ) module, _, _func = handler.val.partition(":") owners = await Get(PythonModuleOwners, PythonModule(module)) address = original_tgt.target.address explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( owners.ambiguous, address, import_reference="module", context= (f"The python_awslambda target {address} has the field " f"`handler={repr(original_tgt.target[PythonAwsLambdaHandlerField].value)}`, which maps " f"to the Python module `{module}`"), ) maybe_disambiguated = explicitly_provided_deps.disambiguated_via_ignores( owners.ambiguous) unambiguous_owners = owners.unambiguous or ( (maybe_disambiguated, ) if maybe_disambiguated else ()) return InjectedDependencies(unambiguous_owners)
async def infer_python_dependencies_via_imports( request: InferPythonImportDependencies, python_infer_subsystem: PythonInferSubsystem, python_setup: PythonSetup, ) -> InferredDependencies: if not python_infer_subsystem.imports: return InferredDependencies([], sibling_dependencies_inferrable=False) wrapped_tgt = await Get(WrappedTarget, Address, request.sources_field.address) detected_imports = await Get( ParsedPythonImports, ParsePythonImportsRequest( request.sources_field, PexInterpreterConstraints.create_from_targets([wrapped_tgt.target], python_setup), ), ) relevant_imports = (detected_imports.all_imports if python_infer_subsystem.string_imports else detected_imports.explicit_imports) owners_per_import = await MultiGet( Get(PythonModuleOwners, PythonModule(imported_module)) for imported_module in relevant_imports if imported_module not in combined_stdlib) merged_result = sorted( set(itertools.chain.from_iterable(owners_per_import))) return InferredDependencies(merged_result, sibling_dependencies_inferrable=True)
async def inject_python_distribution_dependencies( request: InjectPythonDistributionDependencies, python_infer_subsystem: PythonInferSubsystem) -> InjectedDependencies: """Inject dependencies that we can infer from entry points in the distribution.""" if not python_infer_subsystem.entry_points: return InjectedDependencies() original_tgt = await Get(WrappedTarget, Address, request.dependencies_field.address) explicitly_provided_deps, distribution_entry_points, provides_entry_points = await MultiGet( Get(ExplicitlyProvidedDependencies, DependenciesRequest(original_tgt.target[Dependencies])), Get( ResolvedPythonDistributionEntryPoints, ResolvePythonDistributionEntryPointsRequest( entry_points_field=original_tgt. target[PythonDistributionEntryPointsField]), ), Get( ResolvedPythonDistributionEntryPoints, ResolvePythonDistributionEntryPointsRequest( provides_field=original_tgt.target[PythonProvidesField]), ), ) address = original_tgt.target.address all_module_entry_points = [ (category, name, entry_point) for category, entry_points in chain( distribution_entry_points.explicit_modules.items(), provides_entry_points.explicit_modules.items(), ) for name, entry_point in entry_points.items() ] all_module_owners = iter(await MultiGet( Get(PythonModuleOwners, PythonModule(entry_point.module)) for _, _, entry_point in all_module_entry_points)) module_owners: OrderedSet[Address] = OrderedSet() for (category, name, entry_point), owners in zip(all_module_entry_points, all_module_owners): field_str = repr({category: {name: entry_point.spec}}) explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( owners.ambiguous, address, import_reference="module", context=( f"The python_distribution target {address} has the field " f"`entry_points={field_str}`, which maps to the Python module" f"`{entry_point.module}`"), ) maybe_disambiguated = explicitly_provided_deps.disambiguated( owners.ambiguous) unambiguous_owners = owners.unambiguous or ( (maybe_disambiguated, ) if maybe_disambiguated else ()) module_owners.update(unambiguous_owners) return InjectedDependencies( Addresses(module_owners) + distribution_entry_points.pex_binary_addresses + provides_entry_points.pex_binary_addresses)
async def infer_python_dependencies( request: InferPythonDependencies) -> InferredDependencies: stripped_sources = await Get[SourceRootStrippedSources]( StripSourcesFieldRequest(request.sources_field)) modules = tuple( PythonModule.create_from_stripped_path(PurePath(fp)) for fp in stripped_sources.snapshot.files) files_content = await Get[FilesContent](Digest, stripped_sources.snapshot.digest) imports_per_file = tuple( find_python_imports(fc.content.decode(), module_name=module.module) for fc, module in zip(files_content, modules)) owner_per_import = await MultiGet( Get[PythonModuleOwner](PythonModule(imported_module)) for file_imports in imports_per_file for imported_module in file_imports.all_imports if imported_module not in combined_stdlib) return InferredDependencies( owner.address for owner in owner_per_import if owner.address and owner.address != request.sources_field.address)
async def infer_python_dependencies( request: InferPythonDependencies) -> InferredDependencies: stripped_sources = await Get[SourceRootStrippedSources]( StripSourcesFieldRequest(request.sources_field)) modules = tuple( PythonModule.create_from_stripped_path(PurePath(fp)) for fp in stripped_sources.snapshot.files) files_content = await Get[FilesContent](Digest, stripped_sources.snapshot.digest) imports_per_file = tuple( find_python_imports(fc.content.decode(), module_name=module.module) for fc, module in zip(files_content, modules)) owners_per_import = await MultiGet( Get[PythonModuleOwners](PythonModule(imported_module)) for file_imports in imports_per_file for imported_module in file_imports.all_imports if imported_module not in combined_stdlib) # We conservatively only use dep inference if there is exactly one owner for an import. return InferredDependencies( itertools.chain.from_iterable( owners for owners in owners_per_import if len(owners) == 1 and tuple(owners)[0] != request.sources_field.address))
async def inject_lambda_handler_dependency( request: InjectPythonLambdaHandlerDependency, python_infer_subsystem: PythonInferSubsystem ) -> InjectedDependencies: if not python_infer_subsystem.entry_points: return InjectedDependencies() original_tgt = await Get(WrappedTarget, Address, request.dependencies_field.address) handler = await Get( ResolvedPythonAwsHandler, ResolvePythonAwsHandlerRequest(original_tgt.target[PythonAwsLambdaHandlerField]), ) module, _, _func = handler.val.partition(":") owners = await Get(PythonModuleOwners, PythonModule(module)) return InjectedDependencies(owners)
async def inject_pex_binary_entry_point_dependency( request: InjectPexBinaryEntryPointDependency, python_infer_subsystem: PythonInferSubsystem ) -> InjectedDependencies: if not python_infer_subsystem.entry_points: return InjectedDependencies() original_tgt = await Get(WrappedTarget, Address, request.dependencies_field.address) entry_point = await Get( ResolvedPexEntryPoint, ResolvePexEntryPointRequest(original_tgt.target[PexEntryPointField]), ) if entry_point.val is None: return InjectedDependencies() module, _, _func = entry_point.val.partition(":") owners = await Get(PythonModuleOwners, PythonModule(module)) return InjectedDependencies(owners)
async def infer_python_dependencies( request: InferPythonDependencies, python_inference: PythonInference) -> InferredDependencies: if not python_inference.imports: return InferredDependencies([], sibling_dependencies_inferrable=False) stripped_sources = await Get(StrippedSourceFiles, SourceFilesRequest([request.sources_field])) modules = tuple( PythonModule.create_from_stripped_path(PurePath(fp)) for fp in stripped_sources.snapshot.files) digest_contents = await Get(DigestContents, Digest, stripped_sources.snapshot.digest) owners_requests: List[Get[PythonModuleOwners, PythonModule]] = [] for file_content, module in zip(digest_contents, modules): file_imports_obj = find_python_imports( filename=file_content.path, content=file_content.content.decode(), module_name=module.module, ) detected_imports = (file_imports_obj.all_imports if python_inference.string_imports else file_imports_obj.explicit_imports) owners_requests.extend( Get(PythonModuleOwners, PythonModule(imported_module)) for imported_module in detected_imports if imported_module not in combined_stdlib) owners_per_import = await MultiGet(owners_requests) # We remove the request's address so that we don't infer dependencies on self. merged_result = sorted( set(itertools.chain.from_iterable(owners_per_import)) - {request.sources_field.address}) return InferredDependencies(merged_result, sibling_dependencies_inferrable=True)
async def infer_python_dependencies_via_imports( request: InferPythonImportDependencies, python_infer_subsystem: PythonInferSubsystem, python_setup: PythonSetup, ) -> InferredDependencies: if not python_infer_subsystem.imports: return InferredDependencies([], sibling_dependencies_inferrable=False) wrapped_tgt = await Get(WrappedTarget, Address, request.sources_field.address) explicitly_provided_deps, detected_imports = await MultiGet( Get(ExplicitlyProvidedDependencies, DependenciesRequest(wrapped_tgt.target[Dependencies])), Get( ParsedPythonImports, ParsePythonImportsRequest( request.sources_field, PexInterpreterConstraints.create_from_targets( [wrapped_tgt.target], python_setup), ), ), ) relevant_imports = tuple( imp for imp in (detected_imports.all_imports if python_infer_subsystem. string_imports else detected_imports.explicit_imports) if imp not in combined_stdlib) owners_per_import = await MultiGet( Get(PythonModuleOwners, PythonModule(imported_module)) for imported_module in relevant_imports) merged_result: set[Address] = set() for owners, imp in zip(owners_per_import, relevant_imports): merged_result.update(owners.unambiguous) address = wrapped_tgt.target.address explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( owners.ambiguous, address, import_reference="module", context=f"The target {address} imports `{imp}`", ) maybe_disambiguated = explicitly_provided_deps.disambiguated_via_ignores( owners.ambiguous) if maybe_disambiguated: merged_result.add(maybe_disambiguated) return InferredDependencies(sorted(merged_result), sibling_dependencies_inferrable=True)
async def inject_pex_binary_entry_point_dependency( request: InjectPexBinaryEntryPointDependency, python_infer_subsystem: PythonInferSubsystem) -> InjectedDependencies: if not python_infer_subsystem.entry_points: return InjectedDependencies() original_tgt = await Get(WrappedTarget, Address, request.dependencies_field.address) entry_point = await Get( ResolvedPexEntryPoint, ResolvePexEntryPointRequest( original_tgt.target[PexEntryPointField], original_tgt.target[DeprecatedPexBinarySources]), ) if entry_point.val is None: return InjectedDependencies() module, _, _func = entry_point.val.partition(":") owners = await Get(PythonModuleOwners, PythonModule(module)) # TODO: remove the check for == self once the `sources` field is removed. return InjectedDependencies(owner for owner in owners if owner != request.dependencies_field.address)
async def inject_pex_binary_entry_point_dependency( request: InjectPexBinaryEntryPointDependency, python_infer_subsystem: PythonInferSubsystem ) -> InjectedDependencies: if not python_infer_subsystem.entry_points: return InjectedDependencies() original_tgt = await Get(WrappedTarget, Address, request.dependencies_field.address) entry_point_field = original_tgt.target[PexEntryPointField] if entry_point_field.value is None: return InjectedDependencies() explicitly_provided_deps, entry_point = await MultiGet( Get(ExplicitlyProvidedDependencies, DependenciesRequest(original_tgt.target[Dependencies])), Get(ResolvedPexEntryPoint, ResolvePexEntryPointRequest(entry_point_field)), ) if entry_point.val is None: return InjectedDependencies() owners = await Get(PythonModuleOwners, PythonModule(entry_point.val.module)) address = original_tgt.target.address explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( owners.ambiguous, address, # If the entry point was specified as a file, like `app.py`, we know the module must # live in the pex_binary's directory or subdirectory, so the owners must be ancestors. owners_must_be_ancestors=entry_point.file_name_used, import_reference="module", context=( f"The pex_binary target {address} has the field " f"`entry_point={repr(entry_point_field.value.spec)}`, which " f"maps to the Python module `{entry_point.val.module}`" ), ) maybe_disambiguated = explicitly_provided_deps.disambiguated( owners.ambiguous, owners_must_be_ancestors=entry_point.file_name_used ) unambiguous_owners = owners.unambiguous or ( (maybe_disambiguated,) if maybe_disambiguated else () ) return InjectedDependencies(unambiguous_owners)
async def inject_cloud_function_handler_dependency( request: InjectPythonCloudFunctionHandlerDependency, python_infer_subsystem: PythonInferSubsystem, ) -> InjectedDependencies: if not python_infer_subsystem.entry_points: return InjectedDependencies() original_tgt = await Get(WrappedTarget, Address, request.dependencies_field.address) explicitly_provided_deps, handler = await MultiGet( Get(ExplicitlyProvidedDependencies, DependenciesRequest(original_tgt.target[Dependencies])), Get( ResolvedPythonGoogleHandler, ResolvePythonGoogleHandlerRequest( original_tgt.target[PythonGoogleCloudFunctionHandlerField]), ), ) module, _, _func = handler.val.partition(":") owners = await Get(PythonModuleOwners, PythonModule(module)) address = original_tgt.target.address explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( owners.ambiguous, address, # If the handler was specified as a file, like `app.py`, we know the module must # live in the python_google_cloud_function's directory or subdirectory, so the owners must be ancestors. owners_must_be_ancestors=handler.file_name_used, import_reference="module", context= (f"The python_google_cloud_function target {address} has the field " f"`handler={repr(original_tgt.target[PythonGoogleCloudFunctionHandlerField].value)}`, which maps " f"to the Python module `{module}`"), ) maybe_disambiguated = explicitly_provided_deps.disambiguated( owners.ambiguous, owners_must_be_ancestors=handler.file_name_used) unambiguous_owners = owners.unambiguous or ( (maybe_disambiguated, ) if maybe_disambiguated else ()) return InjectedDependencies(unambiguous_owners)
def test_create_module_from_path(stripped_path: PurePath, expected: str) -> None: assert PythonModule.create_from_stripped_path( stripped_path) == PythonModule(expected)
def get_owner(module: str) -> Optional[Address]: return self.request_single_product( PythonModuleOwner, Params(PythonModule(module), options_bootstrapper)).address
async def find_putative_targets( req: PutativePythonTargetsRequest, all_owned_sources: AllOwnedSources, python_setup: PythonSetup, ) -> PutativeTargets: # Find library/test/test_util targets. all_py_files_globs: PathGlobs = req.search_paths.path_globs("*.py") all_py_files = await Get(Paths, PathGlobs, all_py_files_globs) unowned_py_files = set(all_py_files.files) - set(all_owned_sources) classified_unowned_py_files = classify_source_files(unowned_py_files) pts = [] for tgt_type, paths in classified_unowned_py_files.items(): for dirname, filenames in group_by_dir(paths).items(): if issubclass(tgt_type, PythonTestsGeneratorTarget): name = "tests" kwargs = {"name": name} elif issubclass(tgt_type, PythonTestUtilsGeneratorTarget): name = "test_utils" kwargs = {"name": name} else: name = os.path.basename(dirname) kwargs = {} if ( python_setup.tailor_ignore_solitary_init_files and tgt_type == PythonSourcesGeneratorTarget and filenames == {"__init__.py"} ): continue pts.append( PutativeTarget.for_target_type( tgt_type, dirname, name, sorted(filenames), kwargs=kwargs ) ) if python_setup.tailor_requirements_targets: # Find requirements files. all_requirements_files_globs: PathGlobs = req.search_paths.path_globs("*requirements*.txt") all_requirements_files = await Get(Paths, PathGlobs, all_requirements_files_globs) unowned_requirements_files = set(all_requirements_files.files) - set(all_owned_sources) for req_file in unowned_requirements_files: path, name = os.path.split(req_file) pts.append( PutativeTarget( path=path, # python_requirements is a macro and doesn't take a name argument, but the # PutativeTarget still needs a name for display purposes. name=name, type_alias="python_requirements", triggering_sources=[req_file], owned_sources=[req_file], addressable=False, kwargs={} if name == "requirements.txt" else {"requirements_relpath": name}, ) ) if python_setup.tailor_pex_binary_targets: # Find binary targets. # Get all files whose content indicates that they are entry points. digest_contents = await Get(DigestContents, PathGlobs, all_py_files_globs) entry_points = [ file_content.path for file_content in digest_contents if is_entry_point(file_content.content) ] # Get the modules for these entry points. src_roots = await Get( SourceRootsResult, SourceRootsRequest, SourceRootsRequest.for_files(entry_points) ) module_to_entry_point = {} for entry_point in entry_points: entry_point_path = PurePath(entry_point) src_root = src_roots.path_to_root[entry_point_path] stripped_entry_point = entry_point_path.relative_to(src_root.path) module = PythonModule.create_from_stripped_path(stripped_entry_point) module_to_entry_point[module.module] = entry_point # Get existing binary targets for these entry points. entry_point_dirs = {os.path.dirname(entry_point) for entry_point in entry_points} possible_existing_binary_targets = await Get( UnexpandedTargets, AddressSpecs(AscendantAddresses(d) for d in entry_point_dirs) ) possible_existing_binary_entry_points = await MultiGet( Get(ResolvedPexEntryPoint, ResolvePexEntryPointRequest(t[PexEntryPointField])) for t in possible_existing_binary_targets if t.has_field(PexEntryPointField) ) possible_existing_entry_point_modules = { rep.val.module for rep in possible_existing_binary_entry_points if rep.val } unowned_entry_point_modules = ( module_to_entry_point.keys() - possible_existing_entry_point_modules ) # Generate new targets for entry points that don't already have one. for entry_point_module in unowned_entry_point_modules: entry_point = module_to_entry_point[entry_point_module] path, fname = os.path.split(entry_point) name = os.path.splitext(fname)[0] pts.append( PutativeTarget.for_target_type( target_type=PexBinary, path=path, name=name, triggering_sources=tuple(), kwargs={"name": name, "entry_point": fname}, ) ) return PutativeTargets(pts)
async def infer_python_dependencies_via_imports( request: InferPythonImportDependencies, python_infer_subsystem: PythonInferSubsystem, python_setup: PythonSetup, ) -> InferredDependencies: if not python_infer_subsystem.imports: return InferredDependencies([]) wrapped_tgt = await Get(WrappedTarget, Address, request.sources_field.address) explicitly_provided_deps, detected_imports = await MultiGet( Get(ExplicitlyProvidedDependencies, DependenciesRequest(wrapped_tgt.target[Dependencies])), Get( ParsedPythonImports, ParsePythonImportsRequest( cast(PythonSourceField, request.sources_field), InterpreterConstraints.create_from_targets( [wrapped_tgt.target], python_setup), string_imports=python_infer_subsystem.string_imports, string_imports_min_dots=python_infer_subsystem. string_imports_min_dots, ), ), ) owners_per_import = await MultiGet( Get(PythonModuleOwners, PythonModule(imported_module)) for imported_module in detected_imports) merged_result: set[Address] = set() unowned_imports: set[str] = set() address = wrapped_tgt.target.address for owners, imp in zip(owners_per_import, detected_imports): merged_result.update(owners.unambiguous) explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( owners.ambiguous, address, import_reference="module", context=f"The target {address} imports `{imp}`", ) maybe_disambiguated = explicitly_provided_deps.disambiguated( owners.ambiguous) if maybe_disambiguated: merged_result.add(maybe_disambiguated) if not owners.unambiguous and imp.split( ".")[0] not in DEFAULT_UNOWNED_DEPENDENCIES: unowned_imports.add(imp) unowned_dependency_behavior = python_infer_subsystem.unowned_dependency_behavior if unowned_imports and unowned_dependency_behavior is not UnownedDependencyUsage.DoNothing: raise_error = unowned_dependency_behavior is UnownedDependencyUsage.RaiseError log = logger.error if raise_error else logger.warning log(f"The following imports in {address} have no owners:\n\n{bullet_list(unowned_imports)}\n\n" "If you are expecting this import to be provided by your own firstparty code, ensure that it is contained within a source root. " "Otherwise if you are using a requirements file, consider adding the relevant package.\n" "Otherwise consider declaring a `python_requirement_library` target, which can then be inferred.\n" f"See {doc_url('python-third-party-dependencies')}") if raise_error: raise UnownedDependencyError( "One or more unowned dependencies detected. Check logs for more details." ) return InferredDependencies(sorted(merged_result))
def get_owners(module: str) -> List[Address]: return list( rule_runner.request(PythonModuleOwners, [PythonModule(module)]))
def get_owner(module: str) -> Optional[Address]: return self.request_product( PythonModuleOwner, [PythonModule(module), options_bootstrapper]).address
def test_module_address_spec() -> None: assert PythonModule("helloworld.app").address_spec( source_root=".") == AscendantAddresses(directory="helloworld/app") assert PythonModule("helloworld.app").address_spec( source_root="src/python") == AscendantAddresses( directory="src/python/helloworld/app")