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

pantsbuild / pants / 18812500213

26 Oct 2025 03:42AM UTC coverage: 80.284% (+0.005%) from 80.279%
18812500213

Pull #22804

github

web-flow
Merge 2a56fdb46 into 4834308dc
Pull Request #22804: test_shell_command: use correct default cache scope for a test's environment

29 of 31 new or added lines in 2 files covered. (93.55%)

1314 existing lines in 64 files now uncovered.

77900 of 97030 relevant lines covered (80.28%)

3.35 hits per line

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

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

39

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

43

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

50

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

61
    file_content = FileContent("__visitor.py", read_resource(__name__, _visitor_resource))
×
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
        )
75
    )
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
        )
86
    )
87
    # See in script for where we explicitly encoded as utf8. Even though utf8 is the
88
    # default for decode(), we make that explicit here for emphasis.
89
    process_output = process_result.stdout.decode("utf8") or "{}"
×
90
    modules = [
×
91
        f"{django_apps.label_to_name[label]}.migrations.{migration}"
92
        for label, migration in json.loads(process_output)
93
        if label in django_apps.label_to_name
94
    ]
95
    resolve = request.field_set.resolve.normalized_value(python_setup)
×
96

97
    resolved_dependencies = await resolve_parsed_dependencies(
×
98
        ResolvedParsedPythonDependenciesRequest(
99
            request.field_set,
100
            ParsedPythonDependencies(
101
                ParsedPythonImports(
102
                    (module, ParsedPythonImportInfo(0, False)) for module in modules
103
                ),
104
                ParsedPythonAssetPaths(),
105
            ),
106
            resolve,
107
        ),
108
        **implicitly(),
109
    )
110

111
    return InferredDependencies(
×
112
        sorted(
113
            address
114
            for result in resolved_dependencies.resolve_results.values()
115
            if result.status in (ImportOwnerStatus.unambiguous, ImportOwnerStatus.disambiguated)
116
            for address in result.address
117
        )
118
    )
119

120

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

133
    app_package = apps[0].name
×
134

135
    implicit_dependency_packages = [
×
136
        f"{app_package}.{subpackage}" for subpackage in _implicit_dependency_packages
137
    ]
138

139
    resolve = request.field_set.resolve.normalized_value(python_setup)
×
140

141
    resolved_dependencies = await resolve_parsed_dependencies(
×
142
        ResolvedParsedPythonDependenciesRequest(
143
            request.field_set,
144
            ParsedPythonDependencies(
145
                ParsedPythonImports(
146
                    (package, ParsedPythonImportInfo(0, False))
147
                    for package in implicit_dependency_packages
148
                ),
149
                ParsedPythonAssetPaths(),
150
            ),
151
            resolve,
152
        ),
153
        **implicitly(),
154
    )
155

156
    spec_paths = [
×
157
        address.spec_path
158
        for result in resolved_dependencies.resolve_results.values()
159
        if result.status in (ImportOwnerStatus.unambiguous, ImportOwnerStatus.disambiguated)
160
        for address in result.address
161
    ]
162

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
        )
170
    )
171

172
    return InferredDependencies(sorted(target.address for target in targets))
×
173

174

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

191

UNCOV
192
def rules():
1✔
UNCOV
193
    return (
1✔
194
        *collect_rules(),
195
        *pex.rules(),
196
        *dependency_inference.rules.rules(),
197
        UnionRule(InferDependenciesRequest, InferDjangoDependencies),
198
    )
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

© 2025 Coveralls, Inc