mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 07:33:27 -06:00
Add __signature__ to plugins
Auto-generate inspect.Signature from plugin arguments and options. The signature is used by (amongst others) pydoc / help. ``` $ ipa console >>> help(api.Command.group_add) Help on group_add in module ipaserver.plugins.group object: class group_add(ipaserver.plugins.baseldap.LDAPCreate) | group_add(cn: str, *, description: str = None, gidnumber: int = None, setattr: List[str] = None, addattr: List[str] = None, nonposix: bool, external: bool, all: bool, raw: bool, version: str = None, no_members: bool) -> Dict[str, Any] ``` Fixes: https://pagure.io/freeipa/issue/8388 Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
parent
51d5ec1757
commit
069f41a01e
@ -29,6 +29,7 @@ from ipapython.ipautil import APIVersion
|
||||
from ipalib.base import NameSpace
|
||||
from ipalib.plugable import Plugin, APINameSpace
|
||||
from ipalib.parameters import create_param, Param, Str, Flag
|
||||
from ipalib.parameters import create_signature
|
||||
from ipalib.parameters import Password # pylint: disable=unused-import
|
||||
from ipalib.output import Output, Entry, ListOfEntries
|
||||
from ipalib.text import _
|
||||
@ -37,7 +38,7 @@ from ipalib.errors import (ZeroArgumentError, MaxArgumentError, OverlapError,
|
||||
ValidationError, ConversionError)
|
||||
from ipalib import errors, messages
|
||||
from ipalib.request import context, context_frame
|
||||
from ipalib.util import classproperty, json_serialize
|
||||
from ipalib.util import classproperty, classobjectproperty, json_serialize
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
@ -433,6 +434,26 @@ class Command(HasParam):
|
||||
|
||||
topic = classproperty(__topic_getter)
|
||||
|
||||
@classobjectproperty
|
||||
@classmethod
|
||||
def __signature__(cls, obj):
|
||||
# signature is cached on the class object
|
||||
if hasattr(cls, "_signature"):
|
||||
return cls._signature
|
||||
# can only create signature for 'final' classes
|
||||
# help(api.Command.user_show) breaks because pydoc inspects parent
|
||||
# classes and baseuser plugin is not a registered object.
|
||||
if cls.__subclasses__():
|
||||
cls._signature = None
|
||||
return None
|
||||
# special, rare case: user calls help() on a plugin class instead of
|
||||
# an instance
|
||||
if obj is None:
|
||||
from ipalib import api
|
||||
obj = cls(api=api)
|
||||
cls._signature = signature = create_signature(obj)
|
||||
return signature
|
||||
|
||||
@property
|
||||
def forwarded_name(self):
|
||||
return self.full_name
|
||||
|
@ -103,10 +103,13 @@ import re
|
||||
import decimal
|
||||
import base64
|
||||
import datetime
|
||||
import inspect
|
||||
import typing
|
||||
from xmlrpc.client import MAXINT, MININT
|
||||
|
||||
import six
|
||||
from cryptography import x509 as crypto_x509
|
||||
import dns.name
|
||||
|
||||
from ipalib.text import _ as ugettext
|
||||
from ipalib.base import check_name
|
||||
@ -2159,3 +2162,67 @@ class Principal(Param):
|
||||
name=self.get_param_name(),
|
||||
error=_("Service principal is required")
|
||||
)
|
||||
|
||||
|
||||
_map_types = {
|
||||
# map internal certificate subclass to generic cryptography class
|
||||
IPACertificate: crypto_x509.Certificate,
|
||||
# map internal DNS name class to generic dnspython class
|
||||
DNSName: dns.name.Name,
|
||||
# DN, Principal have their names mangled in ipaapi.__init__
|
||||
}
|
||||
|
||||
|
||||
def create_signature(command):
|
||||
"""Create an inspect.Signature for a command
|
||||
|
||||
:param command: ipa plugin instance (server or client)
|
||||
:return: inspect.Signature instance
|
||||
"""
|
||||
|
||||
signature_params = []
|
||||
seen = set()
|
||||
args_options = [
|
||||
(command.get_args(), inspect.Parameter.POSITIONAL_OR_KEYWORD),
|
||||
(command.get_options(), inspect.Parameter.KEYWORD_ONLY)
|
||||
]
|
||||
for ipaparams, kind in args_options:
|
||||
for ipaparam in ipaparams:
|
||||
# filter out duplicates, for example user_del has a preserve flag
|
||||
# and preserve bool.
|
||||
if ipaparam.name in seen:
|
||||
continue
|
||||
seen.add(ipaparam.name)
|
||||
# ipalib.plugins.misc.env has wrong type
|
||||
if not isinstance(ipaparam, Param):
|
||||
continue
|
||||
|
||||
if ipaparam.required:
|
||||
default = inspect.Parameter.empty
|
||||
else:
|
||||
default = ipaparam.default
|
||||
|
||||
allowed_types = tuple(
|
||||
_map_types.get(t, t) for t in ipaparam.allowed_types
|
||||
)
|
||||
# ipalib.parameters.DNSNameParam also handles text
|
||||
if isinstance(ipaparam, DNSNameParam):
|
||||
allowed_types += (six.text_type,)
|
||||
ann = typing.Union[allowed_types]
|
||||
if ipaparam.multivalue:
|
||||
ann = typing.List[ann]
|
||||
|
||||
signature_params.append(
|
||||
inspect.Parameter(
|
||||
ipaparam.name, kind, default=default, annotation=ann
|
||||
)
|
||||
)
|
||||
|
||||
# cannot describe return parameter with typing yet. TypedDict
|
||||
# is only available with mypy_extension.
|
||||
signature = inspect.Signature(
|
||||
signature_params,
|
||||
return_annotation=typing.Dict[typing.Text, typing.Any]
|
||||
)
|
||||
|
||||
return signature
|
||||
|
@ -1027,6 +1027,7 @@ class classproperty:
|
||||
__slots__ = ('__doc__', 'fget')
|
||||
|
||||
def __init__(self, fget=None, doc=None):
|
||||
assert isinstance(fget, classmethod)
|
||||
if doc is None and fget is not None:
|
||||
doc = fget.__doc__
|
||||
|
||||
@ -1049,6 +1050,17 @@ class classproperty:
|
||||
return self
|
||||
|
||||
|
||||
class classobjectproperty(classproperty):
|
||||
# A class property that also passes the object to the getter
|
||||
# obj is None for class objects and 'self' for instance objects.
|
||||
__slots__ = ('__doc__',)
|
||||
|
||||
def __get__(self, obj, obj_type):
|
||||
if self.fget is not None:
|
||||
return self.fget.__get__(obj, obj_type)(obj)
|
||||
raise AttributeError("unreadable attribute")
|
||||
|
||||
|
||||
def normalize_hostname(hostname):
|
||||
"""Use common fqdn form without the trailing dot"""
|
||||
if hostname.endswith(u'.'):
|
||||
|
Loading…
Reference in New Issue
Block a user