Deprecate [rbac] configuration group.
The [rbac] configuration group has been deprecated
and will be removed in the next release. It has been
renamed to the [patrole] group which contains
the exact same options.
This commit makes necessary deprecation changes,
along with renaming changes to documentation, unit
tests and framework.
Change-Id: I71198506b97b98ac18a969b7e6b13b664579c081
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 550d2ce..8ec0013 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -5,7 +5,7 @@
Patrole can be customized by updating Tempest's ``tempest.conf`` configuration
file. All Patrole-specific configuration options should be included under
-the ``rbac`` group.
+the ``patrole`` group.
RBAC Test Role
--------------
diff --git a/doc/source/sampleconf.rst b/doc/source/sampleconf.rst
index 43933db..94ebc4d 100644
--- a/doc/source/sampleconf.rst
+++ b/doc/source/sampleconf.rst
@@ -7,7 +7,7 @@
.. code-block:: ini
- [rbac]
+ [patrole]
# The role that you want the RBAC tests to use for RBAC testing
# This needs to be edited to run the test as a different role.
diff --git a/doc/source/usage.rst b/doc/source/usage.rst
index 7470e9e..dff43f2 100644
--- a/doc/source/usage.rst
+++ b/doc/source/usage.rst
@@ -30,7 +30,7 @@
To change the role that the patrole tests are being run as, edit
``rbac_test_role`` in the ``rbac`` section of tempest.conf: ::
- [rbac]
+ [patrole]
rbac_test_role = Member
...
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index a6f30e7..a53edd4 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -15,19 +15,23 @@
from oslo_config import cfg
-rbac_group = cfg.OptGroup(name='rbac',
- title='RBAC testing options')
-RbacGroup = [
+patrole_group = cfg.OptGroup(name='patrole', title='Patrole Testing Options')
+
+
+PatroleGroup = [
cfg.StrOpt('rbac_test_role',
default='admin',
+ deprecated_group='rbac',
help="""The current RBAC role against which to run Patrole
tests."""),
cfg.BoolOpt('enable_rbac',
default=True,
+ deprecated_group='rbac',
help="Enables RBAC tests."),
cfg.BoolOpt('strict_policy_check',
default=False,
+ deprecated_group='rbac',
help="""If true, throws RbacParsingException for policies which
don't exist or are not included in the service's policy file. If false, throws
skipException."""),
@@ -35,6 +39,7 @@
# other hosts. It may be possible to leverage the v3 identity policy API.
cfg.ListOpt('custom_policy_files',
default=['/etc/%s/policy.json'],
+ deprecated_group='rbac',
help="""List of the paths to search for policy files. Each
policy path assumes that the service name is included in the path once. Also
assumes Patrole is on the same host as the policy files. The paths should be
@@ -45,6 +50,7 @@
default='/etc/cinder/policy.json',
help="""Location of the Cinder policy file. Assumed to be on
the same host as Patrole.""",
+ deprecated_group='rbac',
deprecated_for_removal=True,
deprecated_reason="It is better to use `custom_policy_files` "
"which supports any OpenStack service."),
@@ -52,6 +58,7 @@
default='/etc/glance/policy.json',
help="""Location of the Glance policy file. Assumed to be on
the same host as Patrole.""",
+ deprecated_group='rbac',
deprecated_for_removal=True,
deprecated_reason="It is better to use `custom_policy_files` "
"which supports any OpenStack service."),
@@ -59,6 +66,7 @@
default='/etc/keystone/policy.json',
help="""Location of the custom Keystone policy file. Assumed to
be on the same host as Patrole.""",
+ deprecated_group='rbac',
deprecated_for_removal=True,
deprecated_reason="It is better to use `custom_policy_files` "
"which supports any OpenStack service."),
@@ -66,6 +74,7 @@
default='/etc/neutron/policy.json',
help="""Location of the Neutron policy file. Assumed to be on
the same host as Patrole.""",
+ deprecated_group='rbac',
deprecated_for_removal=True,
deprecated_reason="It is better to use `custom_policy_files` "
"which supports any OpenStack service."),
@@ -73,11 +82,13 @@
default='/etc/nova/policy.json',
help="""Location of the custom Nova policy file. Assumed to be
on the same host as Patrole.""",
+ deprecated_group='rbac',
deprecated_for_removal=True,
deprecated_reason="It is better to use `custom_policy_files` "
"which supports any OpenStack service."),
cfg.BoolOpt('test_custom_requirements',
default=False,
+ deprecated_group='rbac',
help="""
This option determines whether Patrole should run against a
`custom_requirements_file` which defines RBAC requirements. The
@@ -101,6 +112,7 @@
test result: fail (over-permission)
"""),
cfg.StrOpt('custom_requirements_file',
+ deprecated_group='rbac',
help="""
File path of the yaml file that defines your RBAC requirements. This
file must be located on the same host that Patrole runs on. The yaml
@@ -128,3 +140,10 @@
allowed_role = the Keystone role that is allowed to perform the API
""")
]
+
+
+rbac_group = cfg.OptGroup(name='rbac',
+ title='RBAC testing options',
+ help="This group is deprecated and will be removed "
+ "in the next release. Use the [patrole] group "
+ "instead.")
diff --git a/patrole_tempest_plugin/plugin.py b/patrole_tempest_plugin/plugin.py
index 28ce12c..4bba037 100644
--- a/patrole_tempest_plugin/plugin.py
+++ b/patrole_tempest_plugin/plugin.py
@@ -30,10 +30,17 @@
return full_test_dir, base_path
def register_opts(self, conf):
+ # TODO(fmontei): Remove ``rbac_group`` in a future release as it is
+ # currently deprecated.
config.register_opt_group(
conf,
project_config.rbac_group,
- project_config.RbacGroup)
+ project_config.PatroleGroup)
+ config.register_opt_group(
+ conf,
+ project_config.patrole_group,
+ project_config.PatroleGroup)
def get_opt_lists(self):
- return [(project_config.rbac_group.name, project_config.RbacGroup)]
+ return [(project_config.patrole_group.name,
+ project_config.PatroleGroup)]
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 88e9faa..aff4e66 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -72,11 +72,11 @@
# Prioritize dynamically searching for policy files over relying on
# deprecated service-specific policy file locations.
- if CONF.rbac.custom_policy_files:
+ if CONF.patrole.custom_policy_files:
self.discover_policy_files()
self.path = self.policy_files.get(service)
else:
- self.path = getattr(CONF.rbac, '%s_policy_file' % str(service),
+ self.path = getattr(CONF.patrole, '%s_policy_file' % str(service),
None)
self.rules = policy.Rules.load(self._get_policy_data(service),
@@ -110,11 +110,11 @@
def discover_policy_files(cls):
# Dynamically discover the policy file for each service in
# ``cls.available_services``. Pick the first ``candidate_path`` found
- # out of the potential paths in ``CONF.rbac.custom_policy_files``.
+ # out of the potential paths in ``CONF.patrole.custom_policy_files``.
if not hasattr(cls, 'policy_files'):
cls.policy_files = {}
for service in cls.available_services:
- for candidate_path in CONF.rbac.custom_policy_files:
+ for candidate_path in CONF.patrole.custom_policy_files:
if os.path.isfile(candidate_path % service):
cls.policy_files.setdefault(service,
candidate_path % service)
@@ -178,7 +178,7 @@
error_message = (
'Policy file for {0} service neither found in code nor at {1}.'
.format(service, [loc % service for loc in
- CONF.rbac.custom_policy_files])
+ CONF.patrole.custom_policy_files])
)
raise rbac_exceptions.RbacParsingException(error_message)
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index c7bd38b..9705367 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -72,7 +72,7 @@
extra_target_data = {}
def decorator(func):
- role = CONF.rbac.rbac_test_role
+ role = CONF.patrole.rbac_test_role
def wrapper(*args, **kwargs):
if args and isinstance(args[0], test.BaseTestCase):
@@ -149,9 +149,9 @@
:raises RbacResourceSetupFailed: if project_id or user_id are missing from
the Tempest test object's `auth_provider`
- :raises RbacParsingException: if ``CONF.rbac.strict_policy_check`` is
+ :raises RbacParsingException: if ``CONF.patrole.strict_policy_check`` is
enabled and the ``rule_name`` does not exist in the system
- :raises skipException: if ``CONF.rbac.strict_policy_check`` is
+ :raises skipException: if ``CONF.patrole.strict_policy_check`` is
disabled and the ``rule_name`` does not exist in the system
"""
try:
@@ -164,11 +164,11 @@
raise rbac_exceptions.RbacResourceSetupFailed(msg)
try:
- role = CONF.rbac.rbac_test_role
+ role = CONF.patrole.rbac_test_role
# Test RBAC against custom requirements. Otherwise use oslo.policy
- if CONF.rbac.test_custom_requirements:
+ if CONF.patrole.test_custom_requirements:
authority = requirements_authority.RequirementsAuthority(
- CONF.rbac.custom_requirements_file, service)
+ CONF.patrole.custom_requirements_file, service)
else:
formatted_target_data = _format_extra_target_data(
test_obj, extra_target_data)
@@ -185,7 +185,7 @@
rule_name, role)
return is_allowed
except rbac_exceptions.RbacParsingException as e:
- if CONF.rbac.strict_policy_check:
+ if CONF.patrole.strict_policy_check:
raise e
else:
raise testtools.TestCase.skipException(str(e))
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 5543cbb..a7da2d3 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -39,7 +39,7 @@
seamlessly swap between admin credentials, needed for setup and clean up,
and primary credentials, needed to perform the API call which does
policy enforcement. The primary credentials always cycle between roles
- defined by ``[identity] admin_role`` and ``[rbac] rbac_test_role``.
+ defined by ``CONF.identity.admin_role`` and `CONF.patrole.rbac_test_role``.
"""
def __init__(self, test_obj):
@@ -78,14 +78,10 @@
Switch the role used by `os_primary` credentials to:
* admin if `toggle_rbac_role` is False
- * `[rbac] rbac_test_role` if `toggle_rbac_role` is True
+ * `CONF.patrole.rbac_test_role` if `toggle_rbac_role` is True
- :param test_obj: An instance of `tempest.test.BaseTestCase`.
- :param toggle_rbac_role: Role to switch `os_primary` Tempest creds to.
- :returns: None.
- :raises RbacResourceSetupFailed: If admin or test roles are missing. Or
- if `toggle_rbac_role` is not a boolean value or role validation
- fails.
+ :param test_obj: test object of type tempest.lib.base.BaseTestCase
+ :param toggle_rbac_role: role to switch `os_primary` Tempest creds to
"""
self.user_id = test_obj.os_primary.credentials.user_id
self.project_id = test_obj.os_primary.credentials.tenant_id
@@ -127,14 +123,14 @@
admin_role_id = rbac_role_id = None
for role in available_roles['roles']:
- if role['name'] == CONF.rbac.rbac_test_role:
+ if role['name'] == CONF.patrole.rbac_test_role:
rbac_role_id = role['id']
if role['name'] == CONF.identity.admin_role:
admin_role_id = role['id']
if not all([admin_role_id, rbac_role_id]):
- msg = ("Roles defined by `[rbac] rbac_test_role` and `[identity] "
- "admin_role` must be defined in the system.")
+ msg = ("Roles defined by `[patrole] rbac_test_role` and "
+ "`[identity] admin_role` must be defined in the system.")
raise rbac_exceptions.RbacResourceSetupFailed(msg)
self.admin_role_id = admin_role_id
@@ -201,13 +197,32 @@
else:
self.switch_role_history[key] = toggle_rbac_role
+ def _get_roles(self):
+ available_roles = self.admin_roles_client.list_roles()
+ admin_role_id = rbac_role_id = None
+
+ for role in available_roles['roles']:
+ if role['name'] == CONF.patrole.rbac_test_role:
+ rbac_role_id = role['id']
+ if role['name'] == CONF.identity.admin_role:
+ admin_role_id = role['id']
+
+ if not admin_role_id or not rbac_role_id:
+ msg = "Role with name 'admin' does not exist in the system."\
+ if not admin_role_id else "Role defined by rbac_test_role "\
+ "does not exist in the system."
+ raise rbac_exceptions.RbacResourceSetupFailed(msg)
+
+ self.admin_role_id = admin_role_id
+ self.rbac_role_id = rbac_role_id
+
def is_admin():
"""Verifies whether the current test role equals the admin role.
:returns: True if ``rbac_test_role`` is the admin role.
"""
- return CONF.rbac.rbac_test_role == CONF.identity.admin_role
+ return CONF.patrole.rbac_test_role == CONF.identity.admin_role
@six.add_metaclass(abc.ABCMeta)
diff --git a/patrole_tempest_plugin/tests/api/compute/rbac_base.py b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
index 7e9a402..3807ae9 100644
--- a/patrole_tempest_plugin/tests/api/compute/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
@@ -26,7 +26,7 @@
@classmethod
def skip_checks(cls):
super(BaseV2ComputeRbacTest, cls).skip_checks()
- if not CONF.rbac.enable_rbac:
+ if not CONF.patrole.enable_rbac:
raise cls.skipException(
'%s skipped as RBAC testing not enabled' % cls.__name__)
diff --git a/patrole_tempest_plugin/tests/api/identity/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
index 8ee80c6..51daf96 100644
--- a/patrole_tempest_plugin/tests/api/identity/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
@@ -31,7 +31,7 @@
@classmethod
def skip_checks(cls):
super(BaseIdentityV2RbacTest, cls).skip_checks()
- if not CONF.rbac.enable_rbac:
+ if not CONF.patrole.enable_rbac:
raise cls.skipException(
"%s skipped as RBAC testing not enabled" % cls.__name__)
diff --git a/patrole_tempest_plugin/tests/api/image/rbac_base.py b/patrole_tempest_plugin/tests/api/image/rbac_base.py
index e49e2f6..dd4e5ed 100644
--- a/patrole_tempest_plugin/tests/api/image/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/image/rbac_base.py
@@ -24,7 +24,7 @@
@classmethod
def skip_checks(cls):
super(BaseV1ImageRbacTest, cls).skip_checks()
- if not CONF.rbac.enable_rbac:
+ if not CONF.patrole.enable_rbac:
raise cls.skipException(
"%s skipped as RBAC testing not enabled" % cls.__name__)
@@ -39,7 +39,7 @@
@classmethod
def skip_checks(cls):
super(BaseV2ImageRbacTest, cls).skip_checks()
- if not CONF.rbac.enable_rbac:
+ if not CONF.patrole.enable_rbac:
raise cls.skipException(
"%s skipped as RBAC testing not enabled" % cls.__name__)
diff --git a/patrole_tempest_plugin/tests/api/network/rbac_base.py b/patrole_tempest_plugin/tests/api/network/rbac_base.py
index 6cf0138..b495098 100644
--- a/patrole_tempest_plugin/tests/api/network/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/network/rbac_base.py
@@ -26,7 +26,7 @@
@classmethod
def skip_checks(cls):
super(BaseNetworkRbacTest, cls).skip_checks()
- if not CONF.rbac.enable_rbac:
+ if not CONF.patrole.enable_rbac:
raise cls.skipException(
"%s skipped as RBAC testing not enabled" % cls.__name__)
diff --git a/patrole_tempest_plugin/tests/api/volume/rbac_base.py b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
index 4d3b612..c43c552 100644
--- a/patrole_tempest_plugin/tests/api/volume/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
@@ -26,7 +26,7 @@
@classmethod
def skip_checks(cls):
super(BaseVolumeRbacTest, cls).skip_checks()
- if not CONF.rbac.enable_rbac:
+ if not CONF.patrole.enable_rbac:
raise cls.skipException(
"%s skipped as RBAC testing not enabled" % cls.__name__)
diff --git a/patrole_tempest_plugin/tests/unit/fixtures.py b/patrole_tempest_plugin/tests/unit/fixtures.py
index ed50e15..6b42949 100644
--- a/patrole_tempest_plugin/tests/unit/fixtures.py
+++ b/patrole_tempest_plugin/tests/unit/fixtures.py
@@ -61,7 +61,7 @@
def setUp(self):
super(RbacUtilsFixture, self).setUp()
- self.useFixture(ConfPatcher(rbac_test_role='member', group='rbac'))
+ self.useFixture(ConfPatcher(rbac_test_role='member', group='patrole'))
self.useFixture(ConfPatcher(
admin_role='admin', auth_version='v3', group='identity'))
diff --git a/patrole_tempest_plugin/tests/unit/test_patrole.py b/patrole_tempest_plugin/tests/unit/test_patrole.py
index 58aff05..9b8e88c 100644
--- a/patrole_tempest_plugin/tests/unit/test_patrole.py
+++ b/patrole_tempest_plugin/tests/unit/test_patrole.py
@@ -14,16 +14,23 @@
# under the License.
"""
-test_patrole
-----------------------------------
-
Tests for `patrole` module.
"""
+from tempest import config
+
from patrole_tempest_plugin.tests.unit import base
+CONF = config.CONF
+
class TestPatrole(base.TestCase):
- def test_something(self):
- pass
+ def test_rbac_group_backwards_compatability(self):
+ """Validate that the deprecated group [rbac] is available and has the
+ same options and option values as [patrole] group, which is current.
+ """
+ self.assertTrue(hasattr(CONF, 'patrole'))
+ self.assertTrue(hasattr(CONF, 'rbac'))
+ # Validate that both groups are identical.
+ self.assertEqual(CONF.patrole.items(), CONF.rbac.items())
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
index 36fa045..6f173a2 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -64,9 +64,9 @@
current_directory, 'resources', '%s.json')
CONF.set_override(
- 'custom_policy_files', [self.conf_policy_path], group='rbac')
+ 'custom_policy_files', [self.conf_policy_path], group='patrole')
self.addCleanup(CONF.clear_override, 'custom_policy_files',
- group='rbac')
+ group='patrole')
# Guarantee a blank slate for each test.
for attr in ('available_services', 'policy_files'):
@@ -393,7 +393,7 @@
'Policy file for {0} service neither found in code '\
'nor at {1}.'.format(
'test_service',
- [CONF.rbac.custom_policy_files[0] % 'test_service'])
+ [CONF.patrole.custom_policy_files[0] % 'test_service'])
self.assertIn(expected_error, str(e))
@@ -439,7 +439,7 @@
expected_error = (
'Policy file for {0} service neither found in code nor at {1}.'
- .format('tenant_rbac_policy', [CONF.rbac.custom_policy_files[0]
+ .format('tenant_rbac_policy', [CONF.patrole.custom_policy_files[0]
% 'tenant_rbac_policy']))
self.assertIn(expected_error, str(e))
@@ -473,7 +473,7 @@
# The expected policy will be 'baz/test_service'.
CONF.set_override(
'custom_policy_files', ['foo/%s', 'bar/%s', 'baz/%s'],
- group='rbac')
+ group='patrole')
policy_parser = rbac_policy_parser.RbacPolicyParser(
None, None, 'test_service')
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
index a9acf1c..8a69ff6 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -37,8 +37,8 @@
self.mock_args.os_primary.credentials.user_id = \
mock.sentinel.user_id
- CONF.set_override('rbac_test_role', 'Member', group='rbac')
- self.addCleanup(CONF.clear_override, 'rbac_test_role', group='rbac')
+ CONF.set_override('rbac_test_role', 'Member', group='patrole')
+ self.addCleanup(CONF.clear_override, 'rbac_test_role', group='patrole')
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
@@ -310,9 +310,9 @@
def test_invalid_policy_rule_throws_parsing_exception(
self, mock_rbac_policy_parser):
"""Test that invalid policy action causes test to be skipped."""
- CONF.set_override('strict_policy_check', True, group='rbac')
+ CONF.set_override('strict_policy_check', True, group='patrole')
self.addCleanup(CONF.clear_override, 'strict_policy_check',
- group='rbac')
+ group='patrole')
mock_rbac_policy_parser.RbacPolicyParser.return_value.allowed.\
side_effect = rbac_exceptions.RbacParsingException
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index 540283a..87adff0 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -35,7 +35,7 @@
def test_switch_role_with_missing_admin_role(self):
self.rbac_utils.set_roles('member')
error_re = (
- 'Roles defined by `\[rbac\] rbac_test_role` and `\[identity\] '
+ 'Roles defined by `\[patrole\] rbac_test_role` and `\[identity\] '
'admin_role` must be defined in the system.')
self.assertRaisesRegex(rbac_exceptions.RbacResourceSetupFailed,
error_re, self.rbac_utils.switch_role)
@@ -43,7 +43,7 @@
def test_switch_role_with_missing_rbac_role(self):
self.rbac_utils.set_roles('admin')
error_re = (
- 'Roles defined by `\[rbac\] rbac_test_role` and `\[identity\] '
+ 'Roles defined by `\[patrole\] rbac_test_role` and `\[identity\] '
'admin_role` must be defined in the system.')
self.assertRaisesRegex(rbac_exceptions.RbacResourceSetupFailed,
error_re, self.rbac_utils.switch_role)
diff --git a/releasenotes/notes/deprecate-rbac-group-148e222913dc74cc.yaml b/releasenotes/notes/deprecate-rbac-group-148e222913dc74cc.yaml
new file mode 100644
index 0000000..1b1a5ac
--- /dev/null
+++ b/releasenotes/notes/deprecate-rbac-group-148e222913dc74cc.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+ - |
+ The ``[rbac]`` configuration group has been deprecated and will be removed
+ in the next release. Use ``[patrole]`` group instead, which has the exact
+ same options.
diff --git a/tox.ini b/tox.ini
index 41e893c..f1b5b86 100644
--- a/tox.ini
+++ b/tox.ini
@@ -59,7 +59,11 @@
enable-extensions = H106,H203,H904
show-source = True
# E123, E125 skipped as they are invalid PEP-8.
-ignore = E123,E125
+#
+# H405 is another one that is good as a guideline, but sometimes
+# multiline doc strings just don't have a natural summary
+# line. Rejecting code for this reason is wrong.
+ignore = E123,E125,H405
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build