diff --git a/src/lxml-stubs/_types.pyi b/src/lxml-stubs/_types.pyi index 04ce6dd..c3ceb0f 100644 --- a/src/lxml-stubs/_types.pyi +++ b/src/lxml-stubs/_types.pyi @@ -34,11 +34,34 @@ _AnyStr = str | bytes _TagName = str | bytes | bytearray | QName _AttrName = str | bytes | bytearray | QName _AttrVal = str | bytes | bytearray | QName +_AttrNameKey = str | bytes | QName +"""Types supported in attribute name, sans bytearray""" # On the other hand, Elementpath API doesn't do str/byte canonicalization, # only unicode accepted for py3 _ElemPathArg = str | QName +_AttrMapping = SupportsLaxedItems[_AttrNameKey, _AttrVal] +"""Attribute dict-like mapping + +Used in attrib argument of various factories and methods. +Bytearray not supported as key (not hashable). + +Internal stuff +-------------- +Anything that delves into `_initNodeAttributes()` +(in `apihelper.pxi`) should be able to use it. +Need to make sure `_Attrib` and `dict` are supported in +places wherever this alias is used. +""" + +_AttrTuples = Iterable[tuple[_AttrNameKey, _AttrVal]] +"""Tuple form of attribute key/value pairs + +Used in attrib argument where tuple form is accepted, +in place of or in addition to `_AttrMapping`. +""" + # Due to Mapping having invariant key types, Mapping[A | B, ...] # would fail to validate against either Mapping[A, ...] or Mapping[B, ...] # Try to settle for simpler solution, assuming python3 users would not @@ -131,24 +154,24 @@ class _ElementFactory(Protocol, Generic[_ET_co]): This is callback protocol for `makeelement()` method of various element objects, with following signature (which - is identical to `etree.Element()` function): + is identical to `etree.Element()` factory): ```python (_tag, attrib=..., nsmap=..., **_extra) ``` The mapping in `attrib` argument and all `_extra` keyword - arguments would be merged together. The result is usually - `{**attrib, **_extra}`, but some places may deviate. + arguments would be merged together, with `_extra` taking + precedence over `attrib`. """ def __call__( self, _tag: _TagName, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | None = None, nsmap: _NSMapArg | None = None, - **_extra: _AnyStr, + **_extra: _AttrVal, ) -> _ET_co: ... # Note that _TagSelector filters element type not by classes, @@ -165,9 +188,11 @@ _DefEtreeParsers = XMLParser[_ET_co] | HTMLParser[_ET_co] class SupportsLaxedItems(Protocol[_KT_co, _VT_co]): """Relaxed form of SupportsItems - Original SupportsItems from typeshed returns generic set which - is compatible with ItemsView. However, _Attrib doesn't conform - and returns list instead. Gotta find a common ground here. + Original `SupportsItems` from typeshed returns + generic set which is compatible with `ItemsView`. + However, `_Attrib.items()` returns `list` instead. + Here we find a common ground that satisfies both + and avoid the mapping invariance culprit. """ def items(self) -> Collection[tuple[_KT_co, _VT_co]]: ... diff --git a/src/lxml-stubs/etree/_classlookup.pyi b/src/lxml-stubs/etree/_classlookup.pyi index d3663fc..4f85c5c 100644 --- a/src/lxml-stubs/etree/_classlookup.pyi +++ b/src/lxml-stubs/etree/_classlookup.pyi @@ -6,9 +6,9 @@ from abc import ABCMeta, abstractmethod from typing import Mapping, final from .._types import ( - SupportsLaxedItems, - _AnyStr, + _AttrMapping, _AttrName, + _AttrVal, _ElemClsLookupArg, _NSMapArg, ) @@ -59,9 +59,9 @@ class ElementBase(_Element): def __init__( self, *children: object, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | None = None, nsmap: _NSMapArg | None = None, - **_extra: _AnyStr, + **_extra: _AttrVal, ) -> None: ... def _init(self) -> None: ... diff --git a/src/lxml-stubs/etree/_element.pyi b/src/lxml-stubs/etree/_element.pyi index 0b655ec..b322210 100644 --- a/src/lxml-stubs/etree/_element.pyi +++ b/src/lxml-stubs/etree/_element.pyi @@ -392,7 +392,8 @@ class _ElementTree(Generic[_t._ET_co]): class _Attrib: def __setitem__(self, __k: _t._AttrName, __v: _t._AttrVal) -> None: ... def __delitem__(self, __k: _t._AttrName) -> None: ... - # explicitly checks for dict and _Attrib + # _only_ checks for dict and _Attrib to do + # .items() conversion, not any Mapping def update( self, sequence_or_dict: ( diff --git a/src/lxml-stubs/etree/_factory_func.pyi b/src/lxml-stubs/etree/_factory_func.pyi index 0665d00..23fa520 100644 --- a/src/lxml-stubs/etree/_factory_func.pyi +++ b/src/lxml-stubs/etree/_factory_func.pyi @@ -2,8 +2,8 @@ from typing import overload from .._types import ( _ET, - SupportsLaxedItems, - _AnyStr, + _AttrMapping, + _AttrVal, _DefEtreeParsers, _ElementFactory, _ET_co, @@ -45,27 +45,27 @@ def SubElement( _parent: ObjectifiedElement, _tag: _TagName, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | None = None, nsmap: _NSMapArg | None = None, - **_extra: _AnyStr, + **_extra: _AttrVal, ) -> StringElement: ... @overload def SubElement( _parent: HtmlElement, _tag: _TagName, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | None = None, nsmap: _NSMapArg | None = None, - **_extra: _AnyStr, + **_extra: _AttrVal, ) -> HtmlElement: ... @overload def SubElement( _parent: _ET, _tag: _TagName, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | None = None, nsmap: _NSMapArg | None = None, - **_extra: _AnyStr, + **_extra: _AttrVal, ) -> _ET: ... @overload # from element, parser ignored def ElementTree(element: _ET) -> _ElementTree[_ET]: ... diff --git a/src/lxml-stubs/etree/_serializer.pyi b/src/lxml-stubs/etree/_serializer.pyi index 859771a..89b0559 100644 --- a/src/lxml-stubs/etree/_serializer.pyi +++ b/src/lxml-stubs/etree/_serializer.pyi @@ -14,6 +14,8 @@ from _typeshed import SupportsWrite from .._types import ( SupportsLaxedItems, _AnyStr, + _AttrMapping, + _AttrVal, _ElementOrTree, _FileReadSource, _FileWriteSource, @@ -139,10 +141,10 @@ class _IncrementalFileWriter: def element( self, tag: _TagName, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | None = None, nsmap: _NSMapArg | None = None, method: _OutputMethodArg | None = None, - **_extra: _AnyStr, + **_extra: _AttrVal, ) -> ContextManager[None]: ... @final @@ -166,10 +168,10 @@ class _AsyncIncrementalFileWriter: def element( self, tag: _TagName, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | None = None, nsmap: _NSMapArg | None = None, method: _OutputMethodArg | None = None, - **_extra: _AnyStr, + **_extra: _AttrVal, ) -> AsyncContextManager[None]: ... class xmlfile( diff --git a/src/lxml-stubs/objectify/_factory.pyi b/src/lxml-stubs/objectify/_factory.pyi index 3ef7ef3..e76a972 100644 --- a/src/lxml-stubs/objectify/_factory.pyi +++ b/src/lxml-stubs/objectify/_factory.pyi @@ -4,20 +4,30 @@ from typing import Any, Literal, Protocol, TypeVar, overload -from .._types import SupportsLaxedItems, _AnyStr, _ElementFactory, _NSMapArg, _TagName +from .._types import ( + _AnyStr, + _AttrMapping, + _AttrTuples, + _AttrVal, + _ElementFactory, + _NSMapArg, + _TagName, +) from ..etree import _Element from . import _element as _e _DataElem_T = TypeVar("_DataElem_T", bound=_e.ObjectifiedDataElement) +# Objectified Element factories does extra dict() conversion of +# attrib argument, thus supports tuple form def Element( _tag: _TagName, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: str | None = None, - **__attr: _AnyStr, + **_attributes: _AttrVal, ) -> _e.ObjectifiedElement: """Objectify specific version of `lxml.etree` `Element()` factory @@ -48,9 +58,9 @@ def SubElement( _parent: _e.ObjectifiedElement, _tag: _TagName, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | None = None, nsmap: _NSMapArg | None = None, - **__attr: _AnyStr, + **_extra: _AttrVal, ) -> _e.ObjectifiedElement: ... # TODO Current overload situation is unsatisfactory. Will decide @@ -62,133 +72,133 @@ def SubElement( def DataElement( _value: _DataElem_T, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: None = None, _xsi: None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _DataElem_T: ... @overload # native type None def DataElement( _value: None, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: None = None, _xsi: None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.NoneElement: ... @overload # native type str def DataElement( _value: str, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: None = None, _xsi: None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.StringElement: ... @overload # native type bool def DataElement( # pyright: ignore[reportOverlappingOverload] _value: bool, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: None = None, _xsi: None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.BoolElement: ... @overload # native type int def DataElement( _value: int, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: None = None, _xsi: None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.IntElement: ... @overload # native type float def DataElement( _value: float, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: None = None, _xsi: None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.FloatElement: ... @overload # pytype None def DataElement( _value: object, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: Literal["NoneType", "none"], _xsi: str | None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.NoneElement: ... @overload # pytype str def DataElement( _value: object, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: Literal["str"], _xsi: str | None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.StringElement: ... @overload # pytype bool def DataElement( _value: object, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: Literal["bool"], _xsi: str | None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.BoolElement: ... @overload # pytype int def DataElement( _value: object, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: Literal["int"], _xsi: str | None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.IntElement: ... @overload # pytype float def DataElement( _value: object, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: Literal["float"], _xsi: str | None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.FloatElement: ... @overload # Generic fallback def DataElement( _value: object, /, - attrib: SupportsLaxedItems[str, _AnyStr] | None = None, + attrib: _AttrMapping | _AttrTuples | None = None, nsmap: _NSMapArg | None = None, *, _pytype: str | None = None, _xsi: str | None = None, - **__attr: _AnyStr, + **__attr: _AttrVal, ) -> _e.ObjectifiedElement: """Create a new element from a Python value and XML attributes taken from keyword arguments or a dictionary passed as second argument. @@ -269,7 +279,7 @@ class _OEMakerCallProtocol(Protocol): | dict[str, Any] | _OEMakerCallProtocol | None, - **_attrib: _AnyStr, + **_attrib: _AttrVal, ) -> _e.ObjectifiedElement: ... class ElementMaker: @@ -333,7 +343,7 @@ class ElementMaker: | dict[str, Any] | _OEMakerCallProtocol | None, - **_attrib: _AnyStr, + **_attrib: _AttrVal, ) -> _e.ObjectifiedElement: ... # __getattr__ here is special. ElementMaker supports using any # attribute name as tag, which is sort of like a functools.partial