1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
#
# Copyright (c), 2016-2024, SISSA (International School for Advanced Studies).
# All rights reserved.
# This file is distributed under the terms of the MIT License.
# See the file 'LICENSE' in the root directory of the present
# distribution, or http://opensource.org/licenses/MIT.
#
# @author Davide Brunato <brunato@sissa.it>
#
import io
import os
from collections.abc import MutableMapping
from functools import partial
from pathlib import Path
from typing import cast, overload, Any, Optional, Type, TYPE_CHECKING, Union
from urllib.request import OpenerDirector
from xml.etree import ElementTree
from xmlschema.aliases import XMLSourceType, UriMapperType, IterParseType
from xmlschema.exceptions import XMLSchemaValueError
from xmlschema.translation import gettext as _
from xmlschema.utils.etree import is_etree_element, is_etree_document
from xmlschema.utils.misc import is_subclass
from xmlschema.utils.streams import is_file_object
from xmlschema.utils.urls import is_url
from xmlschema.utils.descriptors import Argument, ChoiceArgument, ValueArgument
from xmlschema.xpath import ElementSelector
if TYPE_CHECKING:
from .xml_resource import XMLResource
DEFUSE_MODES = frozenset(('never', 'remote', 'nonlocal', 'always'))
SECURITY_MODES = frozenset(('all', 'remote', 'local', 'sandbox', 'none'))
class SourceArgument(Argument[XMLSourceType]):
"""The XML data source."""
def __init__(self) -> None:
super().__init__(
types=(str, bytes, Path, io.StringIO, io.BytesIO),
validators=(is_file_object, is_etree_element, is_etree_document)
)
def validated_value(self, value: Any) -> XMLSourceType:
value = super().validated_value(value)
if is_etree_document(value):
if value.getroot() is None:
raise XMLSchemaValueError(_("source XML document is empty"))
return cast(XMLSourceType, value)
class BaseUrlArgument(Argument[Optional[str]]):
"""The effective base URL used for completing relative locations."""
def __init__(self) -> None:
super().__init__((str, bytes, Path))
@overload
def __get__(self, instance: None, owner: Type['XMLResource']) \
-> 'BaseUrlArgument': ...
@overload
def __get__(self, instance: 'XMLResource', owner: Type['XMLResource']) \
-> Optional[str]: ...
def __get__(self, instance: Optional['XMLResource'], owner: Type['XMLResource']) \
-> Union['BaseUrlArgument', Optional[str]]:
if instance is None:
return self
if instance.url is not None:
return os.path.dirname(instance.url)
return self.validated_value(getattr(instance, self._private_name))
def validated_value(self, value: Any) -> Optional[str]:
value = super().validated_value(value)
if value is None:
return None
elif not is_url(value):
msg = _("invalid value {!r} for argument {!r}")
raise XMLSchemaValueError(msg.format(value, self._name))
elif isinstance(value, str):
return value
elif isinstance(value, bytes):
return value.decode()
else:
return str(value)
class AllowArgument(ChoiceArgument[str]):
"""The security mode for accessing resource locations."""
def __init__(self) -> None:
super().__init__(str, SECURITY_MODES)
class DefuseArgument(ChoiceArgument[str]):
"""When to defuse XML data."""
def __init__(self) -> None:
super().__init__(str, DEFUSE_MODES)
class TimeoutArgument(ValueArgument[int]):
"""The timeout in seconds for accessing remote resources."""
def __init__(self) -> None:
super().__init__(int, min_value=1)
class LazyArgument(ValueArgument[Union[bool, int]]):
"""Defines if the XML resource is lazy."""
def __init__(self) -> None:
super().__init__((bool, int), 0)
class ThinLazyArgument(ValueArgument[bool]):
"""Defines if the resource is lazy and thin."""
def __init__(self) -> None:
super().__init__(bool)
class FastFindArgument(ValueArgument[bool]):
"""Defines if to use resource uses the `xml.etree.ElementPath.iterfind()` API."""
def __init__(self) -> None:
super().__init__(bool)
class UriMapperArgument(Argument[Optional[UriMapperType]]):
"""The optional URI mapper argument for relocating addressed resources."""
def __init__(self) -> None:
super().__init__(MutableMapping, (callable,))
class OpenerArgument(Argument[Optional[OpenerDirector]]):
def __init__(self) -> None:
super().__init__(OpenerDirector)
class IterParseArgument(Argument[Optional[IterParseType]]):
def __init__(self) -> None:
super().__init__(validators=(callable,))
def validated_value(self, value: Any) -> IterParseType:
value = super().validated_value(value)
if value is not None:
return cast(IterParseType, value)
return ElementTree.iterparse
is_selector_subclass = partial[bool](is_subclass, cls=ElementSelector)
class SelectorArgument(Argument[Optional[type[ElementSelector]]]):
"""Defines if the resource is loaded only for a specific path."""
def __init__(self) -> None:
super().__init__(validators=(is_selector_subclass,))
def validated_value(self, value: Any) -> type[ElementSelector]:
value = super().validated_value(value)
if value is not None:
return cast(type[ElementSelector], value)
return ElementSelector
|