diff --git a/.pylintrc b/.pylintrc index a49f1d94ad..bed3fb8660 100644 --- a/.pylintrc +++ b/.pylintrc @@ -15,12 +15,14 @@ disable= not-callable, # "W" Warnings for stylistic problems or minor programming issues arguments-differ, + arguments-renamed, attribute-defined-outside-init, bad-indentation, broad-except, fixme, # python3 way: pylint suggests to follow PEP 3102 keyword-arg-before-vararg, # TODO + missing-timeout, pointless-string-statement, protected-access, # We should do it carefully considering PEP3134 @@ -37,9 +39,8 @@ disable= # "C" Coding convention violations abstract-method, anomalous-backslash-in-string, - bad-builtin, - bad-continuation, - deprecated-lambda, + consider-using-dict-items, + consider-using-f-string, global-statement, # Not a good idea to disable this globally # Check one by one and add pylint disabled comment if needed @@ -52,19 +53,22 @@ disable= # import order is checked by flake8 (and pylint rule is incompatible with it) wrong-import-order, # "R" Refactor recommendations + consider-using-generator, duplicate-code, inconsistent-return-statements, # TODO - interface-not-implemented, - no-self-use, too-many-ancestors, too-many-arguments, too-many-branches, too-many-function-args, too-many-instance-attributes, too-many-locals, + too-many-positional-arguments, too-many-return-statements, too-many-statements, - useless-object-inheritance + use-a-generator, + use-dict-literal, + use-yield-from, + useless-object-inheritance, [Basic] # Variable names can be 1 to 31 characters long, with lowercase and underscores diff --git a/.zuul.d/django-jobs.yaml b/.zuul.d/django-jobs.yaml index 402dc0089c..7ccc92a869 100644 --- a/.zuul.d/django-jobs.yaml +++ b/.zuul.d/django-jobs.yaml @@ -17,10 +17,10 @@ pre-run: playbooks/horizon-tox-django/pre.yaml run: playbooks/horizon-tox-django/run.yaml vars: - tox_envlist: py311 + tox_envlist: py312 # The following should match the base openstack-tox-pyNN job. - bindep_profile: test py311 - python_version: "3.11" + bindep_profile: test py312 + python_version: "3.12" required-projects: - name: openstack/horizon @@ -42,7 +42,7 @@ # sentinel to make this project template valid even when we support # only one version of Django used as the default. Zuul project # template configuration requires at least one job included. - - openstack-tox-py311 + - openstack-tox-py312 # Let's keep at least one job as a template even when we support # only one version of Django covered by the default job. # Just comment it out for such case. @@ -50,5 +50,5 @@ gate: jobs: # Default python job in openstack-python3-antelope-jobs(-horizon) - - openstack-tox-py311 + - openstack-tox-py312 - horizon-tox-python3-django42 diff --git a/horizon/base.py b/horizon/base.py index 463a773005..01fc9cff8d 100644 --- a/horizon/base.py +++ b/horizon/base.py @@ -595,6 +595,7 @@ class Dashboard(Registry, HorizonComponent): panel_group = PanelGroup(self, panels=panel_set) # Put our results into their appropriate places + # pylint: disable-next=possibly-used-before-assignment panels_to_discover.extend(panel_group.panels) panel_groups.append((panel_group.slug, panel_group)) if panel_group.slug == DEFAULT_PANEL_GROUP: @@ -615,6 +616,7 @@ class Dashboard(Registry, HorizonComponent): before_import_registry = copy.copy(self._registry) import_module('.%s.panel' % panel, package) except Exception: + # pylint: disable-next=used-before-assignment self._registry = before_import_registry if module_has_submodule(mod, panel): raise @@ -866,6 +868,7 @@ class Site(Registry, HorizonComponent): before_import_registry = copy.copy(self._registry) import_module('%s.%s' % (package, mod_name)) except Exception: + # pylint: disable-next=used-before-assignment self._registry = before_import_registry if module_has_submodule(mod, mod_name): raise @@ -897,6 +900,7 @@ class Site(Registry, HorizonComponent): before_import_registry = copy.copy(self._registry) import_module('%s.%s' % (app, mod_name)) except Exception: + # pylint: disable-next=used-before-assignment self._registry = before_import_registry if module_has_submodule(mod, mod_name): raise diff --git a/horizon/forms/fields.py b/horizon/forms/fields.py index 648be448cb..476af0e5d8 100644 --- a/horizon/forms/fields.py +++ b/horizon/forms/fields.py @@ -628,14 +628,14 @@ class ExternalUploadMeta(forms.DeclarativeFieldsMetaclass): """ @classmethod - def __prepare__(cls, name, bases): + def __prepare__(mcs, name, bases): # Required in python 3 to keep the form fields order. # Without this method, the __new__(cls, name, bases, attrs) method # receives a dict as attrs instead of OrderedDict. # This method will be ignored by Python 2. return collections.OrderedDict() - def __new__(cls, name, bases, attrs): + def __new__(mcs, name, bases, attrs): def get_double_name(name): suffix = '__hidden' slen = len(suffix) @@ -660,4 +660,4 @@ class ExternalUploadMeta(forms.DeclarativeFieldsMetaclass): new_attrs[new_attr_name] = hidden_field meth_name = 'clean_' + new_attr_name new_attrs[meth_name] = make_clean_method(new_attr_name) - return super().__new__(cls, name, bases, new_attrs) + return super().__new__(mcs, name, bases, new_attrs) diff --git a/horizon/management/commands/pull_catalog.py b/horizon/management/commands/pull_catalog.py index 3d89d8c7d7..55acf904f7 100644 --- a/horizon/management/commands/pull_catalog.py +++ b/horizon/management/commands/pull_catalog.py @@ -100,5 +100,5 @@ class Command(BaseCommand): # Ensure to use UTF-8 encoding new_po.encoding = 'utf-8' - with open(pofile, 'w+') as f: + with open(pofile, 'w+', encoding="utf-8") as f: f.write(new_po.text) diff --git a/horizon/management/commands/startpanel.py b/horizon/management/commands/startpanel.py index 1ff087846e..5048d988b8 100644 --- a/horizon/management/commands/startpanel.py +++ b/horizon/management/commands/startpanel.py @@ -69,6 +69,7 @@ class Command(TemplateCommand): target = options.pop("target", None) if target == "auto": + # pylint: disable-next=possibly-used-before-assignment target = os.path.join(os.path.dirname(dashboard_mod.__file__), panel_name) if not os.path.exists(target): diff --git a/horizon/notifications.py b/horizon/notifications.py index f89579f918..79758b5b43 100644 --- a/horizon/notifications.py +++ b/horizon/notifications.py @@ -110,7 +110,7 @@ def _is_path(path): def _get_processed_messages(messages_path): - msgs = list() + msgs = list() # pylint: disable=use-list-literal if not _is_path(messages_path): LOG.error('%s is not a valid messages path.', messages_path) diff --git a/horizon/tables/actions.py b/horizon/tables/actions.py index dbbc3092a0..b554650e80 100644 --- a/horizon/tables/actions.py +++ b/horizon/tables/actions.py @@ -48,7 +48,7 @@ class BaseActionMetaClass(type): parameters for the initializer of the object. The object is then initialized clean way. Similar principle is used in DataTableMetaclass. """ - def __new__(cls, name, bases, attrs): + def __new__(mcs, name, bases, attrs): # Options of action are set as class attributes, loading them. options = {} if attrs: @@ -74,7 +74,7 @@ class BaseActionMetaClass(type): # instantiating of the specific Action. attrs['base_options'] = options - return type.__new__(cls, name, bases, attrs) + return type.__new__(mcs, name, bases, attrs) def __call__(cls, *args, **kwargs): cls.base_options.update(kwargs) diff --git a/horizon/tables/base.py b/horizon/tables/base.py index 630b54edb7..bd5019dd5f 100644 --- a/horizon/tables/base.py +++ b/horizon/tables/base.py @@ -1175,7 +1175,7 @@ class DataTableOptions(object): class DataTableMetaclass(type): """Metaclass to add options to DataTable class and collect columns.""" - def __new__(cls, name, bases, attrs): + def __new__(mcs, name, bases, attrs): # Process options from Meta class_name = name dt_attrs = {} @@ -1247,7 +1247,7 @@ class DataTableMetaclass(type): opts._filter_action = actions_dict[opts._filter_action.name] # Create our new class! - return type.__new__(cls, name, bases, dt_attrs) + return type.__new__(mcs, name, bases, dt_attrs) class DataTable(object, metaclass=DataTableMetaclass): diff --git a/horizon/utils/secret_key.py b/horizon/utils/secret_key.py index ca03b64a3c..fe254de23d 100644 --- a/horizon/utils/secret_key.py +++ b/horizon/utils/secret_key.py @@ -48,7 +48,7 @@ def read_from_file(key_file='.secret_key'): raise FilePermissionError( "Insecure permissions on key file %s, should be 0600." % os.path.abspath(key_file)) - with open(key_file, 'r') as f: + with open(key_file, 'r', encoding="utf-8") as f: key = f.readline() return key @@ -76,7 +76,7 @@ def generate_or_read_from_file(key_file='.secret_key', key_length=64): if not os.path.exists(key_file): key = generate_key(key_length) old_umask = os.umask(0o177) # Use '0600' file permissions - with open(key_file, 'w') as f: + with open(key_file, 'w', encoding="utf-8") as f: f.write(key) os.umask(old_umask) else: diff --git a/horizon/workflows/base.py b/horizon/workflows/base.py index 69003e26e8..075fac303a 100644 --- a/horizon/workflows/base.py +++ b/horizon/workflows/base.py @@ -54,28 +54,30 @@ class WorkflowContext(dict): return self.__setitem__(key, None) def set(self, key, val): + # pylint: disable-next=unnecessary-dunder-call return self.__setitem__(key, val) def unset(self, key): + # pylint: disable-next=unnecessary-dunder-call return self.__delitem__(key) class ActionMetaclass(forms.forms.DeclarativeFieldsMetaclass): - def __new__(cls, name, bases, attrs): + def __new__(mcs, name, bases, attrs): # Pop Meta for later processing opts = attrs.pop("Meta", None) # Create our new class - cls_ = super().__new__(cls, name, bases, attrs) + mcs_ = super().__new__(mcs, name, bases, attrs) # Process options from Meta - cls_.name = getattr(opts, "name", name) - cls_.slug = getattr(opts, "slug", slugify(name)) - cls_.permissions = getattr(opts, "permissions", ()) - cls_.policy_rules = getattr(opts, "policy_rules", ()) - cls_.progress_message = getattr(opts, "progress_message", + mcs_.name = getattr(opts, "name", name) + mcs_.slug = getattr(opts, "slug", slugify(name)) + mcs_.permissions = getattr(opts, "permissions", ()) + mcs_.policy_rules = getattr(opts, "policy_rules", ()) + mcs_.progress_message = getattr(opts, "progress_message", _("Processing...")) - cls_.help_text = getattr(opts, "help_text", "") - cls_.help_text_template = getattr(opts, "help_text_template", None) - return cls_ + mcs_.help_text = getattr(opts, "help_text", "") + mcs_.help_text_template = getattr(opts, "help_text_template", None) + return mcs_ class Action(forms.Form, metaclass=ActionMetaclass): @@ -478,10 +480,10 @@ class Step(object): class WorkflowMetaclass(type): - def __new__(cls, name, bases, attrs): - super().__new__(cls, name, bases, attrs) + def __new__(mcs, name, bases, attrs): + super().__new__(mcs, name, bases, attrs) attrs["_cls_registry"] = [] - return type.__new__(cls, name, bases, attrs) + return type.__new__(mcs, name, bases, attrs) class UpdateMembersStep(Step): diff --git a/openstack_auth/policy.py b/openstack_auth/policy.py index 767561ad28..ae58209414 100644 --- a/openstack_auth/policy.py +++ b/openstack_auth/policy.py @@ -90,7 +90,7 @@ def _load_default_rules(service, enforcer): return try: - with open(policy_file) as f: + with open(policy_file, encoding="utf-8") as f: policies = yaml.safe_load(f) except IOError as e: LOG.error('Failed to open the policy file for %(service)s %(path)s: ' diff --git a/openstack_dashboard/api/base.py b/openstack_dashboard/api/base.py index 5a367b583f..a6f444c5dd 100644 --- a/openstack_dashboard/api/base.py +++ b/openstack_dashboard/api/base.py @@ -280,6 +280,7 @@ class QuotaSet(collections.abc.Sequence): return match.pop() if match else Quota(key, default) def add(self, other): + # pylint: disable-next=unnecessary-dunder-call return self.__add__(other) diff --git a/openstack_dashboard/api/rest/json_encoder.py b/openstack_dashboard/api/rest/json_encoder.py index b90fd04a82..f82cd12b70 100644 --- a/openstack_dashboard/api/rest/json_encoder.py +++ b/openstack_dashboard/api/rest/json_encoder.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. import json -import json.encoder as encoder +from json import encoder from django.utils.translation import gettext_lazy as _ diff --git a/openstack_dashboard/contrib/developer/profiler/api.py b/openstack_dashboard/contrib/developer/profiler/api.py index 1fa25eadbc..b0ab96ddc8 100644 --- a/openstack_dashboard/contrib/developer/profiler/api.py +++ b/openstack_dashboard/contrib/developer/profiler/api.py @@ -76,8 +76,7 @@ def get_trace(trace_id): # finishes before the dependent requests do so, to we need to # normalize the duration of all requests by the finishing time of # the one which took longest - if child_finished > finished: - finished = child_finished + finished = max(finished, child_finished) return _data, finished engine = _get_engine() diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/tables.py b/openstack_dashboard/dashboards/admin/networks/subnets/tables.py index 2068d8ad17..fb56f022d7 100644 --- a/openstack_dashboard/dashboards/admin/networks/subnets/tables.py +++ b/openstack_dashboard/dashboards/admin/networks/subnets/tables.py @@ -137,9 +137,8 @@ class SubnetsTab(project_tabs_subnets_tab): subnet_id = subnet_usage.get("subnet_id") subnet_used_ips = subnet_usage.get("used_ips") subnet_total_ips = subnet_usage.get("total_ips") - subnet_free_ips = subnet_total_ips - subnet_used_ips - if subnet_free_ips < 0: - subnet_free_ips = 0 + subnet_free_ips = max(subnet_total_ips - subnet_used_ips, 0) + for item in subnets_dict: id = item.get("id") if id == subnet_id: diff --git a/openstack_dashboard/dashboards/admin/ngflavors/views.py b/openstack_dashboard/dashboards/admin/ngflavors/views.py index 3f7039a924..39ae88ce0b 100644 --- a/openstack_dashboard/dashboards/admin/ngflavors/views.py +++ b/openstack_dashboard/dashboards/admin/ngflavors/views.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import horizon.views as views +from horizon import views class IndexView(views.HorizonTemplateView): diff --git a/openstack_dashboard/dashboards/admin/volume_types/qos_specs/views.py b/openstack_dashboard/dashboards/admin/volume_types/qos_specs/views.py index c8fe8827dc..d0aed2bfbf 100644 --- a/openstack_dashboard/dashboards/admin/volume_types/qos_specs/views.py +++ b/openstack_dashboard/dashboards/admin/volume_types/qos_specs/views.py @@ -42,6 +42,7 @@ class QosSpecMixin(object): qos_spec_id = context['qos_spec_id'] try: + # pylint: disable-next=possibly-used-before-assignment qos_list = api.cinder.qos_spec_get(self.request, qos_spec_id) context['qos_spec_name'] = qos_list.name except Exception: diff --git a/openstack_dashboard/dashboards/project/api_access/views.py b/openstack_dashboard/dashboards/project/api_access/views.py index 4622fba16c..6c671280fd 100644 --- a/openstack_dashboard/dashboards/project/api_access/views.py +++ b/openstack_dashboard/dashboards/project/api_access/views.py @@ -100,6 +100,7 @@ def download_ec2_bundle(request): # Create our file bundle template = 'project/api_access/ec2rc.sh.template' try: + # pylint: disable-next=consider-using-with temp_zip = tempfile.NamedTemporaryFile(delete=True) with closing(zipfile.ZipFile(temp_zip.name, mode='w')) as archive: archive.writestr('ec2rc.sh', render_to_string(template, context)) diff --git a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tables.py b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tables.py index b2f5bed14f..8ae9b4f624 100644 --- a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tables.py +++ b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tables.py @@ -95,16 +95,16 @@ class DeleteRule(tables.DeleteAction): @staticmethod def action_present(count): return ngettext_lazy( - u"Delete Rule", - u"Delete Rules", + "Delete Rule", + "Delete Rules", count ) @staticmethod def action_past(count): return ngettext_lazy( - u"Deleted Rule", - u"Deleted Rules", + "Deleted Rule", + "Deleted Rules", count ) diff --git a/openstack_dashboard/dashboards/project/floating_ips/tables.py b/openstack_dashboard/dashboards/project/floating_ips/tables.py index 82a0ee8d9d..2fd7bfd0d9 100644 --- a/openstack_dashboard/dashboards/project/floating_ips/tables.py +++ b/openstack_dashboard/dashboards/project/floating_ips/tables.py @@ -121,8 +121,8 @@ class ReleaseIPsPortForwarding(ReleaseIPs): @staticmethod def action_past(count): return ngettext_lazy( - u"Successfully redirected", - u"Successfully redirected", + "Successfully redirected", + "Successfully redirected", count ) diff --git a/openstack_dashboard/hooks.py b/openstack_dashboard/hooks.py index 2754cc49e7..a4b39e40e5 100644 --- a/openstack_dashboard/hooks.py +++ b/openstack_dashboard/hooks.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -# pylint: disable=no-name-in-module,import-error +# pylint: disable=no-name-in-module,import-error,deprecated-module from distutils.command import install diff --git a/openstack_dashboard/management/commands/dump_default_policies.py b/openstack_dashboard/management/commands/dump_default_policies.py index d253536362..38273a05aa 100644 --- a/openstack_dashboard/management/commands/dump_default_policies.py +++ b/openstack_dashboard/management/commands/dump_default_policies.py @@ -52,7 +52,9 @@ def _format_default_policy(default): def _write_yaml_file(policies, output_file): - stream = open(output_file, 'w') if output_file else sys.stdout + # pylint: disable-next=consider-using-with + stream = open(output_file, 'w', + encoding="utf-8") if output_file else sys.stdout yaml.dump(policies, stream=stream) if output_file: stream.close() diff --git a/openstack_dashboard/management/commands/extract_messages.py b/openstack_dashboard/management/commands/extract_messages.py index 694698df59..13b7ad0e8c 100644 --- a/openstack_dashboard/management/commands/extract_messages.py +++ b/openstack_dashboard/management/commands/extract_messages.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=import-error +# pylint: disable=import-error,deprecated-module from distutils.dist import Distribution import os from subprocess import call diff --git a/openstack_dashboard/management/commands/make_web_conf.py b/openstack_dashboard/management/commands/make_web_conf.py index feed78fe05..e989ffc1fb 100644 --- a/openstack_dashboard/management/commands/make_web_conf.py +++ b/openstack_dashboard/management/commands/make_web_conf.py @@ -26,7 +26,7 @@ from django import template # rendering it unreadable. warnings.simplefilter('ignore') -cmd_name = __name__.split('.')[-1] +cmd_name = __name__.rsplit('.', maxsplit=1)[-1] CURDIR = os.path.realpath(os.path.dirname(__file__)) PROJECT_PATH = os.path.realpath(os.path.join(CURDIR, '../..')) @@ -301,11 +301,12 @@ location you desire, e.g.:: # Generate the WSGI. if options.get('wsgi'): with open( - os.path.join(CURDIR, 'horizon.wsgi.template'), 'r' + os.path.join(CURDIR, 'horizon.wsgi.template'), 'r', + encoding="utf-8" ) as fp: wsgi_template = template.Template(fp.read()) if not os.path.exists(context['WSGI_FILE']) or force: - with open(context['WSGI_FILE'], 'w') as fp: + with open(context['WSGI_FILE'], 'w', encoding="utf-8") as fp: fp.write(wsgi_template.render(context)) print('Generated "%s"' % context['WSGI_FILE']) else: @@ -319,7 +320,8 @@ location you desire, e.g.:: context['WSGI_FILE'] = context['DEFAULT_WSGI_FILE'] with open( - os.path.join(CURDIR, 'apache_vhost.conf.template'), 'r' + os.path.join(CURDIR, 'apache_vhost.conf.template'), 'r', + encoding="utf-8" ) as fp: wsgi_template = template.Template(fp.read()) sys.stdout.write(wsgi_template.render(context)) diff --git a/openstack_dashboard/management/commands/migrate_settings.py b/openstack_dashboard/management/commands/migrate_settings.py index bae6881546..dbd0049649 100644 --- a/openstack_dashboard/management/commands/migrate_settings.py +++ b/openstack_dashboard/management/commands/migrate_settings.py @@ -114,10 +114,13 @@ class Command(BaseCommand): """ with DirContext(self.local_settings_dir) as dircontext: - if not os.path.exists(self.local_settings_diff) or force: - with open(self.local_settings_example, 'r') as fp: + if not os.path.exists(self.local_settings_diff, + encoding="utf-8") or force: + with open(self.local_settings_example, 'r', + encoding="utf-8") as fp: example_lines = fp.readlines() - with open(self.local_settings_file, 'r') as fp: + with open(self.local_settings_file, 'r', + encoding="utf-8") as fp: local_settings_lines = fp.readlines() local_settings_example_mtime = time.strftime( self.time_fmt, @@ -134,7 +137,8 @@ class Command(BaseCommand): dircontext.curdir, self.local_settings_diff) ) - with open(self.local_settings_diff, 'w') as fp: + with open(self.local_settings_diff, 'w', + encoding="utf-8") as fp: for line in difflib.unified_diff( example_lines, local_settings_lines, fromfile=self.local_settings_example, diff --git a/openstack_dashboard/management/commands/update_catalog.py b/openstack_dashboard/management/commands/update_catalog.py index 48f57bfd82..337aba9531 100644 --- a/openstack_dashboard/management/commands/update_catalog.py +++ b/openstack_dashboard/management/commands/update_catalog.py @@ -18,8 +18,8 @@ import os from subprocess import call -import babel.messages.catalog as catalog -import babel.messages.pofile as babel_pofile +from babel.messages import catalog +from babel.messages import pofile as babel_pofile from django.conf import settings from django.core.management.base import BaseCommand from django.utils import translation @@ -110,7 +110,7 @@ class Command(BaseCommand): continue # Pseudo translation logic - with open(potfile, 'r') as f: + with open(potfile, 'r', encoding="utf-8") as f: pot_cat = babel_pofile.read_po(f, ignore_obsolete=True) new_cat = catalog.Catalog(locale=locale, diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index ed0c83419b..ca5bf5b097 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -272,7 +272,9 @@ if os.path.exists(LOCAL_SETTINGS_DIR_PATH): for filename in sorted(filenames): if filename.endswith(".py"): try: - with open(os.path.join(dirpath, filename)) as f: + with open( + os.path.join(dirpath, filename), encoding="utf-8" + ) as f: # pylint: disable=exec-used exec(f.read()) except Exception: diff --git a/openstack_dashboard/test/selenium/conftest.py b/openstack_dashboard/test/selenium/conftest.py index acd8385972..ddfee4ca51 100644 --- a/openstack_dashboard/test/selenium/conftest.py +++ b/openstack_dashboard/test/selenium/conftest.py @@ -127,7 +127,7 @@ def record_video(request, report_dir, xdisplay): '-video_size', f'{width}x{height}', '-framerate', str(frame_rate), '-f', 'x11grab', - '-i', f':{display}', + '-i', f':{display}', # noqa: E231 filepath, ] fnull = open(os.devnull, 'w') diff --git a/openstack_dashboard/test/selenium/integration/conftest.py b/openstack_dashboard/test/selenium/integration/conftest.py index a61a0b0927..4171d6babe 100644 --- a/openstack_dashboard/test/selenium/integration/conftest.py +++ b/openstack_dashboard/test/selenium/integration/conftest.py @@ -224,7 +224,7 @@ def new_instance_demo(complete_default_test_network, request, instance_name, yield instance if count > 1: for instance in range(0, count): - openstack_demo.delete_server(f"{instance_name}-{instance+1}") + openstack_demo.delete_server(f"{instance_name}-{instance + 1}") else: openstack_demo.delete_server(instance_name) diff --git a/openstack_dashboard/test/selenium/integration/test_images.py b/openstack_dashboard/test/selenium/integration/test_images.py index 3c14a36267..d24c44647d 100644 --- a/openstack_dashboard/test/selenium/integration/test_images.py +++ b/openstack_dashboard/test/selenium/integration/test_images.py @@ -438,7 +438,7 @@ def test_edit_image_description_admin(login, driver, image_names, ".//button[@class='btn btn-primary finish']").click() messages = widgets.get_and_dismiss_messages(driver) assert f"Success: Image {image_name} " \ - f"was successfully updated." in messages + f"was successfully updated." in messages image_id = new_image_admin.id assert (openstack_admin.compute.get(f"/images/{image_id}").json( )['image']['metadata']['description'] == new_description) @@ -471,7 +471,7 @@ def test_update_image_metadata_admin(login, driver, image_form.find_element_by_css_selector( "button.btn span[class='fa fa-plus']").click() image_form.find_element_by_xpath( - f"//span[@title='{name}']/parent::div/input").send_keys(value) + f"//span[@title='{name}']/parent::div/input").send_keys(value) # noqa: E231,E501 image_form.find_element_by_xpath( "//button[@ng-click='modal.save()']").click() messages = widgets.get_and_dismiss_messages(driver) @@ -548,7 +548,7 @@ def test_create_volume_from_image_admin(login, driver, volume_name, name_field.send_keys(volume_name) create_vol_btn = WebDriverWait(driver, config.selenium.page_timeout).until( EC.element_to_be_clickable((By.XPATH, f"//button[@class='btn " - f"btn-primary finish']"))) + f"btn-primary finish']"))) create_vol_btn.click() messages = widgets.get_and_dismiss_messages(driver) assert f"Info: Creating volume {volume_name}" in messages diff --git a/openstack_dashboard/test/selenium/integration/test_instances.py b/openstack_dashboard/test/selenium/integration/test_instances.py index 8b2d6a568b..4dfd07d9d0 100644 --- a/openstack_dashboard/test/selenium/integration/test_instances.py +++ b/openstack_dashboard/test/selenium/integration/test_instances.py @@ -42,7 +42,7 @@ def new_instance_admin(complete_default_test_network, request, instance_name, yield instance if count > 1: for instance in range(0, count): - openstack_admin.delete_server(f"{instance_name}-{instance+1}") + openstack_admin.delete_server(f"{instance_name}-{instance + 1}") else: openstack_admin.delete_server(instance_name) diff --git a/openstack_dashboard/test/selenium/integration/test_networks.py b/openstack_dashboard/test/selenium/integration/test_networks.py index d07ddbcd2b..f4984f8b23 100644 --- a/openstack_dashboard/test/selenium/integration/test_networks.py +++ b/openstack_dashboard/test/selenium/integration/test_networks.py @@ -77,7 +77,7 @@ def ensure_checkbox(required_state, element): current_state = element.is_selected() if required_state != current_state: element.find_element_by_xpath( - f".//following-sibling::label").click() + f".//following-sibling::label").click() # noqa: E231 def test_create_network_without_subnet_demo(login, openstack_demo, driver, diff --git a/openstack_dashboard/test/selenium/integration/test_projects.py b/openstack_dashboard/test/selenium/integration/test_projects.py index e5b2cb42f8..815695915f 100644 --- a/openstack_dashboard/test/selenium/integration/test_projects.py +++ b/openstack_dashboard/test/selenium/integration/test_projects.py @@ -120,8 +120,8 @@ def test_add_member_to_project(login, driver, project_name, openstack_admin, rows[0].find_element_by_css_selector(".data-table-action").click() project_form = driver.find_element_by_css_selector("form .modal-content") project_form.find_element_by_xpath( - f".//*[text()='{admin_name}']//ancestor::li" - f"/following-sibling::li/a[@href='#add_remove']").click() + f".//*[text()='{admin_name}']//ancestor::li" # noqa: E231 + f"/following-sibling::li/a[@href='#add_remove']").click() # noqa: E231 project_form.find_element_by_css_selector( ".btn-primary[value='Save']").click() messages = widgets.get_and_dismiss_messages(driver) @@ -151,8 +151,8 @@ def test_add_role_to_project_member(login, driver, openstack_admin, config, rows[0].find_element_by_css_selector(".data-table-action").click() project_form = driver.find_element_by_css_selector("form .modal-content") select_roles_dropdown = project_form.find_element_by_xpath( - f".//*[text()='{admin_name}']//ancestor::li" - f"/following-sibling::li[@class='dropdown role_options']") + f".//*[text()='{admin_name}']//ancestor::li" # noqa: E231 + f"/following-sibling::li[@class='dropdown role_options']") # noqa: E231 widgets.select_from_dropdown(select_roles_dropdown, admin_role_name) project_form.find_element_by_css_selector( ".btn-primary[value='Save']").click() @@ -188,8 +188,8 @@ def test_add_group_to_project(login, driver, openstack_admin, widgets.select_from_dropdown(actions_column, "Modify Groups") project_form = driver.find_element_by_css_selector("form .modal-content") project_form.find_element_by_xpath( - f".//*[text()='{group_name}']//ancestor::li" - f"/following-sibling::li/a[@href='#add_remove']").click() + f".//*[text()='{group_name}']//ancestor::li" # noqa: E231 + f"/following-sibling::li/a[@href='#add_remove']").click() # noqa: E231 project_form.find_element_by_css_selector( ".btn-primary[value='Save']").click() messages = widgets.get_and_dismiss_messages(driver) @@ -220,8 +220,8 @@ def test_add_role_to_project_group(login, driver, openstack_admin, config, widgets.select_from_dropdown(actions_column, "Modify Groups") project_form = driver.find_element_by_css_selector("form .modal-content") select_roles_dropdown = project_form.find_element_by_xpath( - f".//*[text()='{group_name}']//ancestor::li" - f"/following-sibling::li[@class='dropdown role_options']") + f".//*[text()='{group_name}']//ancestor::li" # noqa: E231 + f"/following-sibling::li[@class='dropdown role_options']") # noqa: E231 widgets.select_from_dropdown(select_roles_dropdown, admin_role_name) project_form.find_element_by_css_selector( ".btn-primary[value='Save']").click() diff --git a/openstack_dashboard/test/selenium/integration/test_routers.py b/openstack_dashboard/test/selenium/integration/test_routers.py index 40272efa1c..402e31f36d 100644 --- a/openstack_dashboard/test/selenium/integration/test_routers.py +++ b/openstack_dashboard/test/selenium/integration/test_routers.py @@ -157,7 +157,7 @@ def test_router_add_interface_demo(login, driver, router_name, openstack_demo, ".modal-dialog form") widgets.select_from_dropdown( add_interface_form, f"{new_network_demo.name}: {new_subnet_demo.cidr} " - f"({new_subnet_demo.name})") + f"({new_subnet_demo.name})") add_interface_form.find_element_by_id( "id_ip_address").send_keys(fixed_ip_test) add_interface_form.find_element_by_css_selector(".btn-primary").click() diff --git a/openstack_dashboard/test/selenium/ui/test_browse.py b/openstack_dashboard/test/selenium/ui/test_browse.py index 89867964af..ce5275b0c9 100644 --- a/openstack_dashboard/test/selenium/ui/test_browse.py +++ b/openstack_dashboard/test/selenium/ui/test_browse.py @@ -145,7 +145,7 @@ def test_browse_left_panel(live_server, driver, user, dashboard_data, WebDriverWait(driver, config.selenium.implicit_wait).until( EC.element_to_be_clickable( (By.CSS_SELECTOR, f"a[data-target='#sidebar-accordion" - f"-{main_panel}-{sec_panel}']"))).click() + f"-{main_panel}-{sec_panel}']"))).click() sidebar = driver.find_element_by_id( f"sidebar-accordion-{main_panel}-{sec_panel}") else: diff --git a/openstack_dashboard/test/selenium/ui/test_hypervisors.py b/openstack_dashboard/test/selenium/ui/test_hypervisors.py index db2e9758fb..f4f0f93eb6 100644 --- a/openstack_dashboard/test/selenium/ui/test_hypervisors.py +++ b/openstack_dashboard/test/selenium/ui/test_hypervisors.py @@ -53,24 +53,24 @@ def test_vcpu_pcpu_data_display(live_server, driver, user, dashboard_data): driver.get(live_server.url + '/admin/hypervisors') assert (driver.find_element_by_xpath( f"//*[normalize-space()='VCPU Usage']/" - f"ancestor::div[contains(@class,'d3_quota_bar')]" - f"/div[contains(@class,'h6')]/" + f"ancestor::div[contains(@class,'d3_quota_bar')]" # noqa: E231 + f"/div[contains(@class,'h6')]/" # noqa: E231 f"span[1]").text == str(p['vcpus_used'])) assert (driver.find_element_by_xpath( f"//*[normalize-space()='VCPU Usage']/" - f"ancestor::div[contains(@class,'d3_quota_bar')]" - f"/div[contains(@class,'h6')]/" + f"ancestor::div[contains(@class,'d3_quota_bar')]" # noqa: E231 + f"/div[contains(@class,'h6')]/" # noqa: E231 f"span[2]").text == str(p['vcpus_capacity'])) assert (driver.find_element_by_xpath( f"//*[normalize-space()='PCPU Usage']/" - f"ancestor::div[contains(@class,'d3_quota_bar')]" - f"/div[contains(@class,'h6')]/" + f"ancestor::div[contains(@class,'d3_quota_bar')]" # noqa: E231 + f"/div[contains(@class,'h6')]/" # noqa: E231 f"span[1]").text == str(p['pcpus_used'])) assert (driver.find_element_by_xpath( f"//*[normalize-space()='PCPU Usage']/" - f"ancestor::div[contains(@class,'d3_quota_bar')]" - f"/div[contains(@class,'h6')]/" + f"ancestor::div[contains(@class,'d3_quota_bar')]" # noqa: E231 + f"/div[contains(@class,'h6')]/" # noqa: E231 f"span[2]").text == str(p['pcpus_capacity'])) driver.find_element_by_link_text("Resource Provider").click() diff --git a/openstack_dashboard/test/selenium/widgets.py b/openstack_dashboard/test/selenium/widgets.py index e2a060033d..c517dd2a55 100644 --- a/openstack_dashboard/test/selenium/widgets.py +++ b/openstack_dashboard/test/selenium/widgets.py @@ -55,7 +55,7 @@ def select_from_dropdown(element, label): def select_from_specific_dropdown_in_form(driver, dropdown_id, label): dropdown = driver.find_element_by_xpath( - f".//*[@for='{dropdown_id}']/following-sibling::div") + f".//*[@for='{dropdown_id}']/following-sibling::div") # noqa: E231 dropdown.click() dropdown_options = dropdown.find_element_by_css_selector( "ul.dropdown-menu") @@ -128,12 +128,12 @@ def select_from_transfer_table(element, label): try: element.find_element_by_xpath( - f".//*[text()='{label}']//ancestor::tr/td//*" + f".//*[text()='{label}']//ancestor::tr/td//*" # noqa: E231 f"[@class='btn btn-default fa fa-arrow-up']").click() except exceptions.NoSuchElementException: try: element.find_element_by_xpath( - f".//*[text()='{label}']//ancestor::tr/td//*" + f".//*[text()='{label}']//ancestor::tr/td//*" # noqa: E231 f"[@class='btn btn-default fa fa-arrow-down']") except exceptions.NoSuchElementException: raise diff --git a/openstack_dashboard/usage/quotas.py b/openstack_dashboard/usage/quotas.py index ce04f66bac..d101f0337c 100644 --- a/openstack_dashboard/usage/quotas.py +++ b/openstack_dashboard/usage/quotas.py @@ -163,9 +163,7 @@ class QuotaUsage(dict): def update_available(self, name): """Updates the "available" metric for the given quota.""" quota = self.usages.get(name, {}).get('quota', float('inf')) - available = quota - self.usages[name]['used'] - if available < 0: - available = 0 + available = max(quota - self.usages[name]['used'], 0) self.usages[name]['available'] = available diff --git a/openstack_dashboard/utils/settings.py b/openstack_dashboard/utils/settings.py index afe771a125..190cc6eb90 100644 --- a/openstack_dashboard/utils/settings.py +++ b/openstack_dashboard/utils/settings.py @@ -312,6 +312,7 @@ def get_xstatic_dirs(XSTATIC_MODULES, HORIZON_CONFIG): file = 'horizon/lib/' + module.NAME + '/' + file HORIZON_CONFIG['xstatic_lib_files'].append(file) except TypeError: + # pylint: disable-next=broad-exception-raised raise Exception( '%s: Nothing to include because files to include are not ' 'defined (i.e., None) in BASE_XSTATIC_MODULES list and ' diff --git a/tox.ini b/tox.ini index 0f8028204e..544532c1ac 100644 --- a/tox.ini +++ b/tox.ini @@ -37,7 +37,7 @@ commands = {posargs} deps = {[testenv]deps} flake8-import-order==0.12 # LGPLv3 - pylint==2.6.0 # GPLv2 + pylint==3.3.1 # GPLv2 bandit[baseline]>=1.7.7 # Apache-2.0 setenv = DJANGO_SETTINGS_MODULE=openstack_dashboard.test.settings