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

pantsbuild / pants / 19529437518

20 Nov 2025 07:44AM UTC coverage: 78.884% (-1.4%) from 80.302%
19529437518

push

github

web-flow
nfpm.native_libs: Add RPM package depends from packaged pex_binaries (#22899)

## PR Series Overview

This is the second in a series of PRs that introduces a new backend:
`pants.backend.npm.native_libs`
Initially, the backend will be available as:
`pants.backend.experimental.nfpm.native_libs`

I proposed this new backend (originally named `bindeps`) in discussion
#22396.

This backend will inspect ELF bin/lib files (like `lib*.so`) in packaged
contents (for this PR series, only in `pex_binary` targets) to identify
package dependency metadata and inject that metadata on the relevant
`nfpm_deb_package` or `nfpm_rpm_package` targets. Effectively, it will
provide an approximation of these native packager features:
- `rpm`: `rpmdeps` + `elfdeps`
- `deb`: `dh_shlibdeps` + `dpkg-shlibdeps` (These substitute
`${shlibs:Depends}` in debian control files have)

### Goal: Host-agnostic package builds

This pants backend is designed to be host-agnostic, like
[nFPM](https://nfpm.goreleaser.com/).

Native packaging tools are often restricted to a single release of a
single distro. Unlike native package builders, this new pants backend
does not use any of those distro-specific or distro-release-specific
utilities or local package databases. This new backend should be able to
run (help with building deb and rpm packages) anywhere that pants can
run (MacOS, rpm linux distros, deb linux distros, other linux distros,
docker, ...).

### Previous PRs in series

- #22873

## PR Overview

This PR adds rules in `nfpm.native_libs` to add package dependency
metadata to `nfpm_rpm_package`. The 2 new rules are:

- `inject_native_libs_dependencies_in_package_fields`:

    - An implementation of the polymorphic rule `inject_nfpm_package_fields`.
      This rule is low priority (`priority = 2`) so that in-repo plugins can
      override/augment what it injects. (See #22864)

    - Rule logic overview:
        - find any pex_binaries that will be packaged in an `nfpm_rpm_package`
   ... (continued)

96 of 118 new or added lines in 3 files covered. (81.36%)

910 existing lines in 53 files now uncovered.

73897 of 93678 relevant lines covered (78.88%)

3.21 hits per line

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

80.7
/src/python/pants/engine/internals/selectors.py
1
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
11✔
5

6
import ast
11✔
7
import itertools
11✔
8
from collections.abc import Coroutine, Generator, Iterable, Sequence
11✔
9
from dataclasses import dataclass
11✔
10
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast, overload
11✔
11

12
from pants.engine.internals.native_engine import PyGeneratorResponseCall, PyGeneratorResponseGet
11✔
13
from pants.util.strutil import softwrap
11✔
14

15
_Output = TypeVar("_Output")
11✔
16
_Input = TypeVar("_Input")
11✔
17

18

19
class GetParseError(ValueError):
11✔
20
    def __init__(
11✔
21
        self, explanation: str, *, get_args: Sequence[ast.expr], source_file_name: str
22
    ) -> None:
UNCOV
23
        def render_arg(expr: ast.expr) -> str:
×
UNCOV
24
            if isinstance(expr, ast.Name):
×
UNCOV
25
                return expr.id
×
UNCOV
26
            if isinstance(expr, ast.Call):
×
27
                # Check if it's a top-level function call.
28
                if hasattr(expr.func, "id"):
×
29
                    return f"{expr.func.id}()"
×
30
                # Check if it's a method call.
31
                if hasattr(expr.func, "attr") and hasattr(expr.func, "value"):
×
32
                    return f"{expr.func.value.id}.{expr.func.attr}()"
×
33

34
            # Fall back to the name of the ast node's class.
UNCOV
35
            return str(type(expr))
×
36

UNCOV
37
        rendered_args = ", ".join(render_arg(arg) for arg in get_args)
×
38
        # TODO: Add the line numbers for the `Get`. The number for `get_args[0].lineno` are
39
        #  off because they're relative to the wrapping rule.
UNCOV
40
        super().__init__(
×
41
            f"Invalid Get. {explanation} Failed for Get({rendered_args}) in {source_file_name}."
42
        )
43

44

45
@dataclass(frozen=True)
11✔
46
class AwaitableConstraints:
11✔
47
    # If this is a call-by-name, then we will already know the callable `@rule` that will be used.
48
    rule_id: str | None
11✔
49
    output_type: type
11✔
50
    # The number of explicit positional arguments passed to a call-by-name awaitable.
51
    explicit_args_arity: int
11✔
52
    input_types: tuple[type, ...]
11✔
53
    is_effect: bool
11✔
54

55
    def __repr__(self) -> str:
11✔
56
        if self.rule_id:
1✔
57
            inputs = ", ".join(f"{t.__name__}" for t in self.input_types)
1✔
58
            return f"{self.rule_id}({inputs}) -> {self.output_type.__name__}"
1✔
59
        else:
60
            name = "Effect" if self.is_effect else "Get"
×
61
            if len(self.input_types) == 0:
×
62
                inputs = ""
×
63
            elif len(self.input_types) == 1:
×
64
                inputs = f", {self.input_types[0].__name__}, .."
×
65
            else:
66
                input_items = ", ".join(f"{t.__name__}: .." for t in self.input_types)
×
67
                inputs = f", {{{input_items}}}"
×
68
            return f"{name}({self.output_type.__name__}{inputs})"
×
69

70
    def __str__(self) -> str:
11✔
71
        return repr(self)
1✔
72

73

74
class Call(PyGeneratorResponseCall):
11✔
75
    def __await__(
11✔
76
        self,
77
    ) -> Generator[Any, None, Any]:
78
        result = yield self
11✔
79
        return result
11✔
80

81
    def __repr__(self) -> str:
11✔
82
        return f"Call({self.rule_id}(...) -> {self.output_type.__name__})"
×
83

84

85
# TODO: Conditional needed until Python 3.8 allows the subscripted type to be used directly.
86
# see https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime
87
if TYPE_CHECKING:
88

89
    class _BasePyGeneratorResponseGet(PyGeneratorResponseGet[_Output]):
90
        pass
91

92
else:
93

94
    class _BasePyGeneratorResponseGet(Generic[_Output], PyGeneratorResponseGet):
11✔
95
        pass
11✔
96

97

98
class Awaitable(Generic[_Output], _BasePyGeneratorResponseGet[_Output]):
11✔
99
    def __await__(
11✔
100
        self,
101
    ) -> Generator[Awaitable[_Output], None, _Output]:
102
        """Allow a Get to be `await`ed within an `async` method, returning a strongly-typed result.
103

104
        The `yield`ed value `self` is interpreted by the engine within
105
        `generator_send()`. This class will yield a single Get instance, which is
106
        a subclass of `PyGeneratorResponseGet`.
107

108
        This is how this method is eventually called:
109
        - When the engine calls an `async def` method decorated with `@rule`, an instance of
110
          `types.CoroutineType` is created.
111
        - The engine will call `.send(None)` on the coroutine, which will either:
112
          - raise StopIteration with a value (if the coroutine `return`s), or
113
          - return a `Get` instance to the engine (if the rule instead called `await Get(...)`).
114
        - The engine will fulfill the `Get` request to produce `x`, then call `.send(x)` and repeat the
115
          above until StopIteration.
116

117
        See more information about implementing this method at
118
        https://www.python.org/dev/peps/pep-0492/#await-expression.
119
        """
120
        result = yield self
×
121
        return cast(_Output, result)
×
122

123

124
class Effect(Generic[_Output], Awaitable[_Output]):
11✔
125
    """Asynchronous generator API for types which are SideEffecting.
126

127
    Unlike `Get`s, `Effect`s can cause side effects (writing files to the workspace, publishing
128
    things, printing to the console), and so they may only be used in `@goal_rule`s.
129

130
    See Get for more information on supported syntaxes.
131
    """
132

133

134
class Get(Generic[_Output], Awaitable[_Output]):
11✔
135
    """Asynchronous generator API for side-effect-free types.
136

137
    A Get can be constructed in 4 ways:
138

139
    + No arguments:
140
        Get(<OutputType>)
141

142
    + Long form:
143
        Get(<OutputType>, <InputType>, input)
144

145
    + Short form
146
        Get(<OutputType>, <InputType>(<constructor args for input>))
147

148
    + Dict form
149
        Get(<OutputType>, {input1: <Input1Type>, ..inputN: <InputNType>})
150

151
    The long form supports providing type information to the rule engine that it could not otherwise
152
    infer from the input variable [1]. Likewise, the short form must use inline construction of the
153
    input in order to convey the input type to the engine. The dict form supports providing >1
154
    inputs to the engine for the Get request.
155

156
    [1] The engine needs to determine all rule and Get input and output types statically before
157
    executing any rules. Since Gets are declared inside function bodies, the only way to extract this
158
    information is through a parse of the rule function. The parse analysis is rudimentary and cannot
159
    infer more than names and calls; so a variable name does not give enough information to infer its
160
    type, only a constructor call unambiguously gives this information without more in-depth parsing
161
    that includes following imports and more.
162
    """
163

164

165
@dataclass(frozen=True)
11✔
166
class _MultiGet:
11✔
167
    gets: tuple[Get | Coroutine, ...]
11✔
168

169
    def __await__(self) -> Generator[tuple[Get | Coroutine, ...], None, tuple]:
11✔
170
        result = yield self.gets
7✔
171
        return cast(tuple, result)
6✔
172

173

174
# These type variables are used to parametrize from 1 to 10 Gets when used in a tuple-style
175
# MultiGet call.
176

177
_Out0 = TypeVar("_Out0")
11✔
178
_Out1 = TypeVar("_Out1")
11✔
179
_Out2 = TypeVar("_Out2")
11✔
180
_Out3 = TypeVar("_Out3")
11✔
181
_Out4 = TypeVar("_Out4")
11✔
182
_Out5 = TypeVar("_Out5")
11✔
183
_Out6 = TypeVar("_Out6")
11✔
184
_Out7 = TypeVar("_Out7")
11✔
185
_Out8 = TypeVar("_Out8")
11✔
186
_Out9 = TypeVar("_Out9")
11✔
187

188

189
@overload
190
async def MultiGet(
191
    __gets: Iterable[Get[_Output] | Coroutine[Any, Any, _Output]],
192
) -> tuple[_Output, ...]: ...
193

194

195
@overload
196
async def MultiGet(
197
    __get0: Get[_Output] | Coroutine[Any, Any, _Output],
198
    __get1: Get[_Output] | Coroutine[Any, Any, _Output],
199
    __get2: Get[_Output] | Coroutine[Any, Any, _Output],
200
    __get3: Get[_Output] | Coroutine[Any, Any, _Output],
201
    __get4: Get[_Output] | Coroutine[Any, Any, _Output],
202
    __get5: Get[_Output] | Coroutine[Any, Any, _Output],
203
    __get6: Get[_Output] | Coroutine[Any, Any, _Output],
204
    __get7: Get[_Output] | Coroutine[Any, Any, _Output],
205
    __get8: Get[_Output] | Coroutine[Any, Any, _Output],
206
    __get9: Get[_Output] | Coroutine[Any, Any, _Output],
207
    __get10: Get[_Output] | Coroutine[Any, Any, _Output],
208
    *__gets: Get[_Output] | Coroutine[Any, Any, _Output],
209
) -> tuple[_Output, ...]: ...
210

211

212
@overload
213
async def MultiGet(
214
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
215
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
216
    __get2: Get[_Out2] | Coroutine[Any, Any, _Out2],
217
    __get3: Get[_Out3] | Coroutine[Any, Any, _Out3],
218
    __get4: Get[_Out4] | Coroutine[Any, Any, _Out4],
219
    __get5: Get[_Out5] | Coroutine[Any, Any, _Out5],
220
    __get6: Get[_Out6] | Coroutine[Any, Any, _Out6],
221
    __get7: Get[_Out7] | Coroutine[Any, Any, _Out7],
222
    __get8: Get[_Out8] | Coroutine[Any, Any, _Out8],
223
    __get9: Get[_Out9] | Coroutine[Any, Any, _Out9],
224
) -> tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5, _Out6, _Out7, _Out8, _Out9]: ...
225

226

227
@overload
228
async def MultiGet(
229
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
230
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
231
    __get2: Get[_Out2] | Coroutine[Any, Any, _Out2],
232
    __get3: Get[_Out3] | Coroutine[Any, Any, _Out3],
233
    __get4: Get[_Out4] | Coroutine[Any, Any, _Out4],
234
    __get5: Get[_Out5] | Coroutine[Any, Any, _Out5],
235
    __get6: Get[_Out6] | Coroutine[Any, Any, _Out6],
236
    __get7: Get[_Out7] | Coroutine[Any, Any, _Out7],
237
    __get8: Get[_Out8] | Coroutine[Any, Any, _Out8],
238
) -> tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5, _Out6, _Out7, _Out8]: ...
239

240

241
@overload
242
async def MultiGet(
243
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
244
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
245
    __get2: Get[_Out2] | Coroutine[Any, Any, _Out2],
246
    __get3: Get[_Out3] | Coroutine[Any, Any, _Out3],
247
    __get4: Get[_Out4] | Coroutine[Any, Any, _Out4],
248
    __get5: Get[_Out5] | Coroutine[Any, Any, _Out5],
249
    __get6: Get[_Out6] | Coroutine[Any, Any, _Out6],
250
    __get7: Get[_Out7] | Coroutine[Any, Any, _Out7],
251
) -> tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5, _Out6, _Out7]: ...
252

253

254
@overload
255
async def MultiGet(
256
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
257
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
258
    __get2: Get[_Out2] | Coroutine[Any, Any, _Out2],
259
    __get3: Get[_Out3] | Coroutine[Any, Any, _Out3],
260
    __get4: Get[_Out4] | Coroutine[Any, Any, _Out4],
261
    __get5: Get[_Out5] | Coroutine[Any, Any, _Out5],
262
    __get6: Get[_Out6] | Coroutine[Any, Any, _Out6],
263
) -> tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5, _Out6]: ...
264

265

266
@overload
267
async def MultiGet(
268
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
269
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
270
    __get2: Get[_Out2] | Coroutine[Any, Any, _Out2],
271
    __get3: Get[_Out3] | Coroutine[Any, Any, _Out3],
272
    __get4: Get[_Out4] | Coroutine[Any, Any, _Out4],
273
    __get5: Get[_Out5] | Coroutine[Any, Any, _Out5],
274
) -> tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5]: ...
275

276

277
@overload
278
async def MultiGet(
279
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
280
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
281
    __get2: Get[_Out2] | Coroutine[Any, Any, _Out2],
282
    __get3: Get[_Out3] | Coroutine[Any, Any, _Out3],
283
    __get4: Get[_Out4] | Coroutine[Any, Any, _Out4],
284
) -> tuple[_Out0, _Out1, _Out2, _Out3, _Out4]: ...
285

286

287
@overload
288
async def MultiGet(
289
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
290
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
291
    __get2: Get[_Out2] | Coroutine[Any, Any, _Out2],
292
    __get3: Get[_Out3] | Coroutine[Any, Any, _Out3],
293
) -> tuple[_Out0, _Out1, _Out2, _Out3]: ...
294

295

296
@overload
297
async def MultiGet(
298
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
299
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
300
    __get2: Get[_Out2] | Coroutine[Any, Any, _Out2],
301
) -> tuple[_Out0, _Out1, _Out2]: ...
302

303

304
@overload
305
async def MultiGet(
306
    __get0: Get[_Out0] | Coroutine[Any, Any, _Out0],
307
    __get1: Get[_Out1] | Coroutine[Any, Any, _Out1],
308
) -> tuple[_Out0, _Out1]: ...
309

310

311
async def MultiGet(
11✔
312
    __arg0: (
313
        Iterable[Get[_Output] | Coroutine[Any, Any, _Output]]
314
        | Get[_Out0]
315
        | Coroutine[Any, Any, _Out0]
316
    ),
317
    __arg1: Get[_Out1] | Coroutine[Any, Any, _Out1] | None = None,
318
    __arg2: Get[_Out2] | Coroutine[Any, Any, _Out2] | None = None,
319
    __arg3: Get[_Out3] | Coroutine[Any, Any, _Out3] | None = None,
320
    __arg4: Get[_Out4] | Coroutine[Any, Any, _Out4] | None = None,
321
    __arg5: Get[_Out5] | Coroutine[Any, Any, _Out5] | None = None,
322
    __arg6: Get[_Out6] | Coroutine[Any, Any, _Out6] | None = None,
323
    __arg7: Get[_Out7] | Coroutine[Any, Any, _Out7] | None = None,
324
    __arg8: Get[_Out8] | Coroutine[Any, Any, _Out8] | None = None,
325
    __arg9: Get[_Out9] | Coroutine[Any, Any, _Out9] | None = None,
326
    *__args: Get[_Output] | Coroutine[Any, Any, _Output],
327
) -> (
328
    tuple[_Output, ...]
329
    | tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5, _Out6, _Out7, _Out8, _Out9]
330
    | tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5, _Out6, _Out7, _Out8]
331
    | tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5, _Out6, _Out7]
332
    | tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5, _Out6]
333
    | tuple[_Out0, _Out1, _Out2, _Out3, _Out4, _Out5]
334
    | tuple[_Out0, _Out1, _Out2, _Out3, _Out4]
335
    | tuple[_Out0, _Out1, _Out2, _Out3]
336
    | tuple[_Out0, _Out1, _Out2]
337
    | tuple[_Out0, _Out1]
338
    | tuple[_Out0]
339
):
340
    """Yield a tuple of Get instances all at once.
341

342
    The `yield`ed value `self.gets` is interpreted by the engine within
343
    `generator_send()`. This class will yield a tuple of Get instances,
344
    which is converted into `PyGeneratorResponse::GetMulti`.
345

346
    The engine will fulfill these Get instances in parallel, and return a tuple of _Output
347
    instances to this method, which then returns this tuple to the `@rule` which called
348
    `await MultiGet(Get(_Output, ...) for ... in ...)`.
349
    """
350
    if (
7✔
351
        isinstance(__arg0, Iterable)
352
        and __arg1 is None
353
        and __arg2 is None
354
        and __arg3 is None
355
        and __arg4 is None
356
        and __arg5 is None
357
        and __arg6 is None
358
        and __arg7 is None
359
        and __arg8 is None
360
        and __arg9 is None
361
        and not __args
362
    ):
363
        return await _MultiGet(tuple(__arg0))
6✔
364

365
    if (
5✔
366
        isinstance(__arg0, (Get, Coroutine))
367
        and __arg1 is None
368
        and __arg2 is None
369
        and __arg3 is None
370
        and __arg4 is None
371
        and __arg5 is None
372
        and __arg6 is None
373
        and __arg7 is None
374
        and __arg8 is None
375
        and __arg9 is None
376
        and not __args
377
    ):
378
        return await _MultiGet((__arg0,))
1✔
379

380
    if (
5✔
381
        isinstance(__arg0, (Get, Coroutine))
382
        and isinstance(__arg1, (Get, Coroutine))
383
        and __arg2 is None
384
        and __arg3 is None
385
        and __arg4 is None
386
        and __arg5 is None
387
        and __arg6 is None
388
        and __arg7 is None
389
        and __arg8 is None
390
        and __arg9 is None
391
        and not __args
392
    ):
393
        return await _MultiGet((__arg0, __arg1))
5✔
394

395
    if (
1✔
396
        isinstance(__arg0, (Get, Coroutine))
397
        and isinstance(__arg1, (Get, Coroutine))
398
        and isinstance(__arg2, (Get, Coroutine))
399
        and __arg3 is None
400
        and __arg4 is None
401
        and __arg5 is None
402
        and __arg6 is None
403
        and __arg7 is None
404
        and __arg8 is None
405
        and __arg9 is None
406
        and not __args
407
    ):
408
        return await _MultiGet((__arg0, __arg1, __arg2))
1✔
409

410
    if (
1✔
411
        isinstance(__arg0, (Get, Coroutine))
412
        and isinstance(__arg1, (Get, Coroutine))
413
        and isinstance(__arg2, (Get, Coroutine))
414
        and isinstance(__arg3, (Get, Coroutine))
415
        and __arg4 is None
416
        and __arg5 is None
417
        and __arg6 is None
418
        and __arg7 is None
419
        and __arg8 is None
420
        and __arg9 is None
421
        and not __args
422
    ):
423
        return await _MultiGet((__arg0, __arg1, __arg2, __arg3))
1✔
424

425
    if (
1✔
426
        isinstance(__arg0, (Get, Coroutine))
427
        and isinstance(__arg1, (Get, Coroutine))
428
        and isinstance(__arg2, (Get, Coroutine))
429
        and isinstance(__arg3, (Get, Coroutine))
430
        and isinstance(__arg4, (Get, Coroutine))
431
        and __arg5 is None
432
        and __arg6 is None
433
        and __arg7 is None
434
        and __arg8 is None
435
        and __arg9 is None
436
        and not __args
437
    ):
438
        return await _MultiGet((__arg0, __arg1, __arg2, __arg3, __arg4))
1✔
439

440
    if (
1✔
441
        isinstance(__arg0, (Get, Coroutine))
442
        and isinstance(__arg1, (Get, Coroutine))
443
        and isinstance(__arg2, (Get, Coroutine))
444
        and isinstance(__arg3, (Get, Coroutine))
445
        and isinstance(__arg4, (Get, Coroutine))
446
        and isinstance(__arg5, (Get, Coroutine))
447
        and __arg6 is None
448
        and __arg7 is None
449
        and __arg8 is None
450
        and __arg9 is None
451
        and not __args
452
    ):
453
        return await _MultiGet((__arg0, __arg1, __arg2, __arg3, __arg4, __arg5))
1✔
454

455
    if (
1✔
456
        isinstance(__arg0, (Get, Coroutine))
457
        and isinstance(__arg1, (Get, Coroutine))
458
        and isinstance(__arg2, (Get, Coroutine))
459
        and isinstance(__arg3, (Get, Coroutine))
460
        and isinstance(__arg4, (Get, Coroutine))
461
        and isinstance(__arg5, (Get, Coroutine))
462
        and isinstance(__arg6, (Get, Coroutine))
463
        and __arg7 is None
464
        and __arg8 is None
465
        and __arg9 is None
466
        and not __args
467
    ):
468
        return await _MultiGet((__arg0, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6))
1✔
469

470
    if (
1✔
471
        isinstance(__arg0, (Get, Coroutine))
472
        and isinstance(__arg1, (Get, Coroutine))
473
        and isinstance(__arg2, (Get, Coroutine))
474
        and isinstance(__arg3, (Get, Coroutine))
475
        and isinstance(__arg4, (Get, Coroutine))
476
        and isinstance(__arg5, (Get, Coroutine))
477
        and isinstance(__arg6, (Get, Coroutine))
478
        and isinstance(__arg7, (Get, Coroutine))
479
        and __arg8 is None
480
        and __arg9 is None
481
        and not __args
482
    ):
483
        return await _MultiGet((__arg0, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7))
1✔
484

485
    if (
1✔
486
        isinstance(__arg0, (Get, Coroutine))
487
        and isinstance(__arg1, (Get, Coroutine))
488
        and isinstance(__arg2, (Get, Coroutine))
489
        and isinstance(__arg3, (Get, Coroutine))
490
        and isinstance(__arg4, (Get, Coroutine))
491
        and isinstance(__arg5, (Get, Coroutine))
492
        and isinstance(__arg6, (Get, Coroutine))
493
        and isinstance(__arg7, (Get, Coroutine))
494
        and isinstance(__arg8, (Get, Coroutine))
495
        and __arg9 is None
496
        and not __args
497
    ):
498
        return await _MultiGet(
1✔
499
            (__arg0, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, __arg8)
500
        )
501

502
    if (
1✔
503
        isinstance(__arg0, (Get, Coroutine))
504
        and isinstance(__arg1, (Get, Coroutine))
505
        and isinstance(__arg2, (Get, Coroutine))
506
        and isinstance(__arg3, (Get, Coroutine))
507
        and isinstance(__arg4, (Get, Coroutine))
508
        and isinstance(__arg5, (Get, Coroutine))
509
        and isinstance(__arg6, (Get, Coroutine))
510
        and isinstance(__arg7, (Get, Coroutine))
511
        and isinstance(__arg8, (Get, Coroutine))
512
        and isinstance(__arg9, (Get, Coroutine))
513
        and all(isinstance(arg, Get) for arg in __args)
514
    ):
515
        return await _MultiGet(
1✔
516
            (
517
                __arg0,
518
                __arg1,
519
                __arg2,
520
                __arg3,
521
                __arg4,
522
                __arg5,
523
                __arg6,
524
                __arg7,
525
                __arg8,
526
                __arg9,
527
                *__args,
528
            )
529
        )
530

531
    args = __arg0, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, __arg8, __arg9, *__args
1✔
532

533
    def render_arg(arg: Any) -> str | None:
1✔
534
        if arg is None:
1✔
535
            return None
1✔
536
        if isinstance(arg, Get):
1✔
537
            return repr(arg)
1✔
538
        return repr(arg)
1✔
539

540
    likely_args_explicitly_passed = tuple(
1✔
541
        reversed(
542
            [
543
                render_arg(arg)
544
                for arg in itertools.dropwhile(lambda arg: arg is None, reversed(args))
545
            ]
546
        )
547
    )
548
    if any(arg is None for arg in likely_args_explicitly_passed):
1✔
549
        raise ValueError(
1✔
550
            softwrap(
551
                f"""
552
                Unexpected concurrently() None arguments: {
553
                    ", ".join(map(str, likely_args_explicitly_passed))
554
                }
555

556
                When calling concurrently() on individual rule calls, all leading arguments must be
557
                awaitables.
558
                """
559
            )
560
        )
561

562
    raise TypeError(
1✔
563
        softwrap(
564
            f"""
565
            Unexpected concurrently() argument types: {", ".join(map(str, likely_args_explicitly_passed))}
566

567
            `concurrently` can be used in two ways:
568
              1. concurrently(Iterable[awaitable[T]]) -> Tuple[T]
569
              2. concurrently(awaitable[T1]], ...) -> Tuple[T1, T2, ...]
570

571
            The 1st form is intended for homogenous collections of rule calls and emulates an
572
            async `for ...` comprehension used to iterate over the collection in parallel and
573
            collect the results in a homogenous tuple when all are complete.
574

575
            The 2nd form supports executing heterogeneous rule calls in parallel and collecting
576
            them in a heterogeneous tuple when all are complete. Currently up to 10 heterogeneous
577
            rule calls can be passed while still tracking their output types for type-checking by
578
            MyPy and similar type checkers. If more than 10 rule calls are passed, type checking
579
            will enforce that they are homogeneous.
580
            """
581
        )
582
    )
583

584

585
# Alias for `MultiGet` to new syntax name `concurrently`, while remaining backwards compatible.
586
concurrently = MultiGet
11✔
587

588

589
@dataclass(frozen=True)
11✔
590
class Params:
11✔
591
    """A set of values with distinct types.
592

593
    Distinct types are enforced at consumption time by the rust type of the same name.
594
    """
595

596
    params: tuple[Any, ...]
11✔
597

598
    def __init__(self, *args: Any) -> None:
11✔
599
        object.__setattr__(self, "params", tuple(args))
11✔
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