Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ jobs:
strategy:
matrix:
python:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
- "pypy-3.9"
- "pypy-3.10"
- "pypy-3.11"
steps:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ For the purpose of determining breaking changes:
### :house: Internal

- Update license metadata as per [PEP 639](https://peps.python.org/pep-0639)
- End of Python 3.9 support
- Add tests for CPython 3.14 and PyPy 3.11
- Use CPython 3.14 for misc. tests
- Upgrade dev dependencies
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -22,7 +21,7 @@ classifiers = [
"Topic :: Utilities",
"Topic :: Text Processing :: Markup :: XML",
]
requires-python = ">=3.9"
requires-python = ">=3.10"
dependencies = [
"defusedxml>=0.7.1",
"typing-extensions>=4.6.0 ; python_version<'3.12'",
Expand Down Expand Up @@ -139,7 +138,7 @@ testpaths = ["docs", "tests"]

[tool.ruff]
src = ["src"]
target-version = "py39"
target-version = "py310"

[tool.ruff.lint]
select = ["ALL"]
Expand Down
164 changes: 70 additions & 94 deletions src/bigxml/handle_mgr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from collections.abc import Iterable, Iterator
from collections.abc import Callable, Iterable, Iterator
import sys
from typing import TYPE_CHECKING, Any, Callable, Optional, Union, overload
from typing import TYPE_CHECKING, Any, Optional, Union, overload

from bigxml.handler_creator import create_handler
from bigxml.typing import (
Expand All @@ -20,12 +20,12 @@


class HandleMgr:
_handle: Optional[
_handle: (
Callable[
[Callable[[Union["XMLElement", "XMLText"]], Iterator[Any]]],
Iterator[Any],
[Callable[[Union["XMLElement", "XMLText"]], Iterator[Any]]], Iterator[Any]
]
] = None
| None
) = None

# iter_from

Expand All @@ -37,67 +37,55 @@ def iter_from(
@overload
def iter_from(
self,
*handlers: Union[
str,
list[str],
tuple[str, ...],
],
*handlers: str | list[str] | tuple[str, ...],
) -> Iterator["XMLElement"]: ...

@overload
def iter_from(
self,
*handlers: Union[
Callable[[Union["XMLElement", "XMLText"]], Optional[Iterable[T]]],
ClassHandlerWithCustomWrapper0[T],
ClassHandlerWithCustomWrapper1[T],
type[ClassHandlerWithCustomWrapper0[T]],
type[ClassHandlerWithCustomWrapper1[T]],
],
*handlers: Callable[[Union["XMLElement", "XMLText"]], Iterable[T] | None]
| ClassHandlerWithCustomWrapper0[T]
| ClassHandlerWithCustomWrapper1[T]
| type[ClassHandlerWithCustomWrapper0[T]]
| type[ClassHandlerWithCustomWrapper1[T]],
) -> Iterator[T]: ...

@overload
def iter_from(
self,
*handlers: Union[
Callable[[Union["XMLElement", "XMLText"]], Optional[Iterable[T]]],
ClassHandlerWithCustomWrapper0[T],
ClassHandlerWithCustomWrapper1[T],
type[ClassHandlerWithCustomWrapper0[T]],
type[ClassHandlerWithCustomWrapper1[T]],
type[T],
],
*handlers: Callable[[Union["XMLElement", "XMLText"]], Iterable[T] | None]
| ClassHandlerWithCustomWrapper0[T]
| ClassHandlerWithCustomWrapper1[T]
| type[ClassHandlerWithCustomWrapper0[T]]
| type[ClassHandlerWithCustomWrapper1[T]]
| type[T],
) -> Iterator[T]: ...

@overload
def iter_from(
self,
*handlers: Union[
Callable[[Union["XMLElement", "XMLText"]], Optional[Iterable[T]]],
ClassHandlerWithCustomWrapper0[T],
ClassHandlerWithCustomWrapper1[T],
type[ClassHandlerWithCustomWrapper0[T]],
type[ClassHandlerWithCustomWrapper1[T]],
str,
list[str],
tuple[str, ...],
],
*handlers: Callable[[Union["XMLElement", "XMLText"]], Iterable[T] | None]
| ClassHandlerWithCustomWrapper0[T]
| ClassHandlerWithCustomWrapper1[T]
| type[ClassHandlerWithCustomWrapper0[T]]
| type[ClassHandlerWithCustomWrapper1[T]]
| str
| list[str]
| tuple[str, ...],
) -> Iterator[Union["XMLElement", T]]: ...

@overload
def iter_from(
self,
*handlers: Union[
Callable[[Union["XMLElement", "XMLText"]], Optional[Iterable[T]]],
ClassHandlerWithCustomWrapper0[T],
ClassHandlerWithCustomWrapper1[T],
type[ClassHandlerWithCustomWrapper0[T]],
type[ClassHandlerWithCustomWrapper1[T]],
type[T],
str,
list[str],
tuple[str, ...],
],
*handlers: Callable[[Union["XMLElement", "XMLText"]], Iterable[T] | None]
| ClassHandlerWithCustomWrapper0[T]
| ClassHandlerWithCustomWrapper1[T]
| type[ClassHandlerWithCustomWrapper0[T]]
| type[ClassHandlerWithCustomWrapper1[T]]
| type[T]
| str
| list[str]
| tuple[str, ...],
) -> Iterator[Union["XMLElement", T]]: ...

@overload
Expand All @@ -122,74 +110,62 @@ def return_from(
@overload
def return_from(
self,
*handlers: Union[
str,
list[str],
tuple[str, ...],
],
*handlers: str | list[str] | tuple[str, ...],
) -> Optional["XMLElement"]: ...

@overload
def return_from(
self,
*handlers: Union[
Callable[[Union["XMLElement", "XMLText"]], Optional[Iterable[T]]],
ClassHandlerWithCustomWrapper0[T],
ClassHandlerWithCustomWrapper1[T],
type[ClassHandlerWithCustomWrapper0[T]],
type[ClassHandlerWithCustomWrapper1[T]],
],
) -> Optional[T]: ...
*handlers: Callable[[Union["XMLElement", "XMLText"]], Iterable[T] | None]
| ClassHandlerWithCustomWrapper0[T]
| ClassHandlerWithCustomWrapper1[T]
| type[ClassHandlerWithCustomWrapper0[T]]
| type[ClassHandlerWithCustomWrapper1[T]],
) -> T | None: ...

@overload
def return_from(
self,
*handlers: Union[
Callable[[Union["XMLElement", "XMLText"]], Optional[Iterable[T]]],
ClassHandlerWithCustomWrapper0[T],
ClassHandlerWithCustomWrapper1[T],
type[ClassHandlerWithCustomWrapper0[T]],
type[ClassHandlerWithCustomWrapper1[T]],
type[T],
],
) -> Optional[T]: ...
*handlers: Callable[[Union["XMLElement", "XMLText"]], Iterable[T] | None]
| ClassHandlerWithCustomWrapper0[T]
| ClassHandlerWithCustomWrapper1[T]
| type[ClassHandlerWithCustomWrapper0[T]]
| type[ClassHandlerWithCustomWrapper1[T]]
| type[T],
) -> T | None: ...

@overload
def return_from(
self,
*handlers: Union[
Callable[[Union["XMLElement", "XMLText"]], Optional[Iterable[T]]],
ClassHandlerWithCustomWrapper0[T],
ClassHandlerWithCustomWrapper1[T],
type[ClassHandlerWithCustomWrapper0[T]],
type[ClassHandlerWithCustomWrapper1[T]],
str,
list[str],
tuple[str, ...],
],
) -> Optional[Union["XMLElement", T]]: ...
*handlers: Callable[[Union["XMLElement", "XMLText"]], Iterable[T] | None]
| ClassHandlerWithCustomWrapper0[T]
| ClassHandlerWithCustomWrapper1[T]
| type[ClassHandlerWithCustomWrapper0[T]]
| type[ClassHandlerWithCustomWrapper1[T]]
| str
| list[str]
| tuple[str, ...],
) -> Union["XMLElement", T] | None: ...

@overload
def return_from(
self,
*handlers: Union[
Callable[[Union["XMLElement", "XMLText"]], Optional[Iterable[T]]],
ClassHandlerWithCustomWrapper0[T],
ClassHandlerWithCustomWrapper1[T],
type[ClassHandlerWithCustomWrapper0[T]],
type[ClassHandlerWithCustomWrapper1[T]],
str,
list[str],
tuple[str, ...],
type[T],
],
) -> Optional[Union["XMLElement", T]]: ...
*handlers: Callable[[Union["XMLElement", "XMLText"]], Iterable[T] | None]
| ClassHandlerWithCustomWrapper0[T]
| ClassHandlerWithCustomWrapper1[T]
| type[ClassHandlerWithCustomWrapper0[T]]
| type[ClassHandlerWithCustomWrapper1[T]]
| str
| list[str]
| tuple[str, ...]
| type[T],
) -> Union["XMLElement", T] | None: ...

@overload
def return_from(
self,
*handlers: object,
) -> Optional[object]: ...
) -> object | None: ...

def return_from(self, *handlers: Any) -> Optional[Any]:
def return_from(self, *handlers: Any) -> Any | None:
return last_item_or_none(self.iter_from(*handlers))
16 changes: 7 additions & 9 deletions src/bigxml/handler_creator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Iterable, Iterator
from collections.abc import Callable, Iterable, Iterator
from dataclasses import is_dataclass
from inspect import getmembers, isclass
from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast
from typing import TYPE_CHECKING, Any, Union, cast
import warnings

from bigxml.marks import get_marks, has_marks
Expand All @@ -27,7 +27,7 @@ def _assert_one_mandatory_param(

def _assert_iterable_or_none(
item: object, klass: type[Any], method_name: str
) -> Optional[Iterable[object]]:
) -> Iterable[object] | None:
if item is None or isinstance(item, Iterable):
return item
raise TypeError(
Expand All @@ -44,7 +44,7 @@ class _HandlerTree:
def __init__(self, path: tuple[str, ...] = ()) -> None:
self.path: tuple[str, ...] = path
self.children: dict[str, _HandlerTree] = {}
self.handler: Optional[Callable[..., Iterable[object]]] = None
self.handler: Callable[..., Iterable[object]] | None = None

def add_handler(
self,
Expand Down Expand Up @@ -111,15 +111,13 @@ def add_handler_callable(
self.handler = handler

@transform_to_iterator
def handle(
self, node: Union["XMLElement", "XMLText"]
) -> Optional[Iterable[object]]:
def handle(self, node: Union["XMLElement", "XMLText"]) -> Iterable[object] | None:
if self.handler:
if isclass(self.handler):
return self._handle_from_class(self.handler, node)
return self.handler(node)

child: Optional[_HandlerTree] = None
child: _HandlerTree | None = None
namespace = getattr(node, "namespace", None)
if namespace is not None:
child = self.children.get(f"{{{namespace}}}{node.name}")
Expand All @@ -137,7 +135,7 @@ def handle(
@staticmethod
def _handle_from_class(
klass: type[Any], node: Union["XMLElement", "XMLText"]
) -> Optional[Iterable[object]]:
) -> Iterable[object] | None:
# instantiate class
init_mandatory_params = get_mandatory_params(klass)
try:
Expand Down
20 changes: 10 additions & 10 deletions src/bigxml/handler_marker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections.abc import Iterable
from typing import Any, Callable, Generic, Optional, Union, cast, overload
from collections.abc import Callable, Iterable
from typing import Any, Generic, cast, overload

from bigxml.marks import add_mark
from bigxml.nodes import XMLElement, XMLText
Expand Down Expand Up @@ -28,15 +28,15 @@ def __call__(
@overload
def __call__(
self,
obj: Callable[[T_co], Optional[Iterable[T]]],
) -> Callable[[Union[XMLElement, XMLText]], Optional[Iterable[T]]]: ...
obj: Callable[[T_co], Iterable[T] | None],
) -> Callable[[XMLElement | XMLText], Iterable[T] | None]: ...

# wrapper for methods
@overload
def __call__(
self,
obj: Callable[[U, T_co], Optional[Iterable[T]]],
) -> Callable[[U, Union[XMLElement, XMLText]], Optional[Iterable[T]]]: ...
obj: Callable[[U, T_co], Iterable[T] | None],
) -> Callable[[U, XMLElement | XMLText], Iterable[T] | None]: ...


def xml_handle_element(*args: str) -> ___xml_handle_xxx_wrapped[XMLElement]:
Expand Down Expand Up @@ -69,15 +69,15 @@ def xml_handle_text(obj: K, /) -> K: ...
# @xml_handle_text (for functions)
@overload
def xml_handle_text(
obj: Callable[[XMLText], Optional[Iterable[T]]], /
) -> Callable[[Union[XMLElement, XMLText]], Optional[Iterable[T]]]: ...
obj: Callable[[XMLText], Iterable[T] | None], /
) -> Callable[[XMLElement | XMLText], Iterable[T] | None]: ...


# @xml_handle_text (for methods)
@overload
def xml_handle_text(
obj: Callable[[U, XMLText], Optional[Iterable[T]]], /
) -> Callable[[U, Union[XMLElement, XMLText]], Optional[Iterable[T]]]: ...
obj: Callable[[U, XMLText], Iterable[T] | None], /
) -> Callable[[U, XMLElement | XMLText], Iterable[T] | None]: ...


# @xml_handle_text(...) (for functions & methods)
Expand Down
Loading