Example #1
0
 def main(self) -> MainSpecification:
     is_default_console_script = self.options.is_default("console_script")
     is_default_entry_point = self.options.is_default("entry_point")
     if not is_default_console_script and not is_default_entry_point:
         raise OptionsError(
             f"Both [{self.scope}].console-script={self.options.console_script} and "
             f"[{self.scope}].entry-point={self.options.entry_point} are configured but these "
             f"options are mutually exclusive. Please pick one.")
     if not is_default_console_script:
         return ConsoleScript(cast(str, self.options.console_script))
     if not is_default_entry_point:
         return EntryPoint.parse(cast(str, self.options.entry_point))
     return self.default_main
Example #2
0
def test_resolve_pex_binary_entry_point() -> None:
    rule_runner = RuleRunner(
        rules=[
            resolve_pex_entry_point,
            QueryRule(ResolvedPexEntryPoint, [ResolvePexEntryPointRequest]),
        ]
    )

    def assert_resolved(
        *, entry_point: str | None, expected: EntryPoint | None, is_file: bool
    ) -> None:
        addr = Address("src/python/project")
        rule_runner.write_files(
            {
                "src/python/project/app.py": "",
                "src/python/project/f2.py": "",
            }
        )
        ep_field = PexEntryPointField(entry_point, addr)
        result = rule_runner.request(ResolvedPexEntryPoint, [ResolvePexEntryPointRequest(ep_field)])
        assert result.val == expected
        assert result.file_name_used == is_file

    # Full module provided.
    assert_resolved(
        entry_point="custom.entry_point", expected=EntryPoint("custom.entry_point"), is_file=False
    )
    assert_resolved(
        entry_point="custom.entry_point:func",
        expected=EntryPoint.parse("custom.entry_point:func"),
        is_file=False,
    )

    # File names are expanded into the full module path.
    assert_resolved(entry_point="app.py", expected=EntryPoint(module="project.app"), is_file=True)
    assert_resolved(
        entry_point="app.py:func",
        expected=EntryPoint(module="project.app", function="func"),
        is_file=True,
    )

    with pytest.raises(ExecutionError):
        assert_resolved(
            entry_point="doesnt_exist.py", expected=EntryPoint("doesnt matter"), is_file=True
        )
    # Resolving >1 file is an error.
    with pytest.raises(ExecutionError):
        assert_resolved(entry_point="*.py", expected=EntryPoint("doesnt matter"), is_file=True)
Example #3
0
 def main(self) -> MainSpecification:
     is_default_console_script = self.options.is_default("console_script")
     is_default_entry_point = self.options.is_default("entry_point")
     if not is_default_console_script and not is_default_entry_point:
         raise OptionsError(
             softwrap(f"""
                 Both [{self.options_scope}].console-script={self.console_script} and
                 [{self.options_scope}].entry-point={self.entry_point} are configured
                 but these options are mutually exclusive. Please pick one.
                 """))
     if not is_default_console_script:
         assert self.console_script is not None
         return ConsoleScript(self.console_script)
     if not is_default_entry_point:
         assert self.entry_point is not None
         return EntryPoint.parse(self.entry_point)
     return self.default_main
Example #4
0
def test_resolve_pex_binary_entry_point() -> None:
    rule_runner = RuleRunner(rules=[
        resolve_pex_entry_point,
        QueryRule(ResolvedPexEntryPoint, [ResolvePexEntryPointRequest]),
    ])

    def assert_resolved(*, entry_point: Optional[str],
                        expected: Optional[EntryPoint]) -> None:
        addr = Address("src/python/project")
        rule_runner.create_file("src/python/project/app.py")
        rule_runner.create_file("src/python/project/f2.py")
        ep_field = PexEntryPointField(entry_point, address=addr)
        result = rule_runner.request(ResolvedPexEntryPoint,
                                     [ResolvePexEntryPointRequest(ep_field)])
        assert result.val == expected

    # Full module provided.
    assert_resolved(entry_point="custom.entry_point",
                    expected=EntryPoint("custom.entry_point"))
    assert_resolved(entry_point="custom.entry_point:func",
                    expected=EntryPoint.parse("custom.entry_point:func"))

    # File names are expanded into the full module path.
    assert_resolved(entry_point="app.py",
                    expected=EntryPoint(module="project.app"))
    assert_resolved(entry_point="app.py:func",
                    expected=EntryPoint(module="project.app", function="func"))

    # We special case the strings `<none>` and `<None>`.
    assert_resolved(entry_point="<none>", expected=None)
    assert_resolved(entry_point="<None>", expected=None)

    with pytest.raises(ExecutionError):
        assert_resolved(entry_point="doesnt_exist.py",
                        expected=EntryPoint("doesnt matter"))
    # Resolving >1 file is an error.
    with pytest.raises(ExecutionError):
        assert_resolved(entry_point="*.py",
                        expected=EntryPoint("doesnt matter"))
Example #5
0
async def resolve_python_distribution_entry_points(
    request: ResolvePythonDistributionEntryPointsRequest,
) -> ResolvedPythonDistributionEntryPoints:
    if request.entry_points_field:
        if request.entry_points_field.value is None:
            return ResolvedPythonDistributionEntryPoints()
        address = request.entry_points_field.address
        all_entry_points = cast(_EntryPointsDictType, request.entry_points_field.value)

    elif request.provides_field:
        address = request.provides_field.address
        provides_field_value = cast(
            _EntryPointsDictType, request.provides_field.value.kwargs.get("entry_points") or {}
        )

        if provides_field_value:
            all_entry_points = provides_field_value
        else:
            return ResolvedPythonDistributionEntryPoints()
    else:
        return ResolvedPythonDistributionEntryPoints()

    classified_entry_points = list(_classify_entry_points(all_entry_points))

    # Pick out all target addresses up front, so we can use MultiGet later.
    #
    # This calls for a bit of trickery however (using the "y_by_x" mapping dicts), so we keep track
    # of which address belongs to which entry point. I.e. the `address_by_ref` and
    # `binary_entry_point_by_address` variables.

    target_refs = [
        entry_point_str for is_target, _, _, entry_point_str in classified_entry_points if is_target
    ]

    # Intermediate step, as Get(Targets) returns a deduplicated set.. which breaks in case of
    # multiple input refs that maps to the same target.
    target_addresses = await Get(
        Addresses,
        UnparsedAddressInputs(
            target_refs,
            owning_address=address,
            description_of_origin="TODO(#14468)",
        ),
    )
    address_by_ref = dict(zip(target_refs, target_addresses))
    targets = await Get(Targets, Addresses, target_addresses)

    # Check that we only have targets with a pex entry_point field.
    for target in targets:
        if not target.has_field(PexEntryPointField):
            raise InvalidEntryPoint(
                softwrap(
                    f"""
                    All target addresses in the entry_points field must be for pex_binary targets,
                    but the target {address} includes the value {target.address}, which has the
                    target type {target.alias}.

                    Alternatively, you can use a module like "project.app:main".
                    See {doc_url('python-distributions')}.
                    """
                )
            )

    binary_entry_points = await MultiGet(
        Get(
            ResolvedPexEntryPoint,
            ResolvePexEntryPointRequest(target[PexEntryPointField]),
        )
        for target in targets
    )
    binary_entry_point_by_address = {
        target.address: entry_point for target, entry_point in zip(targets, binary_entry_points)
    }

    entry_points: DefaultDict[str, Dict[str, PythonDistributionEntryPoint]] = defaultdict(dict)

    # Parse refs/replace with resolved pex entry point, and validate console entry points have function.
    for is_target, category, name, ref in classified_entry_points:
        owner: Optional[Address] = None
        if is_target:
            owner = address_by_ref[ref]
            entry_point = binary_entry_point_by_address[owner].val
            if entry_point is None:
                logger.warning(
                    softwrap(
                        f"""
                        The entry point {name} in {category} references a pex_binary target {ref}
                        which does not set `entry_point`. Skipping.
                        """
                    )
                )
                continue
        else:
            entry_point = EntryPoint.parse(ref, f"{name} for {address} {category}")

        if category in ["console_scripts", "gui_scripts"] and not entry_point.function:
            url = "https://python-packaging.readthedocs.io/en/latest/command-line-scripts.html#the-console-scripts-entry-point"
            raise InvalidEntryPoint(
                dedent(
                    f"""\
                Every entry point in `{category}` for {address} must end in the format `:my_func`,
                but {name} set it to {entry_point.spec!r}. For example, set
                `entry_points={{"{category}": {{"{name}": "{entry_point.module}:main}} }}`.
                See {url}.
                """
                )
            )

        entry_points[category][name] = PythonDistributionEntryPoint(entry_point, owner)

    return ResolvedPythonDistributionEntryPoints(
        FrozenDict(
            {category: FrozenDict(entry_points) for category, entry_points in entry_points.items()}
        )
    )