Clarify/cleanup Selector.type (#7704)

This commit is contained in:
Andrey Rakhmatullin 2026-07-01 11:50:32 +05:00 committed by GitHub
parent a6d6a48aa6
commit fc5216f156
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 17 additions and 30 deletions

View File

@ -1,10 +1,6 @@
"""
XPath selectors based on lxml
"""
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any, Literal
from parsel import Selector as _ParselSelector from parsel import Selector as _ParselSelector
@ -18,13 +14,10 @@ __all__ = ["Selector", "SelectorList"]
_NOT_SET = object() _NOT_SET = object()
def _st(response: TextResponse | None, st: str | None) -> str: SelectorType = Literal["html", "xml", "json", "text"]
if st is None:
return "xml" if isinstance(response, XmlResponse) else "html"
return st
def _response_from_text(text: str | bytes, st: str | None) -> TextResponse: def _response_from_text(text: str | bytes, st: SelectorType | None) -> TextResponse:
rt: type[TextResponse] = XmlResponse if st == "xml" else HtmlResponse rt: type[TextResponse] = XmlResponse if st == "xml" else HtmlResponse
return rt(url="about:blank", encoding="utf-8", body=to_bytes(text, "utf-8")) return rt(url="about:blank", encoding="utf-8", body=to_bytes(text, "utf-8"))
@ -49,23 +42,16 @@ class Selector(_ParselSelector, object_ref):
``response`` isn't available. Using ``text`` and ``response`` together is ``response`` isn't available. Using ``text`` and ``response`` together is
undefined behavior. undefined behavior.
``type`` defines the selector type, it can be ``"html"``, ``"xml"``, ``"json"`` ``type`` defines the selector type, it can be ``"html"``, ``"xml"``,
or ``None`` (default). ``"json"``, ``"text"`` or ``None`` (default). It's passed to
:class:`parsel.Selector` and its meaning is defined there. However, when
``type`` is ``None``, it is set to ``"xml"`` for an
:class:`~scrapy.http.XmlResponse` and to ``"html"`` otherwise before
passing it to :class:`parsel.Selector`.
If ``type`` is ``None``, the selector automatically chooses the best type .. note:: JSON selector support requires ``parsel`` 1.8.0 or higher. With
based on ``response`` type (see below), or defaults to ``"html"`` in case it older versions setting ``type`` to ``"json"`` or ``"text"`` is not
is used together with ``text``. supported.
If ``type`` is ``None`` and a ``response`` is passed, the selector type is
inferred from the response type as follows:
* ``"html"`` for :class:`~scrapy.http.HtmlResponse` type
* ``"xml"`` for :class:`~scrapy.http.XmlResponse` type
* ``"json"`` for :class:`~scrapy.http.TextResponse` type
* ``"html"`` for anything else
Otherwise, if ``type`` is set, the selector type will be forced and no
detection will occur.
""" """
__slots__ = ["response"] __slots__ = ["response"]
@ -75,7 +61,7 @@ class Selector(_ParselSelector, object_ref):
self, self,
response: TextResponse | None = None, response: TextResponse | None = None,
text: str | None = None, text: str | None = None,
type: str | None = None, # noqa: A002 type: SelectorType | None = None, # noqa: A002
root: Any | None = _NOT_SET, root: Any | None = _NOT_SET,
**kwargs: Any, **kwargs: Any,
): ):
@ -84,10 +70,11 @@ class Selector(_ParselSelector, object_ref):
f"{self.__class__.__name__}.__init__() received both response and text" f"{self.__class__.__name__}.__init__() received both response and text"
) )
st = _st(response, type) if type is None:
type = "xml" if isinstance(response, XmlResponse) else "html" # noqa: A001
if text is not None: if text is not None:
response = _response_from_text(text, st) response = _response_from_text(text, type)
if response is not None: if response is not None:
text = response.text text = response.text
@ -98,4 +85,4 @@ class Selector(_ParselSelector, object_ref):
if root is not _NOT_SET: if root is not _NOT_SET:
kwargs["root"] = root kwargs["root"] = root
super().__init__(text=text, type=st, **kwargs) super().__init__(text=text, type=type, **kwargs)