From 8dae11acdfe5e60b90ab256a5f193d6d7afd29a4 Mon Sep 17 00:00:00 2001 From: David Salvisberg Date: Tue, 10 Oct 2023 14:01:13 +0200 Subject: [PATCH] Adds some additional flake8-plugins --- .pre-commit-config.yaml | 3 ++ setup.cfg | 6 ++- src/riskmatrix/controls.py | 49 ++++++++++++++++--- src/riskmatrix/i18n/translation_string.py | 18 ++++--- src/riskmatrix/views/risk.py | 2 +- .../wtform/widgets/checkbox_list_widget.py | 6 ++- test_requirements.txt | 3 ++ 7 files changed, 68 insertions(+), 19 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e587b15..edac196 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,6 +20,9 @@ repos: files: '^(src|stubs|tests)/.*\.(py|pyi)$' additional_dependencies: - flake8-bugbear==23.7.10 + - flake8-comprehensions==3.14.0 + - 'flake8-markupsafe@git+https://github.com/vmagamedov/flake8-markupsafe@b391bd13df9330e01666d304b7f4403d67e5ceba' + - flake8-noqa==1.3.2 - flake8-pyi==23.6.0 - repo: local hooks: diff --git a/setup.cfg b/setup.cfg index 3844590..cc5d3c5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -70,6 +70,8 @@ console_scripts = upgrade = riskmatrix.scripts.upgrade:main [flake8] -extend-select = B901, B903, B904, B908 +extend-select = B901,B903,B904,B908 per_file_ignores = - *.pyi: E301, E302, E305, E501, E701, F401, F403, F405, F822 + *.pyi: B,C,MS,E301,E302,E305,E501,E701,F401,F403,F405,F822 + tests/**.py: C,MS,NQA104 +noqa-require-code = true diff --git a/src/riskmatrix/controls.py b/src/riskmatrix/controls.py index a99dce6..bc58dea 100644 --- a/src/riskmatrix/controls.py +++ b/src/riskmatrix/controls.py @@ -1,12 +1,45 @@ from enum import Enum +from markupsafe import escape from markupsafe import Markup -from wtforms.widgets import html_params from riskmatrix.i18n import translate from typing import ClassVar from typing import Literal +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from collections.abc import Iterator + + +def html_params(**kwargs: object) -> Markup: + """ + Based on wtforms.widgets.html_params. The difference being + that it will properly escape " even if a value is Markup + and thus will not be escaped by "escape". + + Also it returns Markup so it can be included in Markup. + It also treats None the same as False + """ + def params_iter() -> 'Iterator[str]': + for key, value in sorted(kwargs.items()): + key = str(key) + key = key.rstrip('_') + if key.startswith('data_') or key.startswith('aria_'): + key = key.replace('_', '-') + + if value is True: + yield key + + if value is False or value is None: + continue + + yield Markup('{}="{}"').format( + key, + # After escaping we need to still replace quotes + escape(value).replace(Markup('"'), Markup('"')) + ) + return Markup(' ').join(params_iter()) class IconStyle(Enum): @@ -138,15 +171,19 @@ def __call__(self) -> Markup: 'tabindex': 0, 'data_bs_toggle': 'tooltip' } - html = Markup(f'') + html = Markup('').format(html_params(**desc_params)) else: html = Markup('') - html += Markup(f'<{self.element} {html_params(**self.html_params)}>') + html += Markup('<{} {}>').format( + self.element, + html_params(**self.html_params) + ) if not self.disabled and self.description: - description = html_params(title=translate(self.description)) - html += Markup(f'') + html += Markup( + '' + ).format(html_params(title=translate(self.description))) if self.icon: html += self.icon() @@ -158,7 +195,7 @@ def __call__(self) -> Markup: if not self.disabled and self.description: html += Markup('') - html += Markup(f'') + html += Markup('').format(self.element) if self.disabled and self.description: html += Markup('') diff --git a/src/riskmatrix/i18n/translation_string.py b/src/riskmatrix/i18n/translation_string.py index 0f4fcb6..32c5e3e 100644 --- a/src/riskmatrix/i18n/translation_string.py +++ b/src/riskmatrix/i18n/translation_string.py @@ -97,7 +97,7 @@ def __new__( if default is None: _default = None else: - _default = Markup(default) + _default = Markup(default) # noqa: MS001 # NOTE: We prepare everything in the mapping with escape # because interpolate uses re.sub, which is not @@ -109,25 +109,25 @@ def __new__( _mapping = {k: escape(v) for k, v in mapping.items()} if not isinstance(msgid, str) and hasattr(msgid, '__html__'): - msgid = Markup(msgid) + msgid = Markup(msgid) # noqa: MS001 elif isinstance(msgid, translationstring.TranslationString): domain = domain or msgid.domain and msgid.domain[:] context = context or msgid.context and msgid.context[:] - _default = _default or Markup(msgid.default) + _default = _default or Markup(msgid.default) # noqa: MS001 if msgid.mapping: if _mapping: for k, v in msgid.mapping.items(): _mapping.setdefault(k, escape(v)) else: _mapping = {k: escape(v) for k, v in msgid.mapping.items()} - msgid = Markup(str(msgid)) + msgid = Markup(str(msgid)) # noqa: MS001 instance: 'Self' = str.__new__(cls, msgid) instance.domain = domain instance.context = context if _default is None: - _default = Markup(msgid) + _default = Markup(msgid) # noqa: MS001 instance.default = _default instance.mapping = _mapping @@ -140,7 +140,9 @@ def __mod__(self, options: Any) -> 'Self': return type(self)(super().__mod__(options)) def interpolate(self, translated: str | None = None) -> Markup: - return Markup(super().interpolate(translated and Markup(translated))) + if translated is not None: + translated = Markup(translated) # noqa: MS001 + return Markup(super().interpolate(translated)) # noqa: MS001 @classmethod def escape(cls, s: Any) -> 'Self': @@ -157,7 +159,7 @@ def escape(cls, s: Any) -> 'Self': return cls(escape(s)) def translated(self, language: str | None = None) -> Markup: - return Markup(translate(self, language)) + return Markup(translate(self, language)) # noqa: MS001 def TranslationStringFactory(factory_domain: str) -> 'TStrCallable': @@ -248,7 +250,7 @@ def _dugettext_policy( # we don't want it to get interpolated into Markup and not type(tstring) is TranslationString ): - return Markup(translated) + return Markup(translated) # noqa: MS001 return translated diff --git a/src/riskmatrix/views/risk.py b/src/riskmatrix/views/risk.py index d67c40f..cb41f67 100644 --- a/src/riskmatrix/views/risk.py +++ b/src/riskmatrix/views/risk.py @@ -71,7 +71,7 @@ def walk_tree(nodes: list[tuple[str, str]]) -> 'Iterator[tuple[str, str]]': yield from walk_tree(children) return { - group_name: [node for node in walk_tree(children)] + group_name: list(walk_tree(children)) if (children := children_map.get(group_id)) else [(group_name, group_name)] for group_id, group_name in children_map.get(None, []) diff --git a/src/riskmatrix/wtform/widgets/checkbox_list_widget.py b/src/riskmatrix/wtform/widgets/checkbox_list_widget.py index ae183f7..026a6ac 100644 --- a/src/riskmatrix/wtform/widgets/checkbox_list_widget.py +++ b/src/riskmatrix/wtform/widgets/checkbox_list_widget.py @@ -1,5 +1,6 @@ from markupsafe import Markup -from wtforms.widgets import html_params + +from riskmatrix.controls import html_params from typing import Any @@ -25,11 +26,12 @@ def __call__(self, field: 'Field', **kwargs: Any) -> Markup: assert hasattr(field, '__iter__') return Markup('').join( Markup( - f'
' + '
' '{input}' '{label}' '
' ).format( + params=html_params(**kwargs), input=subfield(class_='form-check-input'), label=subfield.label(class_='form-check-label') ) diff --git a/test_requirements.txt b/test_requirements.txt index f2e609f..da2a8d1 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -6,6 +6,9 @@ distlib==0.3.7 filelock==3.12.3 flake8==6.1.0 flake8-bugbear==23.7.10 +flake8-comprehensions==3.14.0 +flake8-markupsafe @ git+https://github.com/vmagamedov/flake8-markupsafe@b391bd13df9330e01666d304b7f4403d67e5ceba +flake8-noqa==1.3.2 flake8-pyi==23.6.0 GitPython==3.1.32 identify==2.5.27