Upgrading soupsieve 2 to 3
Breaking2.10 → 3.0.4Breaking2Platform2New1Changed2Fixed13
BreakingUpgrade verdict
2 breaking changes and 2 platform requirement changes.
In plain terms
- 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.
Must act 4 items need code or config changes
Python requirement changed in 3.0.2: >=3.10 → >=3.11.5.
What you must change
Breaking2
:has() now defaults to stricter CSS behavior, disallowing combinators and nested :has(); the new NOSTRICT flag restores pre-3.0 behavior.:has()NOSTRICTCustom selectors must now be provided in dependency order, with dependencies defined first; pre-compile them with
CustomSelectors.CustomSelectorsPlatform2
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.
API changes
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