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

pantsbuild / pants / 22285099215

22 Feb 2026 08:52PM UTC coverage: 75.854% (-17.1%) from 92.936%
22285099215

Pull #23121

github

web-flow
Merge c7299df9c into ba8359840
Pull Request #23121: fix issue with optional fields in dependency validator

28 of 29 new or added lines in 2 files covered. (96.55%)

11174 existing lines in 400 files now uncovered.

53694 of 70786 relevant lines covered (75.85%)

1.88 hits per line

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

0.0
/src/python/pants/backend/awslambda/python/target_types.py
1
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

UNCOV
4
from __future__ import annotations
×
5

UNCOV
6
import re
×
UNCOV
7
from dataclasses import dataclass
×
UNCOV
8
from enum import Enum
×
UNCOV
9
from re import Match
×
UNCOV
10
from typing import ClassVar, cast
×
11

UNCOV
12
from pants.backend.python.target_types import PexCompletePlatformsField, PythonResolveField
×
UNCOV
13
from pants.backend.python.util_rules.faas import (
×
14
    FaaSArchitecture,
15
    PythonFaaSCompletePlatforms,
16
    PythonFaaSDependencies,
17
    PythonFaaSHandlerField,
18
    PythonFaaSKnownRuntime,
19
    PythonFaaSLayoutField,
20
    PythonFaaSPex3VenvCreateExtraArgsField,
21
    PythonFaaSPexBuildExtraArgs,
22
    PythonFaaSRuntimeField,
23
)
UNCOV
24
from pants.backend.python.util_rules.faas import rules as faas_rules
×
UNCOV
25
from pants.core.environments.target_types import EnvironmentField
×
UNCOV
26
from pants.core.goals.package import OutputPathField
×
UNCOV
27
from pants.engine.addresses import Address
×
UNCOV
28
from pants.engine.rules import collect_rules
×
UNCOV
29
from pants.engine.target import (
×
30
    COMMON_TARGET_FIELDS,
31
    BoolField,
32
    Field,
33
    InvalidFieldException,
34
    StringField,
35
    Target,
36
)
UNCOV
37
from pants.util.docutil import doc_url
×
UNCOV
38
from pants.util.strutil import help_text, softwrap
×
39

40

UNCOV
41
class PythonAwsLambdaHandlerField(PythonFaaSHandlerField):
×
42
    # This doesn't matter (just needs to be fixed), but is the default name used by the AWS
43
    # console when creating a Python lambda, so is as good as any
44
    # https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html
UNCOV
45
    reexported_handler_module = "lambda_function"
×
46

UNCOV
47
    help = help_text(
×
48
        f"""
49
        Entry point to the AWS Lambda handler.
50

51
        {PythonFaaSHandlerField.help}
52

53
        This is re-exported at `{reexported_handler_module}.handler` in the resulting package to be
54
        used as the configured handler of the Lambda in AWS. It can also be accessed under its
55
        source-root-relative module path, for example: `path.to.module.handler_func`.
56
        """
57
    )
58

59

UNCOV
60
@dataclass(frozen=True)
×
UNCOV
61
class ResolvedPythonAwsHandler:
×
UNCOV
62
    val: str
×
UNCOV
63
    file_name_used: bool
×
64

65

UNCOV
66
@dataclass(frozen=True)
×
UNCOV
67
class ResolvePythonAwsHandlerRequest:
×
UNCOV
68
    field: PythonAwsLambdaHandlerField
×
69

70

UNCOV
71
class PythonAwsLambdaIncludeRequirements(BoolField):
×
UNCOV
72
    alias = "include_requirements"
×
UNCOV
73
    default = True
×
UNCOV
74
    help = help_text(
×
75
        """
76
        Whether to resolve requirements and include them in the AWS Lambda artifact. This is most useful with Lambda
77
        Layers to make code uploads smaller when third-party requirements are in layers.
78
        https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html
79
        """
80
    )
81

82

UNCOV
83
class PythonAwsLambdaIncludeSources(BoolField):
×
UNCOV
84
    alias = "include_sources"
×
UNCOV
85
    default = True
×
UNCOV
86
    help = help_text(
×
87
        """
88
        Whether to resolve first party sources and include them in the AWS Lambda artifact. This is
89
        most useful to allow creating a Lambda Layer with only third-party requirements.
90
        https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html
91
        """
92
    )
93

94

UNCOV
95
PYTHON_RUNTIME_REGEX = r"python(?P<major>\d)\.(?P<minor>\d+)"
×
96

97

UNCOV
98
class PythonAwsLambdaFunctionRuntimes(Enum):
×
UNCOV
99
    PYTHON_36 = "python3.6"
×
UNCOV
100
    PYTHON_37 = "python3.7"
×
UNCOV
101
    PYTHON_38 = "python3.8"
×
UNCOV
102
    PYTHON_39 = "python3.9"
×
UNCOV
103
    PYTHON_310 = "python3.10"
×
UNCOV
104
    PYTHON_311 = "python3.11"
×
UNCOV
105
    PYTHON_312 = "python3.12"
×
UNCOV
106
    PYTHON_313 = "python3.13"
×
UNCOV
107
    PYTHON_314 = "python3.14"
×
108

UNCOV
109
    def to_interpreter_version(self) -> tuple[int, int]:
×
110
        """Returns the Python version implied by the runtime, as (major, minor)."""
UNCOV
111
        mo = cast(Match, re.match(PYTHON_RUNTIME_REGEX, self.value))
×
UNCOV
112
        return int(mo.group("major")), int(mo.group("minor"))
×
113

114

UNCOV
115
LAMBDA_DOCKER_REPO = "public.ecr.aws/lambda/python"
×
116

117

UNCOV
118
class PythonAwsLambdaRuntime(PythonFaaSRuntimeField):
×
119
    # https://gallery.ecr.aws/lambda/python
UNCOV
120
    RUNTIME_TAG_MAPPING = {
×
121
        (PythonAwsLambdaFunctionRuntimes.PYTHON_36, FaaSArchitecture.X86_64): "3.6",
122
        (PythonAwsLambdaFunctionRuntimes.PYTHON_37, FaaSArchitecture.X86_64): "3.7",
123
        (PythonAwsLambdaFunctionRuntimes.PYTHON_38, FaaSArchitecture.X86_64): "3.8-x86_64",
124
        (PythonAwsLambdaFunctionRuntimes.PYTHON_38, FaaSArchitecture.ARM64): "3.8-arm64",
125
        (PythonAwsLambdaFunctionRuntimes.PYTHON_39, FaaSArchitecture.X86_64): "3.9-x86_64",
126
        (PythonAwsLambdaFunctionRuntimes.PYTHON_39, FaaSArchitecture.ARM64): "3.9-arm64",
127
        (PythonAwsLambdaFunctionRuntimes.PYTHON_310, FaaSArchitecture.X86_64): "3.10-x86_64",
128
        (PythonAwsLambdaFunctionRuntimes.PYTHON_310, FaaSArchitecture.ARM64): "3.10-arm64",
129
        (PythonAwsLambdaFunctionRuntimes.PYTHON_311, FaaSArchitecture.X86_64): "3.11-x86_64",
130
        (PythonAwsLambdaFunctionRuntimes.PYTHON_311, FaaSArchitecture.ARM64): "3.11-arm64",
131
        (PythonAwsLambdaFunctionRuntimes.PYTHON_312, FaaSArchitecture.X86_64): "3.12-x86_64",
132
        (PythonAwsLambdaFunctionRuntimes.PYTHON_312, FaaSArchitecture.ARM64): "3.12-arm64",
133
        (PythonAwsLambdaFunctionRuntimes.PYTHON_313, FaaSArchitecture.X86_64): "3.13-x86_64",
134
        (PythonAwsLambdaFunctionRuntimes.PYTHON_313, FaaSArchitecture.ARM64): "3.13-arm64",
135
        (PythonAwsLambdaFunctionRuntimes.PYTHON_314, FaaSArchitecture.X86_64): "3.14-x86_64",
136
        (PythonAwsLambdaFunctionRuntimes.PYTHON_314, FaaSArchitecture.ARM64): "3.14-arm64",
137
    }
138

UNCOV
139
    help = help_text(
×
140
        """
141
        The identifier of the AWS Lambda runtime to target (pythonX.Y).
142
        See https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html.
143

144
        N.B.: only one of this and `complete_platforms` can be set. If `runtime` is set, a default complete
145
        platform is chosen, if one is known for that runtime. If you have issues either
146
        packaging the AWS Lambda PEX or running it as a deployed AWS Lambda function, you should try
147
        using an explicit `complete_platforms` instead.
148
        """
149
    )
150

UNCOV
151
    valid_choices = PythonAwsLambdaFunctionRuntimes
×
UNCOV
152
    known_runtimes = tuple(
×
153
        PythonFaaSKnownRuntime(
154
            runtime.value, *runtime.to_interpreter_version(), LAMBDA_DOCKER_REPO, tag, architecture
155
        )
156
        for (runtime, architecture), tag in RUNTIME_TAG_MAPPING.items()
157
    )
158

UNCOV
159
    @classmethod
×
UNCOV
160
    def compute_value(cls, raw_value: str | None, address: Address) -> str | None:
×
UNCOV
161
        value = super().compute_value(raw_value, address)
×
UNCOV
162
        if value is None:
×
UNCOV
163
            return None
×
UNCOV
164
        if not re.match(PYTHON_RUNTIME_REGEX, value):
×
165
            raise InvalidFieldException(
×
166
                softwrap(
167
                    f"""
168
                    The `{cls.alias}` field in target at {address} must be of the form pythonX.Y,
169
                    but was {value}.
170
                    """
171
                )
172
            )
UNCOV
173
        return value
×
174

UNCOV
175
    def to_interpreter_version(self) -> tuple[int, int] | None:
×
176
        """Returns the Python version implied by the runtime, as (major, minor)."""
UNCOV
177
        if self.value is None:
×
UNCOV
178
            return None
×
UNCOV
179
        mo = cast(Match, re.match(PYTHON_RUNTIME_REGEX, self.value))
×
UNCOV
180
        return int(mo.group("major")), int(mo.group("minor"))
×
181

UNCOV
182
    @classmethod
×
UNCOV
183
    def from_interpreter_version(cls, py_major: int, py_minor: int) -> str:
×
UNCOV
184
        return f"python{py_major}.{py_minor}"
×
185

186

UNCOV
187
class AWSLambdaArchitectureField(StringField):
×
UNCOV
188
    alias = "architecture"
×
UNCOV
189
    valid_choices = FaaSArchitecture
×
UNCOV
190
    expected_type = str
×
UNCOV
191
    default = FaaSArchitecture.X86_64.value
×
UNCOV
192
    help = help_text(
×
193
        """
194
        The architecture of the AWS Lambda runtime to target (x86_64 or arm64).
195
        See https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html.
196
        """
197
    )
198

199

UNCOV
200
class PythonAwsLambdaLayerDependenciesField(PythonFaaSDependencies):
×
UNCOV
201
    required = True
×
202

203

UNCOV
204
class _AWSLambdaBaseTarget(Target):
×
UNCOV
205
    core_fields: ClassVar[tuple[type[Field], ...]] = (
×
206
        *COMMON_TARGET_FIELDS,
207
        OutputPathField,
208
        PythonAwsLambdaIncludeRequirements,
209
        PythonAwsLambdaRuntime,
210
        PythonFaaSCompletePlatforms,
211
        PythonFaaSPex3VenvCreateExtraArgsField,
212
        PythonFaaSPexBuildExtraArgs,
213
        PythonFaaSLayoutField,
214
        PythonResolveField,
215
        EnvironmentField,
216
    )
217

UNCOV
218
    def validate(self) -> None:
×
UNCOV
219
        has_runtime = self[PythonAwsLambdaRuntime].value is not None
×
UNCOV
220
        has_complete_platforms = self[PexCompletePlatformsField].value is not None
×
221

UNCOV
222
        runtime_alias = self[PythonAwsLambdaRuntime].alias
×
UNCOV
223
        complete_platforms_alias = self[PexCompletePlatformsField].alias
×
224

UNCOV
225
        if has_runtime and has_complete_platforms:
×
226
            raise ValueError(
×
227
                softwrap(
228
                    f"""
229
                    The `{complete_platforms_alias}` takes precedence over the `{runtime_alias}` field, if
230
                    it is set. Remove the `{runtime_alias}` field to only use the `{complete_platforms_alias}`
231
                    value, or remove the `{complete_platforms_alias}` field to use the default platform
232
                    implied by `{runtime_alias}`.
233
                    """
234
                )
235
            )
236

237

UNCOV
238
class PythonAWSLambda(_AWSLambdaBaseTarget):
×
UNCOV
239
    alias = "python_aws_lambda_function"
×
240

UNCOV
241
    core_fields = (
×
242
        *_AWSLambdaBaseTarget.core_fields,
243
        PythonFaaSDependencies,
244
        PythonAwsLambdaHandlerField,
245
        AWSLambdaArchitectureField,
246
    )
UNCOV
247
    help = help_text(
×
248
        f"""
249
        A self-contained Python function suitable for uploading to AWS Lambda.
250

251
        See {doc_url("docs/python/integrations/aws-lambda")}.
252
        """
253
    )
254

255

UNCOV
256
class PythonAWSLambdaLayer(_AWSLambdaBaseTarget):
×
UNCOV
257
    alias = "python_aws_lambda_layer"
×
UNCOV
258
    core_fields = (
×
259
        *_AWSLambdaBaseTarget.core_fields,
260
        PythonAwsLambdaIncludeSources,
261
        PythonAwsLambdaLayerDependenciesField,
262
        AWSLambdaArchitectureField,
263
    )
UNCOV
264
    help = help_text(
×
265
        f"""
266
        A Python layer suitable for uploading to AWS Lambda.
267

268
        See {doc_url("docs/python/integrations/aws-lambda")}.
269
        """
270
    )
271

272

UNCOV
273
def rules():
×
UNCOV
274
    return (
×
275
        *collect_rules(),
276
        *faas_rules(),
277
    )
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