URL: https://github.com/freeipa/freeipa/pull/2152 Author: tiran Title: #2152: Teach pylint how our api works Action: opened
PR body: """ pylint 2.0 is more strict and complains about several aspects of ipalib.api. It turns out that AstroidBuilder.string_build() can be used to easily teach pylint about object attributes and attribute values. Although the assignment wouldn't work with the actual implementation, the string builder assignments shows pylint the names and values of members. It works without additional transformation. See: pagure.io/freeipa/issue/7614 Signed-off-by: Christian Heimes <chei...@redhat.com> """ To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/2152/head:pr2152 git checkout pr2152
From 3e844e4ee85440a292ab16f941c24320c0702ba5 Mon Sep 17 00:00:00 2001 From: Christian Heimes <chei...@redhat.com> Date: Fri, 13 Jul 2018 10:15:29 +0200 Subject: [PATCH 1/2] Teach pylint how our api works pylint 2.0 is more strict and complains about several aspects of ipalib.api. It turns out that AstroidBuilder.string_build() can be used to easily teach pylint about object attributes and attribute values. Although the assignment wouldn't work with the actual implementation, the string builder assignments shows pylint the names and values of members. It works without additional transformation. See: https://pagure.io/freeipa/issue/7614 Signed-off-by: Christian Heimes <chei...@redhat.com> --- pylint_plugins.py | 216 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 168 insertions(+), 48 deletions(-) diff --git a/pylint_plugins.py b/pylint_plugins.py index 896f954abe..ee00d1612f 100644 --- a/pylint_plugins.py +++ b/pylint_plugins.py @@ -58,30 +58,6 @@ def fake_class(name_or_class_obj, members=()): return cl -fake_backend = {'Backend': [ - {'wsgi_dispatch': ['mount']}, -]} - -NAMESPACE_ATTRS = ['Object', 'Method', fake_backend, 'Updater', - 'Advice'] -fake_api_env = { - 'env': [ - 'host', - 'realm', - 'kinit_lifetime', - ], - 'Command': [ - 'get_plugin', - 'config_show', - 'host_show', - 'service_show', - 'user_show', - ], -} - -# this is due ipaserver.rpcserver.KerberosSession where api is undefined -fake_api = {'api': [fake_api_env] + NAMESPACE_ATTRS} - # 'class': ['generated', 'properties'] ipa_class_members = { # Python standard library & 3rd party classes @@ -96,21 +72,6 @@ def fake_class(name_or_class_obj, members=()): 'find' ], 'ipalib.cli.Collector': ['__options'], - 'ipalib.config.Env': [ - {'__d': ['get']}, - {'__done': ['add']}, - 'ca_host', - 'ca_install_port', - 'debug', - {'domain': dir(str)}, - 'http_timeout', - 'rpc_protocol', - 'startup_traceback', - 'server', - 'validate_api', - 'verbose', - 'xmlrpc_uri', - ], 'ipalib.errors.ACIError': [ 'info', ], @@ -205,12 +166,6 @@ def fake_class(name_or_class_obj, members=()): 'require_service', ], 'ipalib.plugable.API': [ - fake_api_env, - ] + NAMESPACE_ATTRS, - 'ipalib.plugable.Plugin': [ - 'Object', - 'Method', - 'Updater', 'Advice', ], 'ipalib.util.ForwarderValidationError': [ @@ -220,9 +175,6 @@ def fake_class(name_or_class_obj, members=()): 'validatedns', 'normalizedns', ], - 'ipaserver.rpcserver.KerberosSession': [ - fake_api, - ], 'ipatests.test_integration.base.IntegrationTest': [ 'domain', {'master': [ @@ -404,3 +356,171 @@ def visit_import(self, node): def visit_importfrom(self, node): names = ['{}.{}'.format(node.modname, n[0]) for n in node.names] self._check_forbidden_imports(node, names) + + +# +# Teach pylint how api object works +# +# ipalib uses some tricks to create api.env members and api objects. pylint +# is not able to infer member names and types from code. The explict +# assignments inside the string builder templates are good enough to show +# pylint, how the api is created. Additional transformations are not +# required. +# + +AstroidBuilder(MANAGER).string_build(textwrap.dedent( + """ + from ipalib import api + from ipalib import cli, plugable, rpc + from ipalib.base import NameSpace + from ipaclient.plugins import rpcclient + try: + from ipaserver.plugins import dogtag, ldap2, serverroles + except ImportError: + HAS_SERVER = False + else: + HAS_SERVER = True + + def wildcard(*args, **kwargs): + return None + + # ipalib.api members + api.Backend = plugable.APINameSpace(api, None) + api.Command = plugable.APINameSpace(api, None) + api.Method = plugable.APINameSpace(api, None) + api.Object = plugable.APINameSpace(api, None) + api.Updater = plugable.APINameSpace(api, None) + # ipalib.api.Backend members + api.Backend.cli = cli.cli(api) + api.Backend.textui = cli.textui(api) + api.Backend.jsonclient = rpc.jsonclient(api) + api.Backend.rpcclient = rpcclient.rpcclient(api) + api.Backend.xmlclient = rpc.xmlclient(api) + + if HAS_SERVER: + api.Backend.kra = dogtag.kra(api) + api.Backend.ldap2 = ldap2.ldap2(api) + api.Backend.ra = dogtag.ra(api) + api.Backend.ra_certprofile = dogtag.ra_certprofile(api) + api.Backend.ra_lightweight_ca = dogtag.ra_lightweight_ca(api) + api.Backend.serverroles = serverroles.serverroles(api) + + # ipalib.base.NameSpace + NameSpace.find = wildcard + """ +)) + + +AstroidBuilder(MANAGER).string_build(textwrap.dedent( + """ + from ipalib import api + from ipapython.dn import DN + + api.env.api_version = '' + api.env.bin = '' # object + api.env.ca_agent_install_port = None + api.env.ca_agent_port = 0 + api.env.ca_ee_install_port = None + api.env.ca_ee_port = 0 + api.env.ca_host = '' + api.env.ca_install_port = None + api.env.ca_port = 0 + api.env.conf = '' # object + api.env.conf_default = '' # object + api.env.confdir = '' # object + api.env.container_accounts = DN() + api.env.container_adtrusts = DN() + api.env.container_applications = DN() + api.env.container_automember = DN() + api.env.container_automount = DN() + api.env.container_ca = DN() + api.env.container_caacl = DN() + api.env.container_certmap = DN() + api.env.container_certmaprules = DN() + api.env.container_certprofile = DN() + api.env.container_cifsdomains = DN() + api.env.container_configs = DN() + api.env.container_custodia = DN() + api.env.container_deleteuser = DN() + api.env.container_dna = DN() + api.env.container_dna_posix_ids = DN() + api.env.container_dns = DN() + api.env.container_dnsservers = DN() + api.env.container_group = DN() + api.env.container_hbac = DN() + api.env.container_hbacservice = DN() + api.env.container_hbacservicegroup = DN() + api.env.container_host = DN() + api.env.container_hostgroup = DN() + api.env.container_locations = DN() + api.env.container_masters = DN() + api.env.container_netgroup = DN() + api.env.container_otp = DN() + api.env.container_permission = DN() + api.env.container_policies = DN() + api.env.container_policygroups = DN() + api.env.container_policylinks = DN() + api.env.container_privilege = DN() + api.env.container_radiusproxy = DN() + api.env.container_ranges = DN() + api.env.container_realm_domains = DN() + api.env.container_rolegroup = DN() + api.env.container_roles = DN() + api.env.container_s4u2proxy = DN() + api.env.container_selinux = DN() + api.env.container_service = DN() + api.env.container_stageuser = DN() + api.env.container_sudocmd = DN() + api.env.container_sudocmdgroup = DN() + api.env.container_sudorule = DN() + api.env.container_sysaccounts = DN() + api.env.container_topology = DN() + api.env.container_trusts = DN() + api.env.container_user = DN() + api.env.container_vault = DN() + api.env.container_views = DN() + api.env.container_virtual = DN() + api.env.context = '' # object + api.env.debug = False + api.env.delegate = False + api.env.dogtag_version = 0 + api.env.dot_ipa = '' # object + api.env.enable_ra = False + api.env.env_confdir = None + api.env.fallback = True + api.env.force_schema_check = False + api.env.home = '' # object + api.env.host = '' + api.env.http_timeout = 0 + api.env.in_server = False # object + api.env.in_tree = False # object + api.env.interactive = True + api.env.ipalib = '' # object + api.env.kinit_lifetime = None + api.env.log = '' # object + api.env.logdir = '' # object + api.env.mode = '' + api.env.mount_ipa = '' + api.env.nss_dir = '' # object + api.env.plugins_on_demand = False # object + api.env.prompt_all = False + api.env.ra_plugin = '' + api.env.recommended_max_agmts = 0 + api.env.replication_wait_timeout = 0 + api.env.rpc_protocol = '' + api.env.server = '' + api.env.script = '' # object + api.env.site_packages = '' # object + api.env.skip_version_check = False + api.env.startup_timeout = 0 + api.env.startup_traceback = False + api.env.tls_ca_cert = '' # object + api.env.tls_version_max = '' + api.env.tls_version_min = '' + api.env.validate_api = False + api.env.verbose = 0 + api.env.version = '' + api.env.wait_for_dns = 0 + api.env.webui_prod = True + """ +)) From fde6a713fed2329b03bca6886e85a03af19c5b69 Mon Sep 17 00:00:00 2001 From: Christian Heimes <chei...@redhat.com> Date: Fri, 13 Jul 2018 11:07:06 +0200 Subject: [PATCH 2/2] Add pylint ignore to magic config.Env attributes pylinti 2 is having a hard time to handle name mangled, magic attributes correctly. Double under attributes like __d are internally renamed to _Env__d. After multiple failed attempts, it was easier to just add more pylint disable to the implementation. pylint 2 also thinkgs that Env.server is defined much later or the env doesn't have that member at all. Ignore the false warnings, too. Signed-off-by: Christian Heimes <chei...@redhat.com> --- ipalib/config.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/ipalib/config.py b/ipalib/config.py index 52b032a25a..f354547009 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -245,10 +245,12 @@ def __setitem__(self, key, value): SET_ERROR % (self.__class__.__name__, key, value) ) check_name(key) + # pylint: disable=no-member if key in self.__d: raise AttributeError(OVERRIDE_ERROR % (self.__class__.__name__, key, self.__d[key], value) ) + # pylint: enable=no-member assert not hasattr(self, key) if isinstance(value, six.string_types): value = value.strip() @@ -269,15 +271,15 @@ def __setitem__(self, key, value): if type(value) not in (unicode, int, float, bool, type(None), DN): raise TypeError(key, value) object.__setattr__(self, key, value) - # pylint: disable=unsupported-assignment-operation + # pylint: disable=unsupported-assignment-operation, no-member self.__d[key] = value - # pylint: enable=unsupported-assignment-operation + # pylint: enable=unsupported-assignment-operation, no-member def __getitem__(self, key): """ Return the value corresponding to ``key``. """ - return self.__d[key] + return self.__d[key] # pylint: disable=no-member def __delattr__(self, name): """ @@ -300,19 +302,19 @@ def __contains__(self, key): """ Return True if instance contains ``key``; otherwise return False. """ - return key in self.__d + return key in self.__d # pylint: disable=no-member def __len__(self): """ Return number of variables currently set. """ - return len(self.__d) + return len(self.__d) # pylint: disable=no-member def __iter__(self): """ Iterate through keys in ascending order. """ - for key in sorted(self.__d): + for key in sorted(self.__d): # pylint: disable=no-member yield key def _merge(self, **kw): @@ -405,6 +407,7 @@ def _join(self, key, *parts): return None def __doing(self, name): + # pylint: disable=no-member if name in self.__done: raise Exception( '%s.%s() already called' % (self.__class__.__name__, name) @@ -412,11 +415,11 @@ def __doing(self, name): self.__done.add(name) def __do_if_not_done(self, name): - if name not in self.__done: + if name not in self.__done: # pylint: disable=no-member getattr(self, name)() def _isdone(self, name): - return name in self.__done + return name in self.__done # pylint: disable=no-member def _bootstrap(self, **overrides): """ @@ -557,7 +560,8 @@ def _finalize_core(self, **defaults): self.__do_if_not_done('_bootstrap') # Merge in context config file and then default config file: - if self.__d.get('mode', None) != 'dummy': + mode = self.__d.get('mode') # pylint: disable=no-member + if mode != 'dummy': self._merge_from_file(self.conf) self._merge_from_file(self.conf_default) @@ -594,10 +598,12 @@ def _finalize_core(self, **defaults): # and server from jsonrpc_uri so that when only server or xmlrpc_uri # is specified, all 3 keys have a value.) if 'xmlrpc_uri' not in self and 'server' in self: + # pylint: disable=no-member, access-member-before-definition self.xmlrpc_uri = 'https://{}/ipa/xml'.format(self.server) # Derive ldap_uri from server if 'ldap_uri' not in self and 'server' in self: + # pylint: disable=no-member, access-member-before-definition self.ldap_uri = 'ldap://{}'.format(self.server) # Derive jsonrpc_uri from xmlrpc_uri
_______________________________________________ FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org Fedora Code of Conduct: https://getfedora.org/code-of-conduct.html List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedoraproject.org/archives/list/freeipa-devel@lists.fedorahosted.org/message/XUGZWUZVCLE45FL2JZICTHOQIYMVXF2T/