Skip to content

Upgrading soupsieve 2 to 3

Breaking2.10 → 3.0.4Breaking2Platform2New1Changed2Fixed13

BreakingUpgrade verdict

2 breaking changes and 2 platform requirement changes.

  • The `:has()` selector is now safer and faster by default, but it rejects some selectors that used to work. You can turn the old behavior back on with a new flag.
  • If one custom selector uses another, you must now list the one it depends on first. Pre-compiling them is recommended.
  • This version needs a newer Python patch release to work correctly, so older Python 3.11 versions are no longer supported.
  • This version no longer works on Python 3.10.
4 items need code or config changes

Python requirement changed in 3.0.2: >=3.10 → >=3.11.5.

Breaking2
:has() now defaults to stricter CSS behavior, disallowing combinators and nested :has(); the new NOSTRICT flag restores pre-3.0 behavior.
:has()NOSTRICT
Custom selectors must now be provided in dependency order, with dependencies defined first; pre-compile them with CustomSelectors.
CustomSelectors
Platform2
Requires Python 3.11.5 or later for Soup Sieve 3.0+ because earlier Python versions have broken possessive quantifiers.
Drops support for Python 3.10.

Generated by DepDelta from published Python source. Not mixed with prose notes.

  • changed: function closest(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, flags: int=…, *, custom: dict[str, str] | None=…, ignore: Iterable[str] | None=…, **kwargs: Any) -> bs4.Tag | None → function closest(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, flags: int=…, *, custom: Mapping[str, str | ct.SelectorList] | None=…, ignore: Iterable[str] | None=…, max_selectors: int=…, **kwargs: Any) -> bs4.Tag | None
  • changed: function compile(pattern: str, namespaces: dict[str, str] | None=…, flags: int=…, *, custom: dict[str, str] | None=…, ignore: Iterable[str] | None=…, **kwargs: Any) -> cm.SoupSieve → function compile(pattern: str, namespaces: dict[str, str] | None=…, flags: int=…, *, custom: Mapping[str, str | ct.SelectorList] | None=…, ignore: Iterable[str] | None=…, max_selectors: int=…, **kwargs: Any) -> cm.SoupSieve
  • changed: function filter(select: str, iterable: Iterable[bs4.Tag], namespaces: dict[str, str] | None=…, flags: int=…, *, custom: dict[str, str] | None=…, ignore: Iterable[str] | None=…, **kwargs: Any) -> list[bs4.Tag] → function filter(select: str, iterable: Iterable[bs4.Tag], namespaces: dict[str, str] | None=…, flags: int=…, *, custom: Mapping[str, str | ct.SelectorList] | None=…, ignore: Iterable[str] | None=…, max_selectors: int=…, **kwargs: Any) -> list[bs4.Tag]
  • changed: function iselect(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, limit: int=…, flags: int=…, *, custom: dict[str, str] | None=…, ignore: Iterable[str] | None=…, **kwargs: Any) -> Iterator[bs4.Tag] → function iselect(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, limit: int=…, flags: int=…, *, custom: Mapping[str, str | ct.SelectorList] | None=…, ignore: Iterable[str] | None=…, max_selectors: int=…, **kwargs: Any) -> Iterator[bs4.Tag]
  • changed: function match(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, flags: int=…, *, custom: dict[str, str] | None=…, ignore: Iterable[str] | None=…, **kwargs: Any) -> bool → function match(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, flags: int=…, *, custom: Mapping[str, str | ct.SelectorList] | None=…, ignore: Iterable[str] | None=…, max_selectors: int=…, **kwargs: Any) -> bool
  • changed: function select(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, limit: int=…, flags: int=…, *, custom: dict[str, str] | None=…, ignore: Iterable[str] | None=…, **kwargs: Any) -> list[bs4.Tag] → function select(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, limit: int=…, flags: int=…, *, custom: Mapping[str, str | ct.SelectorList] | None=…, ignore: Iterable[str] | None=…, max_selectors: int=…, **kwargs: Any) -> list[bs4.Tag]
  • changed: function select_one(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, flags: int=…, *, custom: dict[str, str] | None=…, ignore: Iterable[str] | None=…, **kwargs: Any) -> bs4.Tag | None → function select_one(select: str, tag: bs4.Tag, namespaces: dict[str, str] | None=…, flags: int=…, *, custom: Mapping[str, str | ct.SelectorList] | None=…, ignore: Iterable[str] | None=…, max_selectors: int=…, **kwargs: Any) -> bs4.Tag | None
  • added: export NOSTRICT
  • added: variable CustomSelectors
  • changed: method CSSMatch.match_future_child(parent: bs4.Tag, relation: ct.SelectorList, recursive: bool=…) -> bool → method CSSMatch.match_future_child(parent: bs4.Tag, relation: ct.SelectorList, recursive: bool=…) -> MatchGenerator
  • changed: method CSSMatch.match_future_relations(el: bs4.Tag, relation: ct.SelectorList) -> bool → method CSSMatch.match_future_relations(el: bs4.Tag, relation: ct.SelectorList) -> MatchGenerator
  • changed: method CSSMatch.match_general_sibling(el: bs4.Tag, relation: ct.SelectorList) -> bool → method CSSMatch.match_general_sibling(el: bs4.Tag, relation: ct.SelectorList) -> MatchGenerator
  • changed: method CSSMatch.match_nth(el: bs4.Tag, nth: tuple[ct.SelectorNth, ...]) -> bool → method CSSMatch.match_nth(el: bs4.Tag, nth: tuple[ct.SelectorNth, ...]) -> MatchGenerator
  • changed: method CSSMatch.match_past_relations(el: bs4.Tag, relation: ct.SelectorList) -> bool → method CSSMatch.match_past_relations(el: bs4.Tag, relation: ct.SelectorList) -> MatchGenerator
  • changed: method CSSMatch.match_relations(el: bs4.Tag, relation: ct.SelectorList) -> bool → method CSSMatch.match_relations(el: bs4.Tag, relation: ct.SelectorList) -> MatchGenerator
  • changed: method CSSMatch.match_subselectors(el: bs4.Tag, selectors: tuple[ct.SelectorList, ...]) -> bool → method CSSMatch.match_subselectors(el: bs4.Tag, selectors: tuple[ct.SelectorList, ...]) -> MatchGenerator
  • added: method CSSMatch.resolve_dir(el: bs4.Tag | None, visited: list[int]) -> int
  • added: static method Inputs.validate_seconds(seconds: float) -> bool
  • added: variable MatchGenerator
  • added: variable MatchRequest
  • removed: class CSSParser
  • removed: class CSSPattern
  • removed: class PseudoSelectorMap(UserDict[str, CSSPattern | ct.SelectorList])
  • removed: class SelectorPattern
  • removed: class SpecialPseudoPattern(SelectorPattern)
  • removed: constructor CSSParser(selector: str, *, custom: dict[str, str | ct.SelectorList] | None=…, ignore: Iterable[str] | None=…, flags: int=…) -> None
  • removed: constructor SelectorPattern(name: str, pattern: str) -> None
  • removed: constructor SpecialPseudoPattern(patterns: tuple[tuple[str, tuple[str, ...], str, type[SelectorPattern]], ...]) -> None
  • removed: field CSSParser.CSS_TOKENS
  • removed: field CSSParser.PSEUDO_SELECTORS
  • removed: field CSSPattern.flags: int
  • removed: field CSSPattern.selector: str
  • removed: function css_unescape(content: str) -> str
  • removed: function escape(ident: str) -> str
  • removed: function process_custom(custom: ct.CustomSelectors | None) -> dict[str, str | ct.SelectorList]
  • removed: method CSSParser.increment_count(increment: int=…) -> None
  • removed: method CSSParser.parse_attribute_selector(sel: _Selector, m: Match[str], has_selector: bool) -> bool
  • removed: method CSSParser.parse_class_id(sel: _Selector, m: Match[str], has_selector: bool) -> bool
  • removed: method CSSParser.parse_combinator(sel: _Selector, m: Match[str], has_selector: bool, selectors: list[_Selector], relations: list[_Selector], is_pseudo: bool, is_forgive: bool, index: int) -> tuple[bool, _Selector]
  • removed: method CSSParser.parse_has_combinator(sel: _Selector, m: Match[str], has_selector: bool, selectors: list[_Selector], rel_type: str, index: int) -> tuple[bool, _Selector, str]
  • removed: method CSSParser.parse_pseudo_class(sel: _Selector, m: Match[str], has_selector: bool, iselector: Iterator[tuple[str, Match[str]]], is_html: bool) -> tuple[bool, bool]
  • removed: method CSSParser.parse_pseudo_class_custom(sel: _Selector, m: Match[str], has_selector: bool) -> bool
  • removed: method CSSParser.parse_pseudo_contains(sel: _Selector, m: Match[str], has_selector: bool) -> bool
  • removed: method CSSParser.parse_pseudo_dir(sel: _Selector, m: Match[str], has_selector: bool) -> bool
  • removed: method CSSParser.parse_pseudo_lang(sel: _Selector, m: Match[str], has_selector: bool) -> bool
  • removed: method CSSParser.parse_pseudo_nth(sel: _Selector, m: Match[str], has_selector: bool, iselector: Iterator[tuple[str, Match[str]]]) -> bool
  • removed: method CSSParser.parse_pseudo_open(sel: _Selector, name: str, has_selector: bool, iselector: Iterator[tuple[str, Match[str]]], index: int) -> bool
  • removed: method CSSParser.parse_selectors(iselector: Iterator[tuple[str, Match[str]]], index: int=…, flags: int=…) -> ct.SelectorList
  • removed: method CSSParser.parse_tag_pattern(sel: _Selector, m: Match[str], has_selector: bool) -> bool
  • removed: method CSSParser.process_selectors(index: int=…, flags: int=…) -> ct.SelectorList
  • removed: method CSSParser.selector_iter(pattern: str) -> Iterator[tuple[str, Match[str]]]
  • removed: method SelectorPattern.get_name() -> str
  • removed: method SelectorPattern.match(selector: str, index: int, flags: int) -> Match[str] | None
  • removed: method SpecialPseudoPattern.get_name() -> str
  • removed: method SpecialPseudoPattern.match(selector: str, index: int, flags: int) -> Match[str] | None
  • removed: property SelectorPattern.re_pattern -> re.Pattern[str]
  • removed: variable ATTR
  • removed: variable COMMA_COMBINATOR
  • removed: variable COMMENTS
  • removed: variable CSS_CHECKED
  • removed: variable CSS_DEFAULT
  • removed: variable CSS_DISABLED
  • removed: variable CSS_ENABLED
  • removed: variable CSS_ESCAPES
  • removed: variable CSS_INDETERMINATE
  • removed: variable CSS_IN_RANGE
  • removed: variable CSS_LINK
  • removed: variable CSS_MUTED
  • removed: variable CSS_NTH_OF_S_DEFAULT
  • removed: variable CSS_OPEN
  • removed: variable CSS_OPTIONAL
  • removed: variable CSS_OUT_OF_RANGE
  • removed: variable CSS_PLACEHOLDER_SHOWN
  • removed: variable CSS_READ_ONLY
  • removed: variable CSS_READ_WRITE
  • removed: variable CSS_REQUIRED
  • removed: variable CSS_STRING_ESCAPES
  • removed: variable FLG_DEFAULT
  • removed: variable FLG_FORGIVE
  • removed: variable FLG_HTML
  • removed: variable FLG_INDETERMINATE
  • removed: variable FLG_IN_RANGE
  • removed: variable FLG_NOT
  • removed: variable FLG_OPEN
  • removed: variable FLG_OUT_OF_RANGE
  • removed: variable FLG_PLACEHOLDER_SHOWN
  • removed: variable FLG_PSEUDO
  • removed: variable FLG_RELATIVE
  • removed: variable IDENTIFIER
  • removed: variable NEWLINE
  • removed: variable NTH
  • removed: variable PAT_AMP
  • removed: variable PAT_ATTR
  • removed: variable PAT_AT_RULE
  • removed: variable PAT_CLASS
  • removed: variable PAT_COMBINE
  • removed: variable PAT_ID
  • removed: variable PAT_PSEUDO_CLASS
  • removed: variable PAT_PSEUDO_CLASS_CUSTOM
  • removed: variable PAT_PSEUDO_CLASS_SPECIAL
  • removed: variable PAT_PSEUDO_CLOSE
  • removed: variable PAT_PSEUDO_CONTAINS
  • removed: variable PAT_PSEUDO_DIR
  • removed: variable PAT_PSEUDO_ELEMENT
  • removed: variable PAT_PSEUDO_LANG
  • removed: variable PAT_PSEUDO_NTH_CHILD
  • removed: variable PAT_PSEUDO_NTH_TYPE
  • removed: variable PAT_TAG
  • removed: variable PSEUDO_COMPLEX
  • removed: variable PSEUDO_COMPLEX_NO_MATCH
  • removed: variable PSEUDO_SIMPLE
  • removed: variable PSEUDO_SIMPLE_NO_MATCH
  • removed: variable PSEUDO_SPECIAL
  • removed: variable PSEUDO_SUPPORTED
  • removed: variable QUOTED
  • removed: variable RE_CSS_ESC
  • removed: variable RE_CSS_STR_ESC
  • removed: variable RE_CUSTOM
  • removed: variable RE_LOCK
  • removed: variable RE_NTH
  • removed: variable RE_PSEUDO_CLASS_SPECIAL
  • removed: variable RE_PSEUDO_IGNORE
  • removed: variable RE_VALUES
  • removed: variable RE_WS
  • removed: variable RE_WS_BEGIN
  • removed: variable RE_WS_END
  • removed: variable SELECTOR_LIMIT
  • removed: variable SEL_LOCK
  • removed: variable UNICODE_REPLACEMENT_CHAR
  • removed: variable VALUE
  • removed: variable WS
  • removed: variable WSC
  • removed: variable WS_COMBINATOR
  • added: export SelectorSyntaxError
  • changed: class CustomSelectors(ImmutableDict) → class CustomSelectors(ImmutableDict[str, SelectorList])
  • changed: class Namespaces(ImmutableDict) → class Namespaces(ImmutableDict[str, str])
  • changed: constructor CustomSelectors(arg: Mapping[str, str]) -> None → constructor CustomSelectors(arg: Mapping[str, str | SelectorList], max_selectors: int | None=…) -> None
  • changed: constructor SelectorAttribute(attribute: str, prefix: str, pattern: Pattern[str] | None, xml_type_pattern: Pattern[str] | None) -> None → constructor SelectorAttribute(attribute: str, prefix: str, pattern: Pattern[str] | None, xml_type_pattern: Pattern[str] | None, inverse: bool) -> None
  • added: field SelectorAttribute.inverse: bool
  • added: variable NOSTRICT