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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ For the purpose of determining breaking changes:

- Update license metadata as per [PEP 639](https://peps.python.org/pep-0639)
- Add tests for PyPy 3.11
- Upgrade dev dependencies

## [1.1.0] - 2024-10-10

Expand Down
10 changes: 5 additions & 5 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
-e .

# build
build==1.2.2
build==1.3.0

# docs
mkdocs==1.6.1

# lint
ruff==0.6.9
ruff==0.14.1

# tests
pytest==8.3.3
pytest-cov==5.0.0
pytest==8.4.2
pytest-cov==7.0.0

# type
mypy==1.11.2
mypy==1.18.2
4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ ignore_missing_imports = false
follow_imports = "normal"
mypy_path = "stubs"
# Platform configuration
python_version = 3.13
python_version = "3.13"
# Disallow dynamic typing
disallow_any_unimported = true
disallow_any_decorated = true
Expand Down Expand Up @@ -143,8 +143,6 @@ target-version = "py39"
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN101",
"ANN102",
"C901",
"COM812",
"D",
Expand Down
4 changes: 2 additions & 2 deletions src/bigxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"HandlerTypeHelper",
"Parser",
"Streamable",
"xml_handle_element",
"xml_handle_text",
"XMLElement",
"XMLElementAttributes",
"XMLText",
"xml_handle_element",
"xml_handle_text",
)
2 changes: 1 addition & 1 deletion src/bigxml/handle_mgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def iter_from(
],
) -> Iterator[Union["XMLElement", T]]: ...

@overload # type: ignore[misc]
@overload
def iter_from(
self,
*handlers: Any, # noqa: ANN401
Expand Down
2 changes: 1 addition & 1 deletion src/bigxml/handler_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def handle(
return None

@staticmethod
def _handle_from_class( # type: ignore[misc]
def _handle_from_class(
klass: type[Any], node: Union["XMLElement", "XMLText"]
) -> Optional[Iterable[object]]:
# instantiate class
Expand Down
9 changes: 4 additions & 5 deletions src/bigxml/handler_marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
class ___xml_handle_xxx_wrapped(Protocol[T_co]): # noqa: N801
# wrapper for classes
@overload
def __call__( # type: ignore[misc]
def __call__(
self,
obj: K,
) -> K: ...
Expand Down Expand Up @@ -49,22 +49,21 @@ def wrapper(obj: F) -> F:
if isinstance(markable, staticmethod):
# staticmethod(xml_handle_element(...)) works as expected
# xml_handle_element(staticmethod(...)) needs special care
markable = cast(F, markable.__func__)
markable = cast("F", markable.__func__)

add_mark(markable, tuple(args))

return obj

return cast(
___xml_handle_xxx_wrapped[XMLElement],
"___xml_handle_xxx_wrapped[XMLElement]",
wrapper,
)


# @xml_handle_text (for classes)
@overload
def xml_handle_text(obj: K, /) -> K: # type: ignore[misc]
...
def xml_handle_text(obj: K, /) -> K: ...


# @xml_handle_text (for functions)
Expand Down
6 changes: 3 additions & 3 deletions src/bigxml/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def _flatten_stream(stream: Streamable) -> Generator[Optional[memoryview], int,
# buffer protocol (bytes, etc.)
try:
# we try-except instead of isinstance(stream, Buffer) for compatibility reasons
yield memoryview(cast(Buffer, stream))
yield memoryview(cast("Buffer", stream))
return # noqa: TRY300
except TypeError:
pass
Expand All @@ -28,7 +28,7 @@ def _flatten_stream(stream: Streamable) -> Generator[Optional[memoryview], int,
if hasattr(stream, "read"):
while True:
size = yield None
data = cast(SupportsRead[Any], stream).read(size)
data = cast("SupportsRead[Any]", stream).read(size)
if not data:
break # EOF
try:
Expand Down Expand Up @@ -57,7 +57,7 @@ def _flatten_stream(stream: Streamable) -> Generator[Optional[memoryview], int,

# stream iterator (recursive)
try:
substreams = iter(cast(Iterable[Streamable], stream))
substreams = iter(cast("Iterable[Streamable]", stream))
except TypeError:
# other types not supported
raise TypeError(f"Invalid stream type: {type(stream).__name__}") from None
Expand Down
2 changes: 1 addition & 1 deletion src/bigxml/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __next__(self) -> T:
def extract_namespace_name(name: str) -> tuple[str, str]:
match = _EXTRACT_NAMESPACE_REGEX.match(name)
if match:
return cast(tuple[str, str], match.groups())
return cast("tuple[str, str]", match.groups())
return ("", name)


Expand Down
5 changes: 3 additions & 2 deletions stubs/defusedxml/ElementTree.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# ruff: noqa: FBT001
# note: only used items are defined here, with used typing

from typing import Iterator, Optional, Protocol, Sequence, TypeVar
from collections.abc import Iterator, Sequence
from typing import Optional, Protocol, TypeVar
from xml.etree.ElementTree import Element, ParseError

_T_co = TypeVar("_T_co", covariant=True)
Expand All @@ -19,4 +20,4 @@ def iterparse(

class DefusedXmlException(ValueError): ... # noqa: N818

__all__ = ("DefusedXmlException", "Element", "iterparse", "ParseError")
__all__ = ("DefusedXmlException", "Element", "ParseError", "iterparse")
4 changes: 3 additions & 1 deletion tests/integration/test_namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ def handle_bbb(node: XMLElement) -> Iterator[tuple[str, str, str]]:
)
# note that a warning is emitted if there are attributes with various
# namespaces but none without namespace
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning, match="Several alternatives for attribute name 'yyy'."
):
# current implementation uses "first" attribute in that case
# but you should not rely on it and specify the namespace to use
yield ("bbb", "yyy default", node.attributes["yyy"])
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_ram_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@pytest.fixture
def ram_usage() -> Iterator[Callable[[], float]]:
try:
import tracemalloc
import tracemalloc # noqa: PLC0415
except ImportError: # e.g. PyPy
pytest.skip("tracemalloc module not available")

Expand Down
12 changes: 5 additions & 7 deletions tests/integration/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,11 @@ def test_external_entities(xml: bytes, msg: str) -> None:


def test_insecurely_allow_entities() -> None:
xml = (
b"<!DOCTYPE foobar [\n"
b' <!ENTITY Omega "&#937;">\n'
b"]>\n"
b"<root>&Omega;</root>\n"
)
with pytest.warns(UserWarning):
xml = b'<!DOCTYPE foobar [<!ENTITY Omega "&#937;">]>\n<root>&Omega;</root>\n'
with pytest.warns(
UserWarning,
match="Using 'insecurely_allow_entities' makes your code vulnerable to some XML attacks.",
):
parser = Parser(xml, insecurely_allow_entities=True)
value = parser.return_from(handler_get_text)
assert value == "Ω"
10 changes: 7 additions & 3 deletions tests/unit/test_handler_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def create_nodes(
for node_name in path:
# the cast below is wrong but makes our life easier
# plus that case is kind of tested in tests below
parents = tuple(cast(list[XMLElement], nodes))
parents = tuple(cast("list[XMLElement]", nodes))
if node_name == ":text:":
node: Union[XMLElement, XMLText] = XMLText(text="text", parents=parents)
else:
Expand Down Expand Up @@ -631,7 +631,9 @@ def handle0(node: XMLElement) -> Iterable[tuple[str, XMLElement]]:

nodes = create_nodes("x", "a")
handler = create_handler(Handler)
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning, match="Items were yielded by some sub-handler of class Handler."
):
items = list(handler(nodes[0]))
assert len(items) == 1
assert isinstance(items[0], Handler)
Expand All @@ -651,7 +653,9 @@ def xml_handler() -> Iterable[tuple[str, None]]:

nodes = create_nodes("x", "a")
handler = create_handler(Handler)
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning, match="Items were yielded by some sub-handler of class Handler."
):
assert list(handler(nodes[0])) == [("end", None)]


Expand Down
6 changes: 4 additions & 2 deletions tests/unit/test_nodes_element_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@
def test_get_without_namespace(key: str, value: str, should_warn: bool) -> None:
context: AbstractContextManager[object] = nullcontext()
if should_warn:
context = pytest.warns(UserWarning)
context = pytest.warns(
UserWarning, match=f"Several alternatives for attribute name '{key}'."
)
with context:
assert XML_ELEMENT_ATTRIBUTES[key] == value

Expand Down Expand Up @@ -89,5 +91,5 @@ def test_eq() -> None:

@pytest.mark.parametrize("key", [r"{aaa", r"{aaa}{bbb"])
def test_invalid_key(key: str) -> None:
with pytest.raises(ValueError, match="Invalid key: '.*'"):
with pytest.raises(ValueError, match=r"Invalid key: '.*'"):
XMLElementAttributes({key: "foo"})
5 changes: 4 additions & 1 deletion tests/unit/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,10 @@ def root_handler(
) -> Iterator[tuple[str, Union[XMLElement, XMLText]]]:
yield from node.iter_from(handler)

with pytest.warns(UserWarning):
with pytest.warns(
UserWarning,
match="Using 'insecurely_allow_entities' makes your code vulnerable to some XML attacks.",
):
parser = Parser(xml, insecurely_allow_entities=True)

assert list(parser.iter_from(root_handler)) == [("handler-yield-0", text_pi_node)]
2 changes: 1 addition & 1 deletion tests/unit/test_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def __repr__() -> str:
],
)
def test_types_invalid(stream: object, err_message: str) -> None:
stream = StreamChain(cast(Streamable, stream))
stream = StreamChain(cast("Streamable", stream))
with pytest.raises(TypeError) as excinfo:
stream.read(42)

Expand Down
6 changes: 2 additions & 4 deletions tests/unit/test_utils_get_mandatory_params.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# ruff: noqa: ARG001

from typing import Callable

import pytest
Expand Down Expand Up @@ -57,9 +55,9 @@ def fct6(
(dict, ()),
],
ids=lambda x: str(x.__name__ if callable(x) else x),
) # type: ignore[misc]
)
# Typing note: see https://github.com/python/mypy/issues/13436
def test_mandatory_params(
def test_mandatory_params( # type: ignore[misc]
fct: Callable[..., object], expected: tuple[str, ...]
) -> None:
assert get_mandatory_params(fct) == expected
12 changes: 6 additions & 6 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ envlist =
package = wheel
wheel_build_env = .pkg # reuse same wheel across envs
deps =
pytest==8.3.3
pytest-cov==5.0.0
pytest==8.4.2
pytest-cov==7.0.0
passenv = PY_COLORS
setenv =
COVERAGE_FILE = {toxworkdir}/{envname}/.coverage
Expand All @@ -22,7 +22,7 @@ commands =
[testenv:build]
skip_install = true
deps =
build==1.2.2
build==1.3.0
commands =
python -m build

Expand All @@ -35,15 +35,15 @@ commands =

[testenv:lint]
deps =
ruff==0.6.9
ruff==0.14.1
commands =
ruff check src docs tests
ruff format --check src docs tests

[testenv:type]
deps =
mypy==1.11.2
pytest==8.3.3 # for typing
mypy==1.18.2
pytest==8.4.2 # for typing
commands =
mypy
mypy --explicit-package-bases docs tests