Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ def rules():
If your publisher has expensive packaging steps, you can use `check_skip_request` to preemptively skip packaging when you know the publish will be skipped:

```python
from pants.core.goals.package import PackageFieldSet
from pants.core.goals.publish import CheckSkipRequest, CheckSkipResult
from pants.core.goals import package


@dataclass(frozen=True)
Expand All @@ -174,7 +174,7 @@ class MyPublishFieldSet(PublishFieldSet):
repositories: MyRepositoriesField
skip_push: MySkipPushField

def check_skip_request(self, package_fs: package.PackageFieldSet) -> CheckSkipRequest[Self] | None:
def check_skip_request(self, package_fs: PackageFieldSet) -> CheckSkipRequest[Self] | None:
"""Return a request to check if we should skip packaging."""
return MyPublishSkipRequest(publish_fs=self, package_fs=package_fs)
```
Expand Down
23 changes: 15 additions & 8 deletions docs/docs/writing-plugins/common-plugin-tasks/add-codegen.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ Instead of users having to explicitly add this dependency every time, you can dy
To inject dependencies:

1. Subclass the `Dependencies` field. Register this subclass on your protocol target type.
2. Define a subclass of `InjectDependenciesRequest` and set the class property `inject_for` to the `Dependencies` subclass defined in the previous step. Register this new class with a [`UnionRule`](../the-rules-api/union-rules-advanced.mdx) for `InjectDependenciesRequest`.
3. Create a new rule that takes your new `InjectDependenciesRequest` subclass as a parameter and returns `InjectedDependencies`.
2. Define a `FieldSet` subclass that includes the `Dependencies` subclass and any other fields needed for inference.
3. Define a subclass of `InferDependenciesRequest` and set the class property `infer_from` to the `FieldSet` subclass defined in the previous step. Register this new class with a [`UnionRule`](../the-rules-api/union-rules-advanced.mdx) for `InferDependenciesRequest`.
4. Create a new rule that takes your new `InferDependenciesRequest` subclass as a parameter and returns `InferredDependencies`.

```python
from pants.engine.addresses import Address
from pants.engine.target import Dependencies, InjectDependenciesRequest, InjectedDependencies
from pants.engine.target import Dependencies, FieldSet, InferDependenciesRequest, InferredDependencies
from pants.engine.rules import collect_rules, rule
from pants.engine.unions import UnionRule

Expand All @@ -72,18 +73,24 @@ class ProtobufSourceTarget(Target):
alias = "protobuf_source"
core_fields = (*COMMON_TARGET_FIELDS, ProtobufDependencies, ProtobufSourceField)

class InjectProtobufDependencies(InjectDependenciesRequest):
inject_for = ProtobufDependencies
class ProtobufDependenciesInferenceFieldSet(FieldSet):
required_fields = (ProtobufDependencies,)
dependencies: ProtobufDependencies

class InferProtobufDependencies(InferDependenciesRequest):
infer_from = ProtobufDependenciesInferenceFieldSet

@rule
async def inject_dependencies(_: InjectProtobufDependencies) -> InjectedDependencies:
async def infer_protobuf_dependencies(
request: InferProtobufDependencies
) -> InferredDependencies:
address = Address("3rdparty/python", target_name="protobuf")
return InjectedDependencies([address])
return InferredDependencies([address])

def rules():
return [
*collect_rules(),
UnionRule(InjectDependenciesRequest, InjectProtobufDependencies),
UnionRule(InferDependenciesRequest, InferProtobufDependencies),
]
```

Expand Down
18 changes: 8 additions & 10 deletions docs/docs/writing-plugins/the-rules-api/testing-plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ We can write this test:
from pants.engine.addresses import Address
from pants.engine.fs import EMPTY_DIGEST, Snapshot
from pants.engine.internals.graph import hydrate_sources
from pants.engine.target import HydratedSources, HydrateSourcesRequest, Target, Sources
from pants.engine.target import HydratedSources, HydrateSourcesRequest, MultipleSourcesField, Target
from pants.testutil.rule_runner import run_rule_with_mocks

class MockTarget(Target):
alias = "mock_target"
core_fields = (Sources,)
core_fields = (MultipleSourcesField,)


def test_find_needle_in_haystack() -> None:
Expand Down Expand Up @@ -442,24 +442,22 @@ Given this rule signature for running the linter Bandit:

```python
@rule
async def bandit_lint(
request: BanditRequest, bandit: Bandit, python_setup: PythonSetup
) -> LintResults:
async def bandit_lint(batch: BanditRequest.Batch) -> LintResult:
...
```

We can write a test like this:

```python
from pants.core.goals.lint import LintResult, LintResults
from pants.core.goals.lint import LintResult
from pants.testutil.rule_runner import QueryRule, RuleRunner

@pytest.fixture
def rule_runner() -> RuleRunner:
return RuleRunner(
rules=[
*bandit_rules(),
QueryRule(LintResults, [BanditRequest]),
QueryRule(LintResult, [BanditRequest.Batch]),
],
target_types=[PythonSourceTarget]
)
Expand All @@ -470,11 +468,11 @@ def test_bandit(rule_runner: RuleRunner) -> None:
...

# Run Bandit rule.
bandit_request = BanditRequest(...)
lint_results = rule_runner.request(LintResults, [bandit_request])
bandit_batch = BanditRequest.Batch(...)
lint_result = rule_runner.request(LintResult, [bandit_batch])
```

Note that our `@rule` takes 3 parameters, but we only explicitly included `BanditRequest` in the inputs. This is possible because the engine knows how to compute all [Subsystems](./options-and-subsystems.mdx) based on the initial input to the graph. See [Concepts](./concepts.mdx).
Note that the lint rule now takes a `Batch` object as input. The engine knows how to compute all [Subsystems](./options-and-subsystems.mdx) based on the initial input to the graph. See [Concepts](./concepts.mdx).

We are happy [to help](/community/members) figure out what rules to register, and what inputs to pass to `rule_runner.request()`. It can also help to [visualize the rule graph](./tips-and-debugging.mdx) when running your code in production. If you're missing an input that you need, the engine will error explaining that there is no way to compute your `OutputType`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ class LintTargetsRequest(ABC):


@dataclass(frozen=True)
class LintResults:
class LintResult:
...


@rule(polymorphic=True)
async def lint_target(req: LintTargetsRequest) -> LintResults:
async def lint_target(req: LintTargetsRequest) -> LintResult:
# If no implementation for the member type is found, this generic
# implementation will be invoked. In this case that is not useful,
# so we raise.
Expand All @@ -63,7 +63,7 @@ async def lint(targets: Targets, union_membership: UnionMembership) -> Lint:
```

```python title="pants-plugins/bash/shellcheck.py"
from pants.core.goals.lint import LintResults, LintTargetsRequest
from pants.core.goals.lint import LintResult, LintTargetsRequest
from pants.engine.rules import collect_rules, rule


Expand All @@ -75,7 +75,7 @@ class ShellcheckRequest(LintTargetsRequest):


@rule
async def shellcheck_target(req: ShellcheckRequest) -> LintResults:
async def shellcheck_target(req: ShellcheckRequest) -> LintResult:
# At runtime, calls to the generic `lint_target()` on a
# `ShellcheckRequest` will be dispatched here.
...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ For example, you might like how `pex_binary` behaves in general, but you have a
Rather than subclassing the original target type, use this pattern:

```python
from pants.backend.python.target_types import PexBinaryTarget, PexEntryPointField
from pants.backend.python.target_types import PexBinary, PexEntryPointField
from pants.engine.target import Target
from pants.util.ordered_set import FrozenOrderedSet

Expand All @@ -81,12 +81,12 @@ class DjangoEntryPointField(PexEntryPointField):
class DjangoManagePyTarget(Target):
alias = "django_manage_py"
core_fields = (
*(FrozenOrderedSet(PexBinaryTarget.core_fields) - {PexEntryPoint}),
*(FrozenOrderedSet(PexBinary.core_fields) - {PexEntryPointField}),
DjangoEntryPointField,
)
```

In this example, we register all of the fields of `PexBinaryTarget`, except for the field `PexEntryPoint `. We instead register our custom field `DjangoEntryPointField `.
In this example, we register all of the fields of `PexBinary`, except for the field `PexEntryPointField`. We instead register our custom field `DjangoEntryPointField`.
:::

## Step 2: Register the target type in `register.py`
Expand Down