Add better argspec typing. (#1011)

This commit is contained in:
Felix Fontein
2026-04-26 14:44:44 +02:00
committed by GitHub
parent fa36f75812
commit 5a50cc1e7a
5 changed files with 119 additions and 39 deletions

View File

@@ -118,7 +118,7 @@ good-names=i,
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
include-naming-hint=yes
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
@@ -157,7 +157,7 @@ property-classes=abc.abstractproperty
# Regular expression matching correct type alias names. If left empty, type
# alias names will be checked with the set naming style.
#typealias-rgx=
typealias-rgx = ^_{0,2}(?!T[A-Z]|Type)[A-Z]+[a-z0-9]+(?:[A-Z][a-z0-9]+)*T?$
# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.

View File

@@ -11,38 +11,124 @@ import typing as t
from ansible.module_utils.basic import AnsibleModule
_T = t.TypeVar("_T")
if t.TYPE_CHECKING:
import datetime # pragma: no cover
from collections.abc import Callable, Mapping, Sequence # pragma: no cover
_T = t.TypeVar("_T") # pragma: no cover
def _ensure_list(value: list[_T] | tuple[_T] | None) -> list[_T]:
if value is None:
return []
return list(value)
ArgSpecType = t.Literal[ # pragma: no cover
"bits",
"bool",
"bytes",
"dict",
"float",
"int",
"json",
"jsonarg",
"list",
"path",
"raw",
"sid",
"str",
]
MutuallyExclusiveT = t.Union[ # pragma: no cover # noqa: UP007
Sequence[str], Sequence[Sequence[str]]
]
MutuallyExclusiveMutT = list[Sequence[str]] # pragma: no cover
RequiredTogetherT = Sequence[Sequence[str]] # pragma: no cover
RequiredTogetherMutT = list[Sequence[str]] # pragma: no cover
RequiredOneOfT = Sequence[Sequence[str]] # pragma: no cover
RequiredOneOfMutT = list[Sequence[str]] # pragma: no cover
RequiredIfT = Sequence[ # pragma: no cover
t.Union[ # noqa: UP007
list[object],
tuple[str, object, Sequence[str]],
tuple[str, object, Sequence[str], bool],
]
]
RequiredIfMutT = list[ # pragma: no cover
t.Union[ # noqa: UP007
list[object],
tuple[str, object, Sequence[str]],
tuple[str, object, Sequence[str], bool],
]
]
RequiredByT = Mapping[str, Sequence[str]] # pragma: no cover
RequiredByMutT = dict[str, Sequence[str]] # pragma: no cover
class DeprecatedAlias(t.TypedDict): # pragma: no cover
name: str
date: t.NotRequired[datetime.date | str]
version: t.NotRequired[str]
collection_name: str
class OneArgumentSpecT(t.TypedDict): # pragma: no cover
type: t.NotRequired[ArgSpecType | Callable[[object], object]]
elements: t.NotRequired[ArgSpecType]
default: t.NotRequired[object]
# For fallback elements, the first element of the sequence has to be a callable, the others sequences or dicts.
# Unfortunately there is no way to specify this in a generic way...
fallback: t.NotRequired[
Sequence[
Callable[[object], object] | Sequence[object] | Mapping[str, object]
]
]
choices: t.NotRequired[Sequence[object]]
context: t.NotRequired[Mapping[object, object]]
required: t.NotRequired[bool]
no_log: t.NotRequired[bool]
aliases: t.NotRequired[Sequence[str]]
apply_defaults: t.NotRequired[bool]
removed_in_version: t.NotRequired[str]
removed_at_date: t.NotRequired[datetime.date | str]
removed_from_collection: t.NotRequired[str]
options: t.NotRequired[Mapping[str, OneArgumentSpecT]] # recursive!
deprecated_aliases: t.NotRequired[Sequence[DeprecatedAlias]]
mutually_exclusive: t.NotRequired[MutuallyExclusiveT]
required_together: t.NotRequired[RequiredTogetherT]
required_one_of: t.NotRequired[RequiredOneOfT]
required_if: t.NotRequired[RequiredIfT]
required_by: t.NotRequired[RequiredByT]
ArgumentSpecT = Mapping[str, OneArgumentSpecT] # pragma: no cover
ArgumentSpecMutT = dict[str, OneArgumentSpecT] # pragma: no cover
class ArgumentSpec:
def __init__(
self,
argument_spec: dict[str, t.Any] | None = None,
argument_spec: ArgumentSpecT | None = None,
*,
mutually_exclusive: list[list[str] | tuple[str, ...]] | None = None,
required_together: list[list[str] | tuple[str, ...]] | None = None,
required_one_of: list[list[str] | tuple[str, ...]] | None = None,
required_if: (
list[
tuple[str, t.Any, list[str] | tuple[str, ...]]
| tuple[str, t.Any, list[str] | tuple[str, ...], bool]
]
| None
) = None,
required_by: dict[str, tuple[str, ...] | list[str]] | None = None,
required_together: RequiredTogetherT | None = None,
required_if: RequiredIfT | None = None,
required_one_of: RequiredOneOfT | None = None,
mutually_exclusive: MutuallyExclusiveT | None = None,
required_by: RequiredByT | None = None,
) -> None:
self.argument_spec = argument_spec or {}
self.mutually_exclusive = _ensure_list(mutually_exclusive)
self.required_together = _ensure_list(required_together)
self.required_one_of = _ensure_list(required_one_of)
self.required_if = _ensure_list(required_if)
self.required_by = required_by or {}
self.argument_spec: ArgumentSpecMutT = {}
self.required_together: RequiredTogetherMutT = []
self.required_if: RequiredIfMutT = []
self.required_one_of: RequiredOneOfMutT = []
self.mutually_exclusive: MutuallyExclusiveMutT = []
self.required_by: RequiredByMutT = {}
if argument_spec:
self.argument_spec.update(argument_spec)
if required_together:
self.required_together.extend(required_together)
if required_if:
self.required_if.extend(required_if)
if required_one_of:
self.required_one_of.extend(required_one_of)
if mutually_exclusive:
if all(isinstance(me, str) for me in mutually_exclusive):
# mutually_exclusive is a Sequence[str]
self.mutually_exclusive.append(mutually_exclusive) # type: ignore
else:
self.mutually_exclusive.extend(mutually_exclusive)
if required_by:
self.required_by.update(required_by)
def update_argspec(self, **kwargs: t.Any) -> t.Self:
self.argument_spec.update(kwargs)
@@ -51,17 +137,11 @@ class ArgumentSpec:
def update(
self,
*,
mutually_exclusive: list[list[str] | tuple[str, ...]] | None = None,
required_together: list[list[str] | tuple[str, ...]] | None = None,
required_one_of: list[list[str] | tuple[str, ...]] | None = None,
required_if: (
list[
tuple[str, t.Any, list[str] | tuple[str, ...]]
| tuple[str, t.Any, list[str] | tuple[str, ...], bool]
]
| None
) = None,
required_by: dict[str, tuple[str, ...] | list[str]] | None = None,
required_together: RequiredTogetherT | None = None,
required_if: RequiredIfT | None = None,
required_one_of: RequiredOneOfT | None = None,
mutually_exclusive: MutuallyExclusiveT | None = None,
required_by: RequiredByT | None = None,
) -> t.Self:
if mutually_exclusive:
self.mutually_exclusive.extend(mutually_exclusive)

View File

@@ -128,7 +128,7 @@ class AcmeCertificateProvider(CertificateProvider):
def add_acme_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
argument_spec.argument_spec["provider"]["choices"].append("acme")
argument_spec.argument_spec["provider"]["choices"].append("acme") # type: ignore
argument_spec.argument_spec.update(
{
"acme_accountkey_path": {"type": "path"},

View File

@@ -340,7 +340,7 @@ class OwnCACertificateProvider(CertificateProvider):
def add_ownca_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
argument_spec.argument_spec["provider"]["choices"].append("ownca")
argument_spec.argument_spec["provider"]["choices"].append("ownca") # type: ignore
argument_spec.argument_spec.update(
{
"ownca_path": {"type": "path"},

View File

@@ -243,7 +243,7 @@ class SelfSignedCertificateProvider(CertificateProvider):
def add_selfsigned_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
argument_spec.argument_spec["provider"]["choices"].append("selfsigned")
argument_spec.argument_spec["provider"]["choices"].append("selfsigned") # type: ignore
argument_spec.argument_spec.update(
{
"selfsigned_version": {