Skip to content

Commit 78962d6

Browse files
authored
[Call-by-name] Migrated django framework (#22007)
This is a PR for the call-by-name migration on the Django backend.
1 parent 5e7123a commit 78962d6

File tree

3 files changed

+91
-97
lines changed

3 files changed

+91
-97
lines changed

docs/notes/2.26.x.md

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Pants has a new mechanism for `@rule` invocation in backends. In this release th
3333
- [autoflake](https://www.pantsbuild.org/stable/reference/subsystems/autoflake)
3434
- [bandit](https://www.pantsbuild.org/stable/reference/subsystems/bandit)
3535
- [black](https://www.pantsbuild.org/stable/reference/subsystems/black)
36+
- [django](https://www.pantsbuild.org/dev/docs/using-pants/key-concepts/backends#available-experimental-backends)
3637
- [docformatter](https://www.pantsbuild.org/stable/reference/subsystems/docformatter)
3738
- [flake8](https://www.pantsbuild.org/stable/reference/subsystems/flake8)
3839
- [isort](https://www.pantsbuild.org/stable/reference/subsystems/isort)

src/python/pants/backend/python/framework/django/dependency_inference.py

+46-44
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,24 @@
1414
from pants.backend.python.dependency_inference.rules import (
1515
ImportOwnerStatus,
1616
PythonImportDependenciesInferenceFieldSet,
17-
ResolvedParsedPythonDependencies,
1817
ResolvedParsedPythonDependenciesRequest,
18+
resolve_parsed_dependencies,
1919
)
2020
from pants.backend.python.framework.django.detect_apps import DjangoApps
2121
from pants.backend.python.subsystems.setup import PythonSetup
2222
from pants.backend.python.target_types import EntryPoint
2323
from pants.backend.python.util_rules import pex
2424
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
25-
from pants.backend.python.util_rules.pex import PexRequest, VenvPex, VenvPexProcess
25+
from pants.backend.python.util_rules.pex import PexRequest, VenvPexProcess, create_venv_pex
2626
from pants.base.specs import FileGlobSpec, RawSpecs
2727
from pants.core.util_rules.source_files import SourceFilesRequest
28-
from pants.core.util_rules.stripped_source_files import StrippedSourceFiles
28+
from pants.core.util_rules.stripped_source_files import strip_source_roots
2929
from pants.engine.fs import CreateDigest, FileContent
30-
from pants.engine.internals.native_engine import Digest
31-
from pants.engine.internals.selectors import Get
32-
from pants.engine.process import ProcessResult
33-
from pants.engine.rules import collect_rules, rule
34-
from pants.engine.target import InferDependenciesRequest, InferredDependencies, Targets
30+
from pants.engine.internals.graph import resolve_targets
31+
from pants.engine.intrinsics import create_digest
32+
from pants.engine.process import execute_process_or_raise
33+
from pants.engine.rules import collect_rules, implicitly, rule
34+
from pants.engine.target import InferDependenciesRequest, InferredDependencies
3535
from pants.engine.unions import UnionRule
3636
from pants.util.logging import LogLevel
3737
from pants.util.resources import read_resource
@@ -53,34 +53,36 @@ async def _django_migration_dependencies(
5353
python_setup: PythonSetup,
5454
django_apps: DjangoApps,
5555
) -> InferredDependencies:
56-
stripped_sources = await Get(
57-
StrippedSourceFiles, SourceFilesRequest([request.field_set.source])
56+
stripped_sources = await strip_source_roots(
57+
**implicitly(SourceFilesRequest([request.field_set.source]))
5858
)
5959
assert len(stripped_sources.snapshot.files) == 1
6060

6161
file_content = FileContent("__visitor.py", read_resource(__name__, _visitor_resource))
62-
visitor_digest = await Get(Digest, CreateDigest([file_content]))
63-
venv_pex = await Get(
64-
VenvPex,
65-
PexRequest(
66-
output_filename="__visitor.pex",
67-
internal_only=True,
68-
main=EntryPoint("__visitor"),
69-
interpreter_constraints=InterpreterConstraints.create_from_compatibility_fields(
70-
[request.field_set.interpreter_constraints], python_setup=python_setup
71-
),
72-
sources=visitor_digest,
73-
),
62+
visitor_digest = await create_digest(CreateDigest([file_content]))
63+
venv_pex = await create_venv_pex(
64+
**implicitly(
65+
PexRequest(
66+
output_filename="__visitor.pex",
67+
internal_only=True,
68+
main=EntryPoint("__visitor"),
69+
interpreter_constraints=InterpreterConstraints.create_from_compatibility_fields(
70+
[request.field_set.interpreter_constraints], python_setup=python_setup
71+
),
72+
sources=visitor_digest,
73+
)
74+
)
7475
)
75-
process_result = await Get(
76-
ProcessResult,
77-
VenvPexProcess(
78-
venv_pex,
79-
argv=[stripped_sources.snapshot.files[0]],
80-
description=f"Determine Django app dependencies for {request.field_set.address}",
81-
input_digest=stripped_sources.snapshot.digest,
82-
level=LogLevel.DEBUG,
83-
),
76+
process_result = await execute_process_or_raise(
77+
**implicitly(
78+
VenvPexProcess(
79+
venv_pex,
80+
argv=[stripped_sources.snapshot.files[0]],
81+
description=f"Determine Django app dependencies for {request.field_set.address}",
82+
input_digest=stripped_sources.snapshot.digest,
83+
level=LogLevel.DEBUG,
84+
)
85+
)
8486
)
8587
# See in script for where we explicitly encoded as utf8. Even though utf8 is the
8688
# default for decode(), we make that explicit here for emphasis.
@@ -92,8 +94,7 @@ async def _django_migration_dependencies(
9294
]
9395
resolve = request.field_set.resolve.normalized_value(python_setup)
9496

95-
resolved_dependencies = await Get(
96-
ResolvedParsedPythonDependencies,
97+
resolved_dependencies = await resolve_parsed_dependencies(
9798
ResolvedParsedPythonDependenciesRequest(
9899
request.field_set,
99100
ParsedPythonDependencies(
@@ -104,6 +105,7 @@ async def _django_migration_dependencies(
104105
),
105106
resolve,
106107
),
108+
**implicitly(),
107109
)
108110

109111
return InferredDependencies(
@@ -136,8 +138,7 @@ async def _django_app_implicit_dependencies(
136138

137139
resolve = request.field_set.resolve.normalized_value(python_setup)
138140

139-
resolved_dependencies = await Get(
140-
ResolvedParsedPythonDependencies,
141+
resolved_dependencies = await resolve_parsed_dependencies(
141142
ResolvedParsedPythonDependenciesRequest(
142143
request.field_set,
143144
ParsedPythonDependencies(
@@ -149,6 +150,7 @@ async def _django_app_implicit_dependencies(
149150
),
150151
resolve,
151152
),
153+
**implicitly(),
152154
)
153155

154156
spec_paths = [
@@ -158,13 +160,13 @@ async def _django_app_implicit_dependencies(
158160
for address in result.address
159161
]
160162

161-
targets = await Get(
162-
Targets,
163-
RawSpecs,
164-
RawSpecs.create(
165-
specs=[FileGlobSpec(f"{spec_path}/*.py") for spec_path in spec_paths],
166-
description_of_origin="Django implicit dependency detection",
167-
),
163+
targets = await resolve_targets(
164+
**implicitly(
165+
RawSpecs.create(
166+
specs=[FileGlobSpec(f"{spec_path}/*.py") for spec_path in spec_paths],
167+
description_of_origin="Django implicit dependency detection",
168+
)
169+
)
168170
)
169171

170172
return InferredDependencies(sorted(target.address for target in targets))
@@ -188,9 +190,9 @@ async def infer_django_dependencies(
188190

189191

190192
def rules():
191-
return [
193+
return (
192194
*collect_rules(),
193195
*pex.rules(),
194196
*dependency_inference.rules.rules(),
195197
UnionRule(InferDependenciesRequest, InferDjangoDependencies),
196-
]
198+
)

src/python/pants/backend/python/framework/django/detect_apps.py

+44-53
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,22 @@
55
import json
66
import os
77
from collections import defaultdict
8+
from collections.abc import Iterable
89
from dataclasses import dataclass
910

1011
from pants.backend.python.subsystems.setup import PythonSetup
1112
from pants.backend.python.target_types import InterpreterConstraintsField
1213
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
13-
from pants.backend.python.util_rules.pex_environment import PythonExecutable
14+
from pants.backend.python.util_rules.pex import find_interpreter
1415
from pants.base.glob_match_error_behavior import GlobMatchErrorBehavior
1516
from pants.base.specs import FileGlobSpec, RawSpecs
16-
from pants.engine.fs import AddPrefix, CreateDigest, Digest, FileContent, MergeDigests
17-
from pants.engine.internals.selectors import Get, MultiGet
18-
from pants.engine.process import Process, ProcessResult
19-
from pants.engine.rules import collect_rules, rule
20-
from pants.engine.target import (
21-
HydratedSources,
22-
HydrateSourcesRequest,
23-
SourcesField,
24-
Target,
25-
Targets,
26-
)
17+
from pants.engine.fs import AddPrefix, CreateDigest, FileContent, MergeDigests
18+
from pants.engine.internals.graph import hydrate_sources, resolve_targets
19+
from pants.engine.internals.selectors import concurrently
20+
from pants.engine.intrinsics import add_prefix, create_digest, merge_digests
21+
from pants.engine.process import Process, execute_process_or_raise
22+
from pants.engine.rules import Rule, collect_rules, implicitly, rule
23+
from pants.engine.target import HydrateSourcesRequest, SourcesField, Target
2724
from pants.util.frozendict import FrozenDict
2825
from pants.util.resources import read_resource
2926

@@ -43,7 +40,7 @@ def label_to_name(self) -> FrozenDict[str, str]:
4340
def label_to_file(self) -> FrozenDict[str, str]:
4441
return FrozenDict((label, app.config_file) for label, app in self.items())
4542

46-
def add_from_json(self, json_bytes: bytes, strip_prefix="") -> DjangoApps:
43+
def add_from_json(self, json_bytes: bytes, strip_prefix: str = "") -> DjangoApps:
4744
json_dict: dict[str, dict[str, str]] = json.loads(json_bytes.decode())
4845
apps = {
4946
label: DjangoApp(
@@ -85,22 +82,22 @@ async def detect_django_apps(python_setup: PythonSetup) -> DjangoApps:
8582
# be dep-inferred as a whole via the full package path in settings.py anyway.
8683
# In the future we may find a way to map third-party apps here as well.
8784
django_apps = DjangoApps(FrozenDict())
88-
targets = await Get(
89-
Targets,
90-
RawSpecs,
91-
RawSpecs.create(
92-
specs=[FileGlobSpec("**/apps.py")],
93-
description_of_origin="Django app detection",
94-
unmatched_glob_behavior=GlobMatchErrorBehavior.ignore,
95-
),
85+
targets = await resolve_targets(
86+
**implicitly(
87+
RawSpecs.create(
88+
specs=[FileGlobSpec("**/apps.py")],
89+
description_of_origin="Django app detection",
90+
unmatched_glob_behavior=GlobMatchErrorBehavior.ignore,
91+
)
92+
)
9693
)
9794
if not targets:
9895
return django_apps
9996

10097
script_file_content = FileContent(
10198
"script/__visitor.py", read_resource(__name__, _script_resource)
10299
)
103-
script_digest = await Get(Digest, CreateDigest([script_file_content]))
100+
script_digest = await create_digest(CreateDigest([script_file_content]))
104101
apps_sandbox_prefix = "_apps_to_detect"
105102

106103
# Partition by ICs, so we can run the detector on the appropriate interpreter.
@@ -112,34 +109,30 @@ async def detect_django_apps(python_setup: PythonSetup) -> DjangoApps:
112109
ics_to_tgts[ics].append(tgt)
113110

114111
for ics, tgts in ics_to_tgts.items():
115-
sources = await MultiGet( # noqa: PNT30: requires triage
116-
[Get(HydratedSources, HydrateSourcesRequest(tgt[SourcesField])) for tgt in tgts]
117-
)
118-
apps_digest = await Get( # noqa: PNT30: requires triage
119-
Digest, MergeDigests([src.snapshot.digest for src in sources])
112+
sources = await concurrently( # noqa: PNT30: requires triage
113+
[
114+
hydrate_sources(HydrateSourcesRequest(tgt[SourcesField]), **implicitly())
115+
for tgt in tgts
116+
]
120117
)
121-
prefixed_apps_digest = await Get( # noqa: PNT30: requires triage
122-
Digest, AddPrefix(apps_digest, apps_sandbox_prefix)
123-
)
124-
125-
input_digest = await Get( # noqa: PNT30: requires triage
126-
Digest, MergeDigests([prefixed_apps_digest, script_digest])
127-
)
128-
python_interpreter = await Get( # noqa: PNT30: requires triage
129-
PythonExecutable, InterpreterConstraints, ics
130-
)
131-
132-
process_result = await Get( # noqa: PNT30: requires triage
133-
ProcessResult,
134-
Process(
135-
argv=[
136-
python_interpreter.path,
137-
script_file_content.path,
138-
apps_sandbox_prefix,
139-
],
140-
input_digest=input_digest,
141-
description="Detect Django apps",
142-
),
118+
apps_digest = await merge_digests(MergeDigests([src.snapshot.digest for src in sources]))
119+
prefixed_apps_digest = await add_prefix(AddPrefix(apps_digest, apps_sandbox_prefix))
120+
121+
input_digest = await merge_digests(MergeDigests([prefixed_apps_digest, script_digest]))
122+
python_interpreter = await find_interpreter(ics, **implicitly())
123+
124+
process_result = await execute_process_or_raise(
125+
**implicitly(
126+
Process(
127+
argv=[
128+
python_interpreter.path,
129+
script_file_content.path,
130+
apps_sandbox_prefix,
131+
],
132+
input_digest=input_digest,
133+
description="Detect Django apps",
134+
)
135+
)
143136
)
144137
django_apps = django_apps.add_from_json(
145138
process_result.stdout or b"{}", strip_prefix=apps_sandbox_prefix
@@ -148,7 +141,5 @@ async def detect_django_apps(python_setup: PythonSetup) -> DjangoApps:
148141
return django_apps
149142

150143

151-
def rules():
152-
return [
153-
*collect_rules(),
154-
]
144+
def rules() -> Iterable[Rule]:
145+
return collect_rules()

0 commit comments

Comments
 (0)