typeshed icon indicating copy to clipboard operation
typeshed copied to clipboard

`ET.Element.tag` does not allow comparision with `ET.Comment`

Open hterik opened this issue 5 months ago • 2 comments

To Reproduce

import xml.etree.ElementTree as ET

parser = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True))
doc = ET.ElementTree().parse("foo.xml", parser)
for el in doc.findall("*"):
    if el.tag is ET.Comment:     #  error: Non-overlapping identity check (left operand type: "str", right operand type: "Callable[[str | None], Element[Callable[..., Element[Any]]]]")  [comparison-overlap]
        print("Comment:", x.text)

Expected Behavior

el.tag should be allowed to be compared to ET.Comment

Actual Behavior

el.tag is constrained to str only and raises following comparision-overlap error when compared with ET.Comment: # error: Non-overlapping identity check (left operand type: "str", right operand type: "Callable[[str | None], Element[Callable[..., Element[Any]]]]") [comparison-overlap]

This error started after upgrading from mypy 1.14 to 1.15, most likely caused by https://github.com/python/typeshed/pull/13349

Tbh this isn't exactly documented behaviour and parsing comments programmatically isn't exactly best practice either, but some times you are not in control of the xmls received and reading the tag is the the only way to identify the comments that i know.

Your Environment

  • Mypy version used: 1.17
  • Mypy command-line flags: mypy --strict test.py
  • Python version used: 3.13

hterik avatar Sep 03 '25 09:09 hterik

Can someone transfer this issue and python/typeshed#14672 to the typeshed tracker? @JukkaL maybe?

cc @tungol

brianschubert avatar Sep 03 '25 11:09 brianschubert

I think I should re-work how typevars are used in xml.etree, but overall this is mostly doable. Unfortunately, I don't believe there's any way to get mypy to understand that ET.TreeBuilder(insert_comments=True) can return str or ET.Comment tags while ET.TreeBuilder() would only create elements with str tags. A second issue is that static typing of the 'target' parameter of XMLParser is basically impossible. I can get the stubs to a state where this is possible, however:

import xml.etree.ElementTree as ET

type _ParserType = ET.XMLParser[ET.Element[str | ET._ElementCallable]]

parser: _ParserType = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True))
doc = ET.ElementTree().parse("foo.xml", parser)
for el in doc.findall("*"):
    if el.tag is ET.Comment:
        print("Comment:", el.text)

While this will correctly throw the error you got:

import xml.etree.ElementTree as ET

type _ParserType = ET.XMLParser[ET.Element[str]]

parser: _ParserType = ET.XMLParser(target=ET.TreeBuilder())
doc = ET.ElementTree().parse("foo.xml", parser)
for el in doc.findall("*"):
    if el.tag is ET.Comment:
        print("Comment:", el.text)

The question is which behavior should this have?

import xml.etree.ElementTree as ET

parser = ET.XMLParser(target=ET.TreeBuilder())
doc = ET.ElementTree().parse("foo.xml", parser)
for el in doc.findall("*"):
    if el.tag is ET.Comment:
        print("Comment:", el.text)

parser2 = ET.XMLParser(target=ET.TreeBuilder(insert_comments=True))
doc = ET.ElementTree().parse("foo.xml", parser2)
for el in doc.findall("*"):
    "".join(["hello", el.tag])

With the stubs I've put together, this now gets a type for parser of XMLParser[Element[Any]] and no error is given for either. Without a better way to make mypy understand insert_comments=True and how that propagates through, that's about the best I can do.

tungol avatar Sep 04 '25 05:09 tungol