From 1f05c31ee04f2f3c72439320dfcae24d43ef7c09 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 31 Aug 2023 08:36:15 +0300 Subject: [PATCH] Upgrade Python syntax with pyupgrade --py38-plus --- soupsieve/__init__.py | 25 +++++----- soupsieve/__meta__.py | 16 +++---- soupsieve/css_match.py | 10 ++-- soupsieve/css_parser.py | 86 +++++++++++++++++------------------ soupsieve/css_types.py | 24 +++++----- soupsieve/pretty.py | 2 +- soupsieve/util.py | 4 +- tests/test_api.py | 2 +- tests/test_level4/test_dir.py | 1 - tests/util.py | 2 +- 10 files changed, 84 insertions(+), 88 deletions(-) diff --git a/soupsieve/__init__.py b/soupsieve/__init__.py index 06403633..8173fd40 100644 --- a/soupsieve/__init__.py +++ b/soupsieve/__init__.py @@ -78,13 +78,13 @@ def purge() -> None: def closest( select: str, - tag: 'bs4.Tag', + tag: bs4.Tag, namespaces: dict[str, str] | None = None, flags: int = 0, *, custom: dict[str, str] | None = None, **kwargs: Any -) -> 'bs4.Tag': +) -> bs4.Tag: """Match closest ancestor.""" return compile(select, namespaces, flags, **kwargs).closest(tag) @@ -92,7 +92,7 @@ def closest( def match( select: str, - tag: 'bs4.Tag', + tag: bs4.Tag, namespaces: dict[str, str] | None = None, flags: int = 0, *, @@ -106,13 +106,13 @@ def match( def filter( # noqa: A001 select: str, - iterable: Iterable['bs4.Tag'], + iterable: Iterable[bs4.Tag], namespaces: dict[str, str] | None = None, flags: int = 0, *, custom: dict[str, str] | None = None, **kwargs: Any -) -> list['bs4.Tag']: +) -> list[bs4.Tag]: """Filter list of nodes.""" return compile(select, namespaces, flags, **kwargs).filter(iterable) @@ -120,13 +120,13 @@ def filter( # noqa: A001 def select_one( select: str, - tag: 'bs4.Tag', + tag: bs4.Tag, namespaces: dict[str, str] | None = None, flags: int = 0, *, custom: dict[str, str] | None = None, **kwargs: Any -) -> 'bs4.Tag': +) -> bs4.Tag: """Select a single tag.""" return compile(select, namespaces, flags, **kwargs).select_one(tag) @@ -134,14 +134,14 @@ def select_one( def select( select: str, - tag: 'bs4.Tag', + tag: bs4.Tag, namespaces: dict[str, str] | None = None, limit: int = 0, flags: int = 0, *, custom: dict[str, str] | None = None, **kwargs: Any -) -> list['bs4.Tag']: +) -> list[bs4.Tag]: """Select the specified tags.""" return compile(select, namespaces, flags, **kwargs).select(tag, limit) @@ -149,18 +149,17 @@ def select( def iselect( select: str, - tag: 'bs4.Tag', + tag: bs4.Tag, namespaces: dict[str, str] | None = None, limit: int = 0, flags: int = 0, *, custom: dict[str, str] | None = None, **kwargs: Any -) -> Iterator['bs4.Tag']: +) -> Iterator[bs4.Tag]: """Iterate the specified tags.""" - for el in compile(select, namespaces, flags, **kwargs).iselect(tag, limit): - yield el + yield from compile(select, namespaces, flags, **kwargs).iselect(tag, limit) def escape(ident: str) -> str: diff --git a/soupsieve/__meta__.py b/soupsieve/__meta__.py index 6f35e152..f872a1a9 100644 --- a/soupsieve/__meta__.py +++ b/soupsieve/__meta__.py @@ -93,7 +93,7 @@ def __new__( raise ValueError("All version parts except 'release' should be integers.") if release not in REL_MAP: - raise ValueError("'{}' is not a valid release type.".format(release)) + raise ValueError(f"'{release}' is not a valid release type.") # Ensure valid pre-release (we do not allow implicit pre-releases). if ".dev-candidate" < release < "final": @@ -118,7 +118,7 @@ def __new__( elif dev: raise ValueError("Version is not a development release.") - return super(Version, cls).__new__(cls, major, minor, micro, release, pre, post, dev) + return super().__new__(cls, major, minor, micro, release, pre, post, dev) def _is_pre(self) -> bool: """Is prerelease.""" @@ -145,15 +145,15 @@ def _get_canonical(self) -> str: # Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed.. if self.micro == 0: - ver = "{}.{}".format(self.major, self.minor) + ver = f"{self.major}.{self.minor}" else: - ver = "{}.{}.{}".format(self.major, self.minor, self.micro) + ver = f"{self.major}.{self.minor}.{self.micro}" if self._is_pre(): - ver += '{}{}'.format(REL_MAP[self.release], self.pre) + ver += f'{REL_MAP[self.release]}{self.pre}' if self._is_post(): - ver += ".post{}".format(self.post) + ver += f".post{self.post}" if self._is_dev(): - ver += ".dev{}".format(self.dev) + ver += f".dev{self.dev}" return ver @@ -164,7 +164,7 @@ def parse_version(ver: str) -> Version: m = RE_VER.match(ver) if m is None: - raise ValueError("'{}' is not a valid version".format(ver)) + raise ValueError(f"'{ver}' is not a valid version") # Handle major, minor, micro major = int(m.group('major')) diff --git a/soupsieve/css_match.py b/soupsieve/css_match.py index 168e4be6..e483de96 100644 --- a/soupsieve/css_match.py +++ b/soupsieve/css_match.py @@ -85,7 +85,7 @@ def assert_valid_input(cls, tag: Any) -> None: # Fail on unexpected types. if not cls.is_tag(tag): - raise TypeError("Expected a BeautifulSoup 'Tag', but instead received type {}".format(type(tag))) + raise TypeError(f"Expected a BeautifulSoup 'Tag', but instead received type {type(tag)}") @staticmethod def is_doc(obj: bs4.Tag) -> bool: @@ -165,8 +165,7 @@ def is_root(self, el: bs4.Tag) -> bool: def get_contents(self, el: bs4.Tag, no_iframe: bool = False) -> Iterator[bs4.PageElement]: """Get contents or contents in reverse.""" if not no_iframe or not self.is_iframe(el): - for content in el.contents: - yield content + yield from el.contents def get_children( self, @@ -394,7 +393,7 @@ def validate_day(year: int, month: int, day: int) -> bool: def validate_week(year: int, week: int) -> bool: """Validate week.""" - max_week = datetime.strptime("{}-{}-{}".format(12, 31, year), "%m-%d-%Y").isocalendar()[1] + max_week = datetime.strptime(f"{12}-{31}-{year}", "%m-%d-%Y").isocalendar()[1] if max_week == 1: max_week = 53 return 1 <= week <= max_week @@ -1571,8 +1570,7 @@ def select(self, tag: bs4.Tag, limit: int = 0) -> list[bs4.Tag]: def iselect(self, tag: bs4.Tag, limit: int = 0) -> Iterator[bs4.Tag]: """Iterate the specified tags.""" - for el in CSSMatch(self.selectors, tag, self.namespaces, self.flags).select(limit): - yield el + yield from CSSMatch(self.selectors, tag, self.namespaces, self.flags).select(limit) def __repr__(self) -> str: # pragma: no cover """Representation.""" diff --git a/soupsieve/css_parser.py b/soupsieve/css_parser.py index 10ac6f21..a4b40580 100644 --- a/soupsieve/css_parser.py +++ b/soupsieve/css_parser.py @@ -92,14 +92,14 @@ # Sub-patterns parts # Whitespace NEWLINE = r'(?:\r\n|(?!\r\n)[\n\f\r])' -WS = r'(?:[ \t]|{})'.format(NEWLINE) +WS = fr'(?:[ \t]|{NEWLINE})' # Comments COMMENTS = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' # Whitespace with comments included -WSC = r'(?:{ws}|{comments})'.format(ws=WS, comments=COMMENTS) +WSC = fr'(?:{WS}|{COMMENTS})' # CSS escapes -CSS_ESCAPES = r'(?:\\(?:[a-f0-9]{{1,6}}{ws}?|[^\r\n\f]|$))'.format(ws=WS) -CSS_STRING_ESCAPES = r'(?:\\(?:[a-f0-9]{{1,6}}{ws}?|[^\r\n\f]|$|{nl}))'.format(ws=WS, nl=NEWLINE) +CSS_ESCAPES = fr'(?:\\(?:[a-f0-9]{{1,6}}{WS}?|[^\r\n\f]|$))' +CSS_STRING_ESCAPES = fr'(?:\\(?:[a-f0-9]{{1,6}}{WS}?|[^\r\n\f]|$|{NEWLINE}))' # CSS Identifier IDENTIFIER = r''' (?:(?:-?(?:[^\x00-\x2f\x30-\x40\x5B-\x5E\x60\x7B-\x9f]|{esc})+|--) @@ -118,9 +118,9 @@ # Selector patterns # IDs (`#id`) -PAT_ID = r'\#{ident}'.format(ident=IDENTIFIER) +PAT_ID = fr'\#{IDENTIFIER}' # Classes (`.class`) -PAT_CLASS = r'\.{ident}'.format(ident=IDENTIFIER) +PAT_CLASS = fr'\.{IDENTIFIER}' # Prefix:Tag (`prefix|tag`) PAT_TAG = r'(?P(?:{ident}|\*)?\|)?(?P{ident}|\*)'.format(ident=IDENTIFIER) # Attributes (`[attr]`, `[attr=value]`, etc.) @@ -128,17 +128,17 @@ \[{ws}*(?P(?:{ident}|\*)?\|)?(?P{ident}){attr} '''.format(ws=WSC, ident=IDENTIFIER, attr=ATTR) # Pseudo class (`:pseudo-class`, `:pseudo-class(`) -PAT_PSEUDO_CLASS = r'(?P:{ident})(?P\({ws}*)?'.format(ws=WSC, ident=IDENTIFIER) +PAT_PSEUDO_CLASS = fr'(?P:{IDENTIFIER})(?P\({WSC}*)?' # Pseudo class special patterns. Matches `:pseudo-class(` for special case pseudo classes. -PAT_PSEUDO_CLASS_SPECIAL = r'(?P:{ident})(?P\({ws}*)'.format(ws=WSC, ident=IDENTIFIER) +PAT_PSEUDO_CLASS_SPECIAL = fr'(?P:{IDENTIFIER})(?P\({WSC}*)' # Custom pseudo class (`:--custom-pseudo`) -PAT_PSEUDO_CLASS_CUSTOM = r'(?P:(?=--){ident})'.format(ident=IDENTIFIER) +PAT_PSEUDO_CLASS_CUSTOM = fr'(?P:(?=--){IDENTIFIER})' # Closing pseudo group (`)`) -PAT_PSEUDO_CLOSE = r'{ws}*\)'.format(ws=WSC) +PAT_PSEUDO_CLOSE = fr'{WSC}*\)' # Pseudo element (`::pseudo-element`) -PAT_PSEUDO_ELEMENT = r':{}'.format(PAT_PSEUDO_CLASS) +PAT_PSEUDO_ELEMENT = fr':{PAT_PSEUDO_CLASS}' # At rule (`@page`, etc.) (not supported) -PAT_AT_RULE = r'@P{ident}'.format(ident=IDENTIFIER) +PAT_AT_RULE = fr'@P{IDENTIFIER}' # Pseudo class `nth-child` (`:nth-child(an+b [of S]?)`, `:first-child`, etc.) PAT_PSEUDO_NTH_CHILD = r''' (?P{name} @@ -154,7 +154,7 @@ name=PAT_PSEUDO_CLASS_SPECIAL, ws=WSC, value=VALUE ) # Pseudo class direction (`:dir(ltr)`) -PAT_PSEUDO_DIR = r'{name}(?Pltr|rtl){ws}*\)'.format(name=PAT_PSEUDO_CLASS_SPECIAL, ws=WSC) +PAT_PSEUDO_DIR = fr'{PAT_PSEUDO_CLASS_SPECIAL}(?Pltr|rtl){WSC}*\)' # Combining characters (`>`, `~`, ` `, `+`, `,`) PAT_COMBINE = r'{wsc}*?(?P[,+>~]|{ws}(?![,+>~])){wsc}*'.format(ws=WS, wsc=WSC) # Extra: Contains (`:contains(text)`) @@ -164,9 +164,9 @@ # Regular expressions # CSS escape pattern -RE_CSS_ESC = re.compile(r'(?:(\\[a-f0-9]{{1,6}}{ws}?)|(\\[^\r\n\f])|(\\$))'.format(ws=WSC), re.I) +RE_CSS_ESC = re.compile(fr'(?:(\\[a-f0-9]{{1,6}}{WSC}?)|(\\[^\r\n\f])|(\\$))', re.I) RE_CSS_STR_ESC = re.compile( - r'(?:(\\[a-f0-9]{{1,6}}{ws}?)|(\\[^\r\n\f])|(\\$)|(\\{nl}))'.format(ws=WS, nl=NEWLINE), re.I + fr'(?:(\\[a-f0-9]{{1,6}}{WS}?)|(\\[^\r\n\f])|(\\$)|(\\{NEWLINE}))', re.I ) # Pattern to break up `nth` specifiers RE_NTH = re.compile( @@ -177,9 +177,9 @@ RE_VALUES = re.compile(r'(?:(?P{value})|(?P{ws}*,{ws}*))'.format(ws=WSC, value=VALUE), re.X) # Whitespace checks RE_WS = re.compile(WS) -RE_WS_BEGIN = re.compile('^{}*'.format(WSC)) -RE_WS_END = re.compile('{}*$'.format(WSC)) -RE_CUSTOM = re.compile(r'^{}$'.format(PAT_PSEUDO_CLASS_CUSTOM), re.X) +RE_WS_BEGIN = re.compile(f'^{WSC}*') +RE_WS_END = re.compile(f'{WSC}*$') +RE_CUSTOM = re.compile(fr'^{PAT_PSEUDO_CLASS_CUSTOM}$', re.X) # Constants # List split token @@ -241,9 +241,9 @@ def process_custom(custom: ct.CustomSelectors | None) -> dict[str, str | ct.Sele for key, value in custom.items(): name = util.lower(key) if RE_CUSTOM.match(name) is None: - raise SelectorSyntaxError("The name '{}' is not a valid custom pseudo-class name".format(name)) + raise SelectorSyntaxError(f"The name '{name}' is not a valid custom pseudo-class name") if name in custom_selectors: - raise KeyError("The custom selector '{}' has already been registered".format(name)) + raise KeyError(f"The custom selector '{name}' has already been registered") custom_selectors[css_unescape(name)] = value return custom_selectors @@ -283,23 +283,23 @@ def escape(ident: str) -> str: start_dash = length > 0 and ident[0] == '-' if length == 1 and start_dash: # Need to escape identifier that is a single `-` with no other characters - string.append('\\{}'.format(ident)) + string.append(f'\\{ident}') else: for index, c in enumerate(ident): codepoint = ord(c) if codepoint == 0x00: string.append('\ufffd') elif (0x01 <= codepoint <= 0x1F) or codepoint == 0x7F: - string.append('\\{:x} '.format(codepoint)) + string.append(f'\\{codepoint:x} ') elif (index == 0 or (start_dash and index == 1)) and (0x30 <= codepoint <= 0x39): - string.append('\\{:x} '.format(codepoint)) + string.append(f'\\{codepoint:x} ') elif ( codepoint in (0x2D, 0x5F) or codepoint >= 0x80 or (0x30 <= codepoint <= 0x39) or (0x30 <= codepoint <= 0x39) or (0x41 <= codepoint <= 0x5A) or (0x61 <= codepoint <= 0x7A) ): string.append(c) else: - string.append('\\{}'.format(c)) + string.append(f'\\{c}') return ''.join(string) @@ -563,7 +563,7 @@ def parse_pseudo_class_custom(self, sel: _Selector, m: Match[str], has_selector: selector = self.custom.get(pseudo) if selector is None: raise SelectorSyntaxError( - "Undefined custom selector '{}' found at position {}".format(pseudo, m.end(0)), + f"Undefined custom selector '{pseudo}' found at position {m.end(0)}", self.pattern, m.end(0) ) @@ -663,13 +663,13 @@ def parse_pseudo_class( has_selector = True elif pseudo in PSEUDO_SUPPORTED: raise SelectorSyntaxError( - "Invalid syntax for pseudo class '{}'".format(pseudo), + f"Invalid syntax for pseudo class '{pseudo}'", self.pattern, m.start(0) ) else: raise NotImplementedError( - "'{}' pseudo-class is not implemented at this time".format(pseudo) + f"'{pseudo}' pseudo-class is not implemented at this time" ) return has_selector, is_html @@ -793,7 +793,7 @@ def parse_has_combinator( # multiple non-whitespace combinators. So if the current combinator is not a whitespace, # then we've hit the multiple combinator case, so we should fail. raise SelectorSyntaxError( - 'The multiple combinators at position {}'.format(index), + f'The multiple combinators at position {index}', self.pattern, index ) @@ -824,7 +824,7 @@ def parse_combinator( if not has_selector: if not is_forgive or combinator != COMMA_COMBINATOR: raise SelectorSyntaxError( - "The combinator '{}' at position {}, must have a selector before it".format(combinator, index), + f"The combinator '{combinator}' at position {index}, must have a selector before it", self.pattern, index ) @@ -982,13 +982,13 @@ def parse_selectors( # Handle parts if key == "at_rule": - raise NotImplementedError("At-rules found at position {}".format(m.start(0))) + raise NotImplementedError(f"At-rules found at position {m.start(0)}") elif key == 'pseudo_class_custom': has_selector = self.parse_pseudo_class_custom(sel, m, has_selector) elif key == 'pseudo_class': has_selector, is_html = self.parse_pseudo_class(sel, m, has_selector, iselector, is_html) elif key == 'pseudo_element': - raise NotImplementedError("Pseudo-element found at position {}".format(m.start(0))) + raise NotImplementedError(f"Pseudo-element found at position {m.start(0)}") elif key == 'pseudo_contains': has_selector = self.parse_pseudo_contains(sel, m, has_selector) elif key in ('pseudo_nth_type', 'pseudo_nth_child'): @@ -1003,7 +1003,7 @@ def parse_selectors( if not has_selector: if not is_forgive: raise SelectorSyntaxError( - "Expected a selector at position {}".format(m.start(0)), + f"Expected a selector at position {m.start(0)}", self.pattern, m.start(0) ) @@ -1013,7 +1013,7 @@ def parse_selectors( break else: raise SelectorSyntaxError( - "Unmatched pseudo-class close at position {}".format(m.start(0)), + f"Unmatched pseudo-class close at position {m.start(0)}", self.pattern, m.start(0) ) @@ -1031,7 +1031,7 @@ def parse_selectors( elif key == 'tag': if has_selector: raise SelectorSyntaxError( - "Tag name found at position {} instead of at the start".format(m.start(0)), + f"Tag name found at position {m.start(0)} instead of at the start", self.pattern, m.start(0) ) @@ -1046,7 +1046,7 @@ def parse_selectors( # Handle selectors that are not closed if is_open and not closed: raise SelectorSyntaxError( - "Unclosed pseudo-class at position {}".format(index), + f"Unclosed pseudo-class at position {index}", self.pattern, index ) @@ -1076,7 +1076,7 @@ def parse_selectors( # We will always need to finish a selector when `:has()` is used as it leads with combining. # May apply to others as well. raise SelectorSyntaxError( - 'Expected a selector at position {}'.format(index), + f'Expected a selector at position {index}', self.pattern, index ) @@ -1108,7 +1108,7 @@ def selector_iter(self, pattern: str) -> Iterator[tuple[str, Match[str]]]: end = (m.start(0) - 1) if m else (len(pattern) - 1) if self.debug: # pragma: no cover - print('## PARSING: {!r}'.format(pattern)) + print(f'## PARSING: {pattern!r}') while index <= end: m = None for v in self.css_tokens: @@ -1116,7 +1116,7 @@ def selector_iter(self, pattern: str) -> Iterator[tuple[str, Match[str]]]: if m: name = v.get_name() if self.debug: # pragma: no cover - print("TOKEN: '{}' --> {!r} at position {}".format(name, m.group(0), m.start(0))) + print(f"TOKEN: '{name}' --> {m.group(0)!r} at position {m.start(0)}") index = m.end(0) yield name, m break @@ -1126,15 +1126,15 @@ def selector_iter(self, pattern: str) -> Iterator[tuple[str, Match[str]]]: # throw an exception mentioning that the known selector type is in error; # otherwise, report the invalid character. if c == '[': - msg = "Malformed attribute selector at position {}".format(index) + msg = f"Malformed attribute selector at position {index}" elif c == '.': - msg = "Malformed class selector at position {}".format(index) + msg = f"Malformed class selector at position {index}" elif c == '#': - msg = "Malformed id selector at position {}".format(index) + msg = f"Malformed id selector at position {index}" elif c == ':': - msg = "Malformed pseudo-class selector at position {}".format(index) + msg = f"Malformed pseudo-class selector at position {index}" else: - msg = "Invalid character {!r} position {}".format(c, index) + msg = f"Invalid character {c!r} position {index}" raise SelectorSyntaxError(msg, self.pattern, index) if self.debug: # pragma: no cover print('## END PARSING') diff --git a/soupsieve/css_types.py b/soupsieve/css_types.py index c263bb04..37ffe7f4 100644 --- a/soupsieve/css_types.py +++ b/soupsieve/css_types.py @@ -45,11 +45,11 @@ def __init__(self, **kwargs: Any) -> None: for k, v in kwargs.items(): temp.append(type(v)) temp.append(v) - super(Immutable, self).__setattr__(k, v) - super(Immutable, self).__setattr__('_hash', hash(tuple(temp))) + super().__setattr__(k, v) + super().__setattr__('_hash', hash(tuple(temp))) @classmethod - def __base__(cls) -> "type[Immutable]": + def __base__(cls) -> type[Immutable]: """Get base class.""" return cls @@ -78,13 +78,13 @@ def __hash__(self) -> int: def __setattr__(self, name: str, value: Any) -> None: """Prevent mutability.""" - raise AttributeError("'{}' is immutable".format(self.__class__.__name__)) + raise AttributeError(f"'{self.__class__.__name__}' is immutable") def __repr__(self) -> str: # pragma: no cover """Representation.""" return "{}({})".format( - self.__class__.__name__, ', '.join(["{}={!r}".format(k, getattr(self, k)) for k in self.__slots__[:-1]]) + self.__class__.__name__, ', '.join([f"{k}={getattr(self, k)!r}" for k in self.__slots__[:-1]]) ) __str__ = __repr__ @@ -113,9 +113,9 @@ def _validate(self, arg: dict[Any, Any] | Iterable[tuple[Any, Any]]) -> None: if isinstance(arg, dict): if not all([isinstance(v, Hashable) for v in arg.values()]): - raise TypeError('{} values must be hashable'.format(self.__class__.__name__)) + raise TypeError(f'{self.__class__.__name__} values must be hashable') elif not all([isinstance(k, Hashable) and isinstance(v, Hashable) for k, v in arg]): - raise TypeError('{} values must be hashable'.format(self.__class__.__name__)) + raise TypeError(f'{self.__class__.__name__} values must be hashable') def __iter__(self) -> Iterator[Any]: """Iterator.""" @@ -140,7 +140,7 @@ def __hash__(self) -> int: def __repr__(self) -> str: # pragma: no cover """Representation.""" - return "{!r}".format(self._d) + return f"{self._d!r}" __str__ = __repr__ @@ -158,9 +158,9 @@ def _validate(self, arg: dict[str, str] | Iterable[tuple[str, str]]) -> None: if isinstance(arg, dict): if not all([isinstance(v, str) for v in arg.values()]): - raise TypeError('{} values must be hashable'.format(self.__class__.__name__)) + raise TypeError(f'{self.__class__.__name__} values must be hashable') elif not all([isinstance(k, str) and isinstance(v, str) for k, v in arg]): - raise TypeError('{} keys and values must be Unicode strings'.format(self.__class__.__name__)) + raise TypeError(f'{self.__class__.__name__} keys and values must be Unicode strings') class CustomSelectors(ImmutableDict): @@ -176,9 +176,9 @@ def _validate(self, arg: dict[str, str] | Iterable[tuple[str, str]]) -> None: if isinstance(arg, dict): if not all([isinstance(v, str) for v in arg.values()]): - raise TypeError('{} values must be hashable'.format(self.__class__.__name__)) + raise TypeError(f'{self.__class__.__name__} values must be hashable') elif not all([isinstance(k, str) and isinstance(v, str) for k, v in arg]): - raise TypeError('{} keys and values must be Unicode strings'.format(self.__class__.__name__)) + raise TypeError(f'{self.__class__.__name__} keys and values must be Unicode strings') class Selector(Immutable): diff --git a/soupsieve/pretty.py b/soupsieve/pretty.py index f848d5e2..2841c893 100644 --- a/soupsieve/pretty.py +++ b/soupsieve/pretty.py @@ -132,7 +132,7 @@ def pretty(obj: Any) -> str: # pragma: no cover elif name in ('sep',): output.append('{}\n{}'.format(m.group(1), " " * indent)) elif name in ('dsep',): - output.append('{} '.format(m.group(1))) + output.append(f'{m.group(1)} ') break return ''.join(output) diff --git a/soupsieve/util.py b/soupsieve/util.py index 9f91233b..9b2e64df 100644 --- a/soupsieve/util.py +++ b/soupsieve/util.py @@ -37,7 +37,7 @@ def __init__(self, msg: str, pattern: str | None = None, index: int | None = Non if pattern is not None and index is not None: # Format pattern to show line and column position self.context, self.line, self.col = get_pattern_context(pattern, index) - msg = '{}\n line {}:\n{}'.format(msg, self.line, self.context) + msg = f'{msg}\n line {self.line}:\n{self.context}' super().__init__(msg) @@ -105,7 +105,7 @@ def get_pattern_context(pattern: str, index: int) -> tuple[str, int, int]: # we will render the output with just `\n`. We will still log the column # correctly though. text.append('\n') - text.append('{}{}'.format(indent, linetext)) + text.append(f'{indent}{linetext}') if offset is not None: text.append('\n') text.append(' ' * (col + offset) + '^') diff --git a/tests/test_api.py b/tests/test_api.py index 7bd4fb4a..10dd3aa6 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -463,7 +463,7 @@ def test_cache(self): sv.purge() self.assertEqual(sv.cp._cached_css_compile.cache_info().currsize, 0) for x in range(1000): - value = '[value="{}"]'.format(str(random.randint(1, 10000))) + value = f'[value="{str(random.randint(1, 10000))}"]' p = sv.compile(value) self.assertTrue(p.pattern == value) self.assertTrue(sv.cp._cached_css_compile.cache_info().currsize > 0) diff --git a/tests/test_level4/test_dir.py b/tests/test_level4/test_dir.py index e4277154..a9a5054c 100644 --- a/tests/test_level4/test_dir.py +++ b/tests/test_level4/test_dir.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Test direction selectors.""" from .. import util import soupsieve as sv diff --git a/tests/util.py b/tests/util.py index 49173540..a7af35e3 100644 --- a/tests/util.py +++ b/tests/util.py @@ -134,7 +134,7 @@ def available_parsers(*parsers): (parser in ('xml', 'lxml') and not LXML_PRESENT) or (parser == 'html5lib' and not HTML5LIB_PRESENT) ): - print('SKIPPED {}, not installed'.format(parser)) + print(f'SKIPPED {parser}, not installed') else: ran_test = True yield parser