File: arguments.py

package info (click to toggle)
python-xmlschema 4.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 5,208 kB
  • sloc: python: 39,174; xml: 1,282; makefile: 36
file content (162 lines) | stat: -rw-r--r-- 5,525 bytes parent folder | download
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