D4Vinci/Scrapling · error · ValueError

Expressions of type {__name__}.XPathExpr can ony join expres

Error message

Expressions of type {__name__}.XPathExpr can ony join expressions of the same type (or its descendants), got {type(other)}

What it means

Raised by `XPathExpr.join` in scrapling/core/translator.py, Scrapling's cssselect extension that adds `::text` and `::attr()` pseudo-elements. When two CSS selector expressions are combined (comma grouping inside a selector like `div a, div b::text`), `join` requires `other` to be an `XPathExpr` (or subclass) so the `textnode`/`attribute` state carries over correctly. Passing a plain `cssselect.GenericTranslator` XPathExpr — which happens if a different translator or a raw expression object is mixed in — triggers this ValueError.

Source

Thrown at scrapling/core/translator.py:61

            else:
                path += "/text()"

        if self.attribute is not None:
            if path.endswith("::*/*"):  # pragma: no cover
                path = path[:-2]
            path += f"/@{self.attribute}"

        return path

    def join(
        self: Self,
        combiner: str,
        other: OriginalXPathExpr,
        *args: Any,
        **kwargs: Any,
    ) -> Self:
        if not isinstance(other, XPathExpr):
            raise ValueError(  # pragma: no cover
                f"Expressions of type {__name__}.XPathExpr can ony join expressions"
                f" of the same type (or its descendants), got {type(other)}"
            )
        super().join(combiner, other, *args, **kwargs)
        self.textnode = other.textnode
        self.attribute = other.attribute
        return self


# e.g. cssselect.GenericTranslator, cssselect.HTMLTranslator
class TranslatorProtocol(Protocol):
    def xpath_element(self, selector: Element) -> OriginalXPathExpr:  # pyright: ignore # pragma: no cover
        pass

    def css_to_xpath(self, css: str, prefix: str = ...) -> str:  # pyright: ignore # pragma: no cover
        pass

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Let Scrapling translate the whole CSS selector itself — pass the full selector string to `page.css('a::attr(href), a::text')` instead of pre-translating parts with cssselect.
  2. If you must combine programmatically, wrap the foreign expression first: `XPathExpr.from_xpath(raw_expr)` before joining.
  3. Check for stray `GenericTranslator`/`HTMLTranslator` usage in your code and replace with Scrapling's translator classes.
  4. Reproduce with a minimal selector to confirm which branch produces the non-Scrapling expression.

Example fix

# before
from cssselect import GenericTranslator
partial = GenericTranslator().css_to_xpath('div a')
selector.css(partial + '|//span')  # mixing translators

# after
selector.css('div a, span')  # one selector, one translator
Defensive patterns

Strategy: validation

Validate before calling

from scrapling.core.translator import XPathExpr
from cssselect.xpath import XPathExpr as OriginalXPathExpr

def joinable(expr) -> bool:
    return isinstance(expr, XPathExpr)

Type guard

from scrapling.core.translator import XPathExpr

def is_scrapling_xpath_expr(expr) -> bool:
    """True if expr can be joined with Scrapling XPathExpr instances."""
    return isinstance(expr, XPathExpr)

Try / catch

try:
    combined = expr_a.join(',', expr_b)
except ValueError as e:
    # wrap foreign expressions first, then retry once
    from scrapling.core.translator import XPathExpr
    combined = expr_a.join(',', XPathExpr.from_xpath(expr_b))

Prevention

When it happens

Trigger: Combining selector expressions where one side was produced by a non-Scrapling translator (plain `GenericTranslator().selector_to_xpath(...)` result fed into a Scrapling CSS query chain), or calling `XPathExpr.join(combiner, other)` manually with a `cssselect.xpath.XPathExpr` instance. Typical user-facing selector: grouping with `::text`/`::attr()` across differently-translated branches.

Common situations: Mixing Scrapling's `HTMLTranslator` with stock cssselect translation output; code that pre-compiles part of a selector with `cssselect` directly and concatenates it into a `Selector.css()` query; upgrading Scrapling versions where the translator internals changed shape.

Related errors


AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14). Data as JSON: /api/errors/03c39c316c466cef. Report an issue: GitHub.