• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 25443604553

06 May 2026 03:05PM UTC coverage: 92.879% (-0.04%) from 92.915%
25443604553

push

github

web-flow
[pants_ng] Scaffolding for a pants_ng mode. (#23319)

In this mode the command line is parsed as an
NG invocation, and dispatched appropriately.

Of course at the moment there are no
implementations to dispatch to. That will follow.

This does expose a new option, `pants_ng` to users. 
There is a big warning not to set it, but we're not trying
to hide that we're working on a new thing, so I am
comfortable with this.

25 of 76 new or added lines in 9 files covered. (32.89%)

1294 existing lines in 76 files now uncovered.

92234 of 99306 relevant lines covered (92.88%)

4.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.77
/src/python/pants/backend/python/framework/django/dependency_inference.py
1
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

UNCOV
4
import json
1✔
UNCOV
5
from pathlib import PurePath
1✔
6

UNCOV
7
from pants.backend.python import dependency_inference
1✔
UNCOV
8
from pants.backend.python.dependency_inference.parse_python_dependencies import (
1✔
9
    ExplicitPythonDependencies,
10
    ParsedPythonAssetPaths,
11
    ParsedPythonImportInfo,
12
    ParsedPythonImports,
13
    PythonFileDependencies,
14
)
UNCOV
15
from pants.backend.python.dependency_inference.rules import (
1✔
16
    ImportOwnerStatus,
17
    PythonImportDependenciesInferenceFieldSet,
18
    ResolvedParsedPythonDependenciesRequest,
19
    resolve_parsed_dependencies,
20
)
UNCOV
21
from pants.backend.python.framework.django.detect_apps import DjangoApps
1✔
UNCOV
22
from pants.backend.python.subsystems.setup import PythonSetup
1✔
UNCOV
23
from pants.backend.python.target_types import EntryPoint
1✔
UNCOV
24
from pants.backend.python.util_rules import pex
1✔
UNCOV
25
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
1✔
UNCOV
26
from pants.backend.python.util_rules.pex import PexRequest, VenvPexProcess, create_venv_pex
1✔
UNCOV
27
from pants.base.specs import FileGlobSpec, RawSpecs
1✔
UNCOV
28
from pants.core.util_rules.source_files import SourceFilesRequest
1✔
UNCOV
29
from pants.core.util_rules.stripped_source_files import strip_source_roots
1✔
UNCOV
30
from pants.engine.fs import CreateDigest, FileContent
1✔
UNCOV
31
from pants.engine.internals.graph import resolve_targets
1✔
UNCOV
32
from pants.engine.intrinsics import create_digest
1✔
UNCOV
33
from pants.engine.process import execute_process_or_raise
1✔
UNCOV
34
from pants.engine.rules import collect_rules, implicitly, rule
1✔
UNCOV
35
from pants.engine.target import InferDependenciesRequest, InferredDependencies
1✔
UNCOV
36
from pants.engine.unions import UnionRule
1✔
UNCOV
37
from pants.util.logging import LogLevel
1✔
UNCOV
38
from pants.util.resources import read_resource
1✔
39

40

UNCOV
41
class InferDjangoDependencies(InferDependenciesRequest):
1✔
UNCOV
42
    infer_from = PythonImportDependenciesInferenceFieldSet
1✔
43

44

UNCOV
45
_visitor_resource = "scripts/dependency_visitor.py"
1✔
UNCOV
46
_implicit_dependency_packages = (
1✔
47
    "management.commands",
48
    "migrations",
49
)
50

51

UNCOV
52
async def _django_migration_dependencies(
1✔
53
    request: InferDjangoDependencies,
54
    python_setup: PythonSetup,
55
    django_apps: DjangoApps,
56
) -> InferredDependencies:
UNCOV
57
    stripped_sources = await strip_source_roots(
1✔
58
        **implicitly(SourceFilesRequest([request.field_set.source]))
59
    )
UNCOV
60
    assert len(stripped_sources.snapshot.files) == 1
1✔
61

UNCOV
62
    file_content = FileContent("__visitor.py", read_resource(__name__, _visitor_resource))
1✔
UNCOV
63
    visitor_digest = await create_digest(CreateDigest([file_content]))
1✔
UNCOV
64
    venv_pex = await create_venv_pex(
1✔
65
        **implicitly(
66
            PexRequest(
67
                output_filename="__visitor.pex",
68
                internal_only=True,
69
                main=EntryPoint("__visitor"),
70
                interpreter_constraints=InterpreterConstraints.create_from_field_sets(
71
                    [request.field_set], python_setup=python_setup
72
                ),
73
                sources=visitor_digest,
74
            )
75
        )
76
    )
UNCOV
77
    process_result = await execute_process_or_raise(
1✔
78
        **implicitly(
79
            VenvPexProcess(
80
                venv_pex,
81
                argv=[stripped_sources.snapshot.files[0]],
82
                description=f"Determine Django app dependencies for {request.field_set.address}",
83
                input_digest=stripped_sources.snapshot.digest,
84
                level=LogLevel.DEBUG,
85
            )
86
        )
87
    )
88
    # See in script for where we explicitly encoded as utf8. Even though utf8 is the
89
    # default for decode(), we make that explicit here for emphasis.
UNCOV
90
    process_output = process_result.stdout.decode("utf8") or "{}"
1✔
UNCOV
91
    modules = [
1✔
92
        f"{django_apps.label_to_name[label]}.migrations.{migration}"
93
        for label, migration in json.loads(process_output)
94
        if label in django_apps.label_to_name
95
    ]
UNCOV
96
    resolve = request.field_set.resolve.normalized_value(python_setup)
1✔
97

UNCOV
98
    resolved_dependencies = await resolve_parsed_dependencies(
1✔
99
        ResolvedParsedPythonDependenciesRequest(
100
            request.field_set,
101
            PythonFileDependencies(
102
                ParsedPythonImports(
103
                    (module, ParsedPythonImportInfo(0, False)) for module in modules
104
                ),
105
                ParsedPythonAssetPaths(),
106
                ExplicitPythonDependencies(),
107
            ),
108
            resolve,
109
        ),
110
        **implicitly(),
111
    )
112

UNCOV
113
    return InferredDependencies(
1✔
114
        sorted(
115
            address
116
            for result in resolved_dependencies.resolve_results.values()
117
            if result.status in (ImportOwnerStatus.unambiguous, ImportOwnerStatus.disambiguated)
118
            for address in result.address
119
        )
120
    )
121

122

UNCOV
123
async def _django_app_implicit_dependencies(
1✔
124
    request: InferDjangoDependencies,
125
    python_setup: PythonSetup,
126
    django_apps: DjangoApps,
127
) -> InferredDependencies:
UNCOV
128
    file_path = request.field_set.source.file_path
1✔
UNCOV
129
    apps = [
1✔
130
        django_app for django_app in django_apps.values() if django_app.config_file == file_path
131
    ]
UNCOV
132
    if not apps:
1✔
133
        return InferredDependencies([])
×
134

UNCOV
135
    app_package = apps[0].name
1✔
136

UNCOV
137
    implicit_dependency_packages = [
1✔
138
        f"{app_package}.{subpackage}" for subpackage in _implicit_dependency_packages
139
    ]
140

UNCOV
141
    resolve = request.field_set.resolve.normalized_value(python_setup)
1✔
142

UNCOV
143
    resolved_dependencies = await resolve_parsed_dependencies(
1✔
144
        ResolvedParsedPythonDependenciesRequest(
145
            request.field_set,
146
            PythonFileDependencies(
147
                ParsedPythonImports(
148
                    (package, ParsedPythonImportInfo(0, False))
149
                    for package in implicit_dependency_packages
150
                ),
151
                ParsedPythonAssetPaths(),
152
                ExplicitPythonDependencies(),
153
            ),
154
            resolve,
155
        ),
156
        **implicitly(),
157
    )
158

UNCOV
159
    spec_paths = [
1✔
160
        address.spec_path
161
        for result in resolved_dependencies.resolve_results.values()
162
        if result.status in (ImportOwnerStatus.unambiguous, ImportOwnerStatus.disambiguated)
163
        for address in result.address
164
    ]
165

UNCOV
166
    targets = await resolve_targets(
1✔
167
        **implicitly(
168
            RawSpecs.create(
169
                specs=[FileGlobSpec(f"{spec_path}/*.py") for spec_path in spec_paths],
170
                description_of_origin="Django implicit dependency detection",
171
            )
172
        )
173
    )
174

UNCOV
175
    return InferredDependencies(sorted(target.address for target in targets))
1✔
176

177

UNCOV
178
@rule
1✔
UNCOV
179
async def infer_django_dependencies(
1✔
180
    request: InferDjangoDependencies,
181
    python_setup: PythonSetup,
182
    django_apps: DjangoApps,
183
) -> InferredDependencies:
UNCOV
184
    source_field = request.field_set.source
1✔
185
    # NB: This doesn't consider https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-MIGRATION_MODULES
UNCOV
186
    path = PurePath(source_field.file_path)
1✔
UNCOV
187
    if path.match("migrations/*.py"):
1✔
UNCOV
188
        return await _django_migration_dependencies(request, python_setup, django_apps)
1✔
UNCOV
189
    elif path.match("apps.py"):
1✔
UNCOV
190
        return await _django_app_implicit_dependencies(request, python_setup, django_apps)
1✔
191
    else:
192
        return InferredDependencies([])
×
193

194

UNCOV
195
def rules():
1✔
UNCOV
196
    return (
1✔
197
        *collect_rules(),
198
        *pex.rules(),
199
        *dependency_inference.rules.rules(),
200
        UnionRule(InferDependenciesRequest, InferDjangoDependencies),
201
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc