django-gisserver 2.0__py3-none-any.whl → 2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_gisserver-2.0.dist-info → django_gisserver-2.1.dist-info}/METADATA +26 -10
- django_gisserver-2.1.dist-info/RECORD +68 -0
- {django_gisserver-2.0.dist-info → django_gisserver-2.1.dist-info}/WHEEL +1 -1
- gisserver/__init__.py +1 -1
- gisserver/crs.py +401 -0
- gisserver/db.py +71 -5
- gisserver/exceptions.py +106 -2
- gisserver/extensions/functions.py +122 -28
- gisserver/extensions/queries.py +15 -10
- gisserver/features.py +44 -36
- gisserver/geometries.py +64 -306
- gisserver/management/commands/loadgeojson.py +41 -21
- gisserver/operations/base.py +11 -7
- gisserver/operations/wfs20.py +31 -93
- gisserver/output/__init__.py +6 -2
- gisserver/output/base.py +28 -13
- gisserver/output/csv.py +18 -6
- gisserver/output/geojson.py +7 -6
- gisserver/output/gml32.py +43 -23
- gisserver/output/results.py +25 -39
- gisserver/output/utils.py +9 -2
- gisserver/parsers/ast.py +171 -65
- gisserver/parsers/fes20/__init__.py +76 -4
- gisserver/parsers/fes20/expressions.py +97 -27
- gisserver/parsers/fes20/filters.py +9 -6
- gisserver/parsers/fes20/identifiers.py +27 -7
- gisserver/parsers/fes20/lookups.py +8 -6
- gisserver/parsers/fes20/operators.py +101 -49
- gisserver/parsers/fes20/sorting.py +14 -6
- gisserver/parsers/gml/__init__.py +10 -19
- gisserver/parsers/gml/base.py +32 -14
- gisserver/parsers/gml/geometries.py +48 -21
- gisserver/parsers/ows/kvp.py +10 -2
- gisserver/parsers/ows/requests.py +6 -4
- gisserver/parsers/query.py +6 -2
- gisserver/parsers/values.py +61 -4
- gisserver/parsers/wfs20/__init__.py +2 -0
- gisserver/parsers/wfs20/adhoc.py +25 -17
- gisserver/parsers/wfs20/base.py +12 -7
- gisserver/parsers/wfs20/projection.py +3 -3
- gisserver/parsers/wfs20/requests.py +1 -0
- gisserver/parsers/wfs20/stored.py +3 -2
- gisserver/parsers/xml.py +12 -0
- gisserver/projection.py +17 -7
- gisserver/static/gisserver/index.css +8 -3
- gisserver/templates/gisserver/base.html +12 -0
- gisserver/templates/gisserver/index.html +9 -15
- gisserver/templates/gisserver/service_description.html +12 -6
- gisserver/templates/gisserver/wfs/feature_field.html +1 -1
- gisserver/templates/gisserver/wfs/feature_type.html +35 -13
- gisserver/types.py +150 -81
- gisserver/views.py +47 -24
- django_gisserver-2.0.dist-info/RECORD +0 -66
- {django_gisserver-2.0.dist-info → django_gisserver-2.1.dist-info/licenses}/LICENSE +0 -0
- {django_gisserver-2.0.dist-info → django_gisserver-2.1.dist-info}/top_level.txt +0 -0
gisserver/parsers/ast.py
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
By transforming the XML Element nodes into Python objects, most logic naturally follows.
|
|
4
4
|
For example, the FES filter syntax can be processed into objects that build an ORM query.
|
|
5
5
|
|
|
6
|
-
Python classes can inherit :class:`
|
|
6
|
+
Python classes can inherit :class:`AstNode` and register themselves as the parser/handler
|
|
7
7
|
for a given tag. Both normal Python classes and dataclass work,
|
|
8
|
-
as long as it has an :meth:`
|
|
8
|
+
as long as it has an :meth:`AstNode.from_xml` class method.
|
|
9
9
|
The custom `from_xml()` method should copy the XML data into local attributes.
|
|
10
10
|
|
|
11
11
|
Next, when :meth:`TagRegistry.node_from_xml` is called,
|
|
12
12
|
it will detect which class the XML Element refers to and initialize it using the ``from_xml()`` call.
|
|
13
|
-
As convenience, calling :meth:`
|
|
14
|
-
initialize the right subclass and initialize it.
|
|
13
|
+
As convenience, calling a :meth:`AstNode.child_from_xml`
|
|
14
|
+
on a subclass will also initialize the right subclass and initialize it.
|
|
15
15
|
|
|
16
|
-
Since clients may not follow the desired XML schema, and make mistakes,
|
|
16
|
+
Since clients may not follow the desired XML schema, and make mistakes, we should guard against
|
|
17
17
|
creating an invalid Abstract Syntax Tree. When using :meth:`TagRegistry.node_from_xml`,
|
|
18
18
|
the allowed child types can also be provided, preventing invalid child elements.
|
|
19
19
|
Furthermore, to support the creation of ``from_xml()`` methods, the :func:`expect_tag`,
|
|
@@ -27,15 +27,17 @@ from __future__ import annotations
|
|
|
27
27
|
from collections.abc import Iterable
|
|
28
28
|
from enum import Enum
|
|
29
29
|
from functools import wraps
|
|
30
|
-
from itertools import chain
|
|
31
30
|
from typing import TypeVar
|
|
32
31
|
from xml.etree.ElementTree import QName
|
|
33
32
|
|
|
34
|
-
from
|
|
33
|
+
from django.utils.functional import classproperty
|
|
34
|
+
|
|
35
|
+
from gisserver.exceptions import InvalidXmlElement, XmlElementNotSupported
|
|
36
|
+
from gisserver.parsers.xml import NSElement, xmlns
|
|
35
37
|
|
|
36
38
|
__all__ = (
|
|
37
39
|
"TagNameEnum",
|
|
38
|
-
"
|
|
40
|
+
"AstNode",
|
|
39
41
|
"TagRegistry",
|
|
40
42
|
"tag_registry",
|
|
41
43
|
"expect_children",
|
|
@@ -43,11 +45,9 @@ __all__ = (
|
|
|
43
45
|
"expect_no_children",
|
|
44
46
|
)
|
|
45
47
|
|
|
46
|
-
from gisserver.parsers.xml import NSElement, xmlns
|
|
47
|
-
|
|
48
48
|
|
|
49
49
|
class TagNameEnum(Enum):
|
|
50
|
-
"""An
|
|
50
|
+
"""An base clas for enumerations of XML tag names.
|
|
51
51
|
|
|
52
52
|
All enumerations that represent tag names inherit from this.
|
|
53
53
|
Each member name should be exactly the XML tag that it refers to.
|
|
@@ -55,7 +55,12 @@ class TagNameEnum(Enum):
|
|
|
55
55
|
|
|
56
56
|
@classmethod
|
|
57
57
|
def from_xml(cls, element: NSElement):
|
|
58
|
-
"""Cast the element tag name into the enum member
|
|
58
|
+
"""Cast the element tag name into the enum member.
|
|
59
|
+
|
|
60
|
+
This translates the element name
|
|
61
|
+
such as``{http://www.opengis.net/fes/2.0}PropertyIsEqualTo``
|
|
62
|
+
into a ``PropertyIsEqualTo`` member.
|
|
63
|
+
"""
|
|
59
64
|
tag_name = element.tag
|
|
60
65
|
if tag_name.startswith("{"):
|
|
61
66
|
# Split the element tag into the namespace and local name.
|
|
@@ -74,7 +79,7 @@ class TagNameEnum(Enum):
|
|
|
74
79
|
return f"{self.__class__.__name__}.{self.name}"
|
|
75
80
|
|
|
76
81
|
|
|
77
|
-
class
|
|
82
|
+
class AstNode:
|
|
78
83
|
"""The base node for all classes that represent an XML tag.
|
|
79
84
|
|
|
80
85
|
All subclasses of this class build an Abstract Syntax Tree (AST)
|
|
@@ -86,23 +91,29 @@ class BaseNode:
|
|
|
86
91
|
"""
|
|
87
92
|
|
|
88
93
|
xml_ns: xmlns | str | None = None
|
|
89
|
-
|
|
94
|
+
|
|
95
|
+
_xml_tags = []
|
|
96
|
+
|
|
97
|
+
@classproperty
|
|
98
|
+
def xml_name(cls) -> str:
|
|
99
|
+
"""Tell the default tag by which this class is registered"""
|
|
100
|
+
return cls._xml_tags[0]
|
|
90
101
|
|
|
91
102
|
def __init_subclass__(cls):
|
|
92
103
|
# Each class level has a fresh list of supported child tags.
|
|
93
|
-
cls.
|
|
104
|
+
cls._xml_tags = []
|
|
94
105
|
|
|
95
106
|
@classmethod
|
|
96
107
|
def from_xml(cls, element: NSElement):
|
|
97
108
|
"""Initialize this Python class from the data of the corresponding XML tag.
|
|
98
|
-
Each subclass overrides this to implement the
|
|
109
|
+
Each subclass overrides this to implement the XML parsing of that particular XML tag.
|
|
99
110
|
"""
|
|
100
111
|
raise NotImplementedError(
|
|
101
112
|
f"{cls.__name__}.from_xml() is not implemented to parse <{element.tag}>"
|
|
102
113
|
)
|
|
103
114
|
|
|
104
115
|
@classmethod
|
|
105
|
-
def child_from_xml(cls, element: NSElement) ->
|
|
116
|
+
def child_from_xml(cls, element: NSElement) -> AstNode:
|
|
106
117
|
"""Parse the element, returning the correct subclass of this tag.
|
|
107
118
|
|
|
108
119
|
When ``Expression.child_from_xml(some_node)`` is given, it may
|
|
@@ -112,12 +123,23 @@ class BaseNode:
|
|
|
112
123
|
return sub_class.from_xml(element)
|
|
113
124
|
|
|
114
125
|
@classmethod
|
|
115
|
-
def get_tag_names(cls) ->
|
|
126
|
+
def get_tag_names(cls) -> list[str]:
|
|
116
127
|
"""Provide all known XMl tags that this code can parse."""
|
|
117
|
-
|
|
128
|
+
try:
|
|
129
|
+
# Because a cached class property is hard to build
|
|
130
|
+
return _KNOWN_TAG_NAMES[cls]
|
|
131
|
+
except KeyError:
|
|
132
|
+
all_xml_tags = cls._xml_tags.copy()
|
|
133
|
+
for sub_cls in cls.__subclasses__():
|
|
134
|
+
all_xml_tags.extend(sub_cls.get_tag_names())
|
|
135
|
+
_KNOWN_TAG_NAMES[cls] = all_xml_tags
|
|
136
|
+
return all_xml_tags
|
|
137
|
+
|
|
118
138
|
|
|
139
|
+
_KNOWN_TAG_NAMES = {}
|
|
119
140
|
|
|
120
|
-
|
|
141
|
+
BaseNode = AstNode # keep old name around
|
|
142
|
+
A = TypeVar("A", bound=AstNode)
|
|
121
143
|
|
|
122
144
|
|
|
123
145
|
class TagRegistry:
|
|
@@ -126,7 +148,7 @@ class TagRegistry:
|
|
|
126
148
|
The same class can be registered multiple times for different tag names.
|
|
127
149
|
"""
|
|
128
150
|
|
|
129
|
-
parsers: dict[str, type[
|
|
151
|
+
parsers: dict[str, type[AstNode]]
|
|
130
152
|
|
|
131
153
|
def __init__(self):
|
|
132
154
|
self.parsers = {}
|
|
@@ -141,9 +163,11 @@ class TagRegistry:
|
|
|
141
163
|
|
|
142
164
|
Usage:
|
|
143
165
|
|
|
166
|
+
.. code-block:: python
|
|
167
|
+
|
|
144
168
|
@dataclass
|
|
145
169
|
@tag_registry.register()
|
|
146
|
-
class SomeXmlTag(
|
|
170
|
+
class SomeXmlTag(AstNode):
|
|
147
171
|
xml_ns = FES
|
|
148
172
|
|
|
149
173
|
@classmethod
|
|
@@ -159,7 +183,7 @@ class TagRegistry:
|
|
|
159
183
|
each member name is assumed to be an XML tag name.
|
|
160
184
|
"""
|
|
161
185
|
|
|
162
|
-
def _dec(node_class: type[
|
|
186
|
+
def _dec(node_class: type[AstNode]) -> type[AstNode]:
|
|
163
187
|
if tag is None or isinstance(tag, str):
|
|
164
188
|
# Single tag name for the class.
|
|
165
189
|
self._register_tag_parser(
|
|
@@ -170,7 +194,9 @@ class TagRegistry:
|
|
|
170
194
|
# Note using __members__, not _member_names_.
|
|
171
195
|
# The latter will skip aliased items (like BBOX/Within).
|
|
172
196
|
for member_name in tag.__members__:
|
|
173
|
-
self._register_tag_parser(
|
|
197
|
+
self._register_tag_parser(
|
|
198
|
+
node_class, tag=member_name, namespace=namespace, hidden=hidden
|
|
199
|
+
)
|
|
174
200
|
else:
|
|
175
201
|
raise TypeError("tag type incorrect")
|
|
176
202
|
|
|
@@ -180,14 +206,14 @@ class TagRegistry:
|
|
|
180
206
|
|
|
181
207
|
def _register_tag_parser(
|
|
182
208
|
self,
|
|
183
|
-
node_class: type[
|
|
209
|
+
node_class: type[AstNode],
|
|
184
210
|
tag: str,
|
|
185
211
|
namespace: xmlns | str | None = None,
|
|
186
212
|
hidden: bool = False,
|
|
187
213
|
):
|
|
188
214
|
"""Register a Python (data) class as parser for an XML node."""
|
|
189
|
-
if not issubclass(node_class,
|
|
190
|
-
raise TypeError(f"{node_class} must be a subclass of
|
|
215
|
+
if not issubclass(node_class, AstNode):
|
|
216
|
+
raise TypeError(f"{node_class} must be a subclass of AstNode")
|
|
191
217
|
|
|
192
218
|
if namespace is None and node_class.xml_ns is None:
|
|
193
219
|
raise RuntimeError(
|
|
@@ -202,49 +228,46 @@ class TagRegistry:
|
|
|
202
228
|
|
|
203
229
|
self.parsers[xml_name] = node_class # Track this parser to resolve the tag.
|
|
204
230
|
if not hidden:
|
|
205
|
-
node_class.
|
|
231
|
+
node_class._xml_tags.append(xml_name) # Allow fetching all names later
|
|
206
232
|
|
|
207
|
-
def node_from_xml(
|
|
208
|
-
|
|
209
|
-
) -> BN:
|
|
210
|
-
"""Find the ``BaseNode`` subclass that corresponds to the given XML element,
|
|
233
|
+
def node_from_xml(self, element: NSElement, allowed_types: tuple[type[A]] | None = None) -> A:
|
|
234
|
+
"""Find the ``AstNode`` subclass that corresponds to the given XML element,
|
|
211
235
|
and initialize it with the element. This is a convenience shortcut.
|
|
212
236
|
``"""
|
|
213
237
|
node_class = self.resolve_class(element, allowed_types)
|
|
214
238
|
return node_class.from_xml(element)
|
|
215
239
|
|
|
216
240
|
def resolve_class(
|
|
217
|
-
self, element: NSElement, allowed_types: tuple[type[
|
|
218
|
-
) -> type[
|
|
219
|
-
"""Find the ``
|
|
241
|
+
self, element: NSElement, allowed_types: tuple[type[A]] | None = None
|
|
242
|
+
) -> type[A]:
|
|
243
|
+
"""Find the ``AstNode`` subclass that corresponds to the given XML element."""
|
|
220
244
|
try:
|
|
221
245
|
node_class = self.parsers[element.tag]
|
|
222
246
|
except KeyError:
|
|
223
|
-
msg = f"Unsupported tag: <{element.
|
|
247
|
+
msg = f"Unsupported tag: <{element.qname}>"
|
|
224
248
|
if "{" not in element.tag:
|
|
225
249
|
msg = f"{msg} without an XML namespace"
|
|
226
250
|
if allowed_types:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
msg = f"{msg}, expected one of: {types}"
|
|
251
|
+
allowed = _tag_names_to_text(_get_allowed_tag_names(*allowed_types), element)
|
|
252
|
+
msg = f"{msg}, expected one of: {allowed}."
|
|
230
253
|
|
|
231
|
-
raise
|
|
254
|
+
raise XmlElementNotSupported(msg) from None
|
|
232
255
|
|
|
233
256
|
# Check whether the resolved class is indeed a valid option here.
|
|
234
257
|
if allowed_types is not None and not issubclass(node_class, allowed_types):
|
|
235
258
|
types = ", ".join(c.__name__ for c in allowed_types)
|
|
236
|
-
raise
|
|
237
|
-
f"Unexpected {node_class.__name__} for <{element.
|
|
259
|
+
raise InvalidXmlElement(
|
|
260
|
+
f"Unexpected {node_class.__name__} for <{element.qname}> node, "
|
|
238
261
|
f"expected one of: {types}"
|
|
239
262
|
)
|
|
240
263
|
|
|
241
264
|
return node_class
|
|
242
265
|
|
|
243
|
-
def get_parser_class(self, xml_qname) -> type[
|
|
266
|
+
def get_parser_class(self, xml_qname) -> type[AstNode]:
|
|
244
267
|
"""Provide the parser class for a given XML Qualified name."""
|
|
245
268
|
return self.parsers[xml_qname]
|
|
246
269
|
|
|
247
|
-
def find_subclasses(self, node_type: type[
|
|
270
|
+
def find_subclasses(self, node_type: type[A]) -> list[type[A]]:
|
|
248
271
|
"""Find all registered parsers for a given node."""
|
|
249
272
|
return {
|
|
250
273
|
tag: node_class
|
|
@@ -254,7 +277,19 @@ class TagRegistry:
|
|
|
254
277
|
|
|
255
278
|
|
|
256
279
|
def expect_tag(namespace: xmlns | str, *tag_names: str):
|
|
257
|
-
"""
|
|
280
|
+
"""Decorator for ``from_xml()`` methods that validate whether a given tag is provided.
|
|
281
|
+
|
|
282
|
+
For example:
|
|
283
|
+
|
|
284
|
+
.. code-block:: python
|
|
285
|
+
|
|
286
|
+
@classmethod
|
|
287
|
+
@expect_tag(xmlns.fes20, "Literal")
|
|
288
|
+
def from_xml(cls, element):
|
|
289
|
+
...
|
|
290
|
+
|
|
291
|
+
This guard is needed when nodes are passed directly to a ``from_xml()`` method.
|
|
292
|
+
"""
|
|
258
293
|
valid_tags = {QName(namespace, name).text for name in tag_names}
|
|
259
294
|
expect0 = QName(namespace, tag_names[0]).text
|
|
260
295
|
|
|
@@ -262,8 +297,9 @@ def expect_tag(namespace: xmlns | str, *tag_names: str):
|
|
|
262
297
|
@wraps(func)
|
|
263
298
|
def _expect_tag_decorator(cls, element: NSElement, *args, **kwargs):
|
|
264
299
|
if element.tag not in valid_tags:
|
|
265
|
-
raise
|
|
266
|
-
f"{cls.__name__} parser expects an <{expect0}> node,
|
|
300
|
+
raise InvalidXmlElement(
|
|
301
|
+
f"{cls.__name__} parser expects an <{_replace_common_ns(expect0, element)}> node,"
|
|
302
|
+
f" got <{element.qname}>"
|
|
267
303
|
)
|
|
268
304
|
return func(cls, element, *args, **kwargs)
|
|
269
305
|
|
|
@@ -273,13 +309,25 @@ def expect_tag(namespace: xmlns | str, *tag_names: str):
|
|
|
273
309
|
|
|
274
310
|
|
|
275
311
|
def expect_no_children(from_xml_func):
|
|
276
|
-
"""
|
|
312
|
+
"""Decorator for ``from_xml()`` methods that validate that the XML tag has no child nodes.
|
|
313
|
+
|
|
314
|
+
For example:
|
|
315
|
+
|
|
316
|
+
.. code-block:: python
|
|
317
|
+
|
|
318
|
+
@classmethod
|
|
319
|
+
@expect_tag(xmlns.fes20, "ResourceId")
|
|
320
|
+
@expect_no_children
|
|
321
|
+
def from_xml(cls, element):
|
|
322
|
+
...
|
|
323
|
+
"""
|
|
277
324
|
|
|
278
325
|
@wraps(from_xml_func)
|
|
279
326
|
def _expect_no_children_decorator(cls, element: NSElement, *args, **kwargs):
|
|
280
327
|
if len(element):
|
|
281
|
-
raise
|
|
282
|
-
f"
|
|
328
|
+
raise InvalidXmlElement(
|
|
329
|
+
f"Element <{element.qname}> does not support child elements,"
|
|
330
|
+
f" found <{element[0].qname}>."
|
|
283
331
|
)
|
|
284
332
|
|
|
285
333
|
return from_xml_func(cls, element, *args, **kwargs)
|
|
@@ -287,28 +335,53 @@ def expect_no_children(from_xml_func):
|
|
|
287
335
|
return _expect_no_children_decorator
|
|
288
336
|
|
|
289
337
|
|
|
290
|
-
def expect_children(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
for
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
338
|
+
def expect_children( # noqa: C901
|
|
339
|
+
min_child_nodes, *expect_types: str | type[AstNode], silent_allowed: tuple[str] = ()
|
|
340
|
+
):
|
|
341
|
+
"""Decorator for ``from_xml()`` methods to validate whether an element has the expected children.
|
|
342
|
+
|
|
343
|
+
For example:
|
|
344
|
+
|
|
345
|
+
.. code-block:: python
|
|
346
|
+
|
|
347
|
+
@classmethod
|
|
348
|
+
@expect_children(2, Expression)
|
|
349
|
+
def from_xml(cls, element):
|
|
350
|
+
...
|
|
351
|
+
"""
|
|
352
|
+
# Validate arguments early
|
|
353
|
+
for child_type in expect_types + silent_allowed:
|
|
354
|
+
if isinstance(child_type, str):
|
|
355
|
+
if not child_type.startswith("{"):
|
|
356
|
+
raise ValueError(
|
|
357
|
+
f"String arguments to @expect_children() should be"
|
|
358
|
+
f" fully qualified XML namespaces, not {child_type!r}"
|
|
359
|
+
)
|
|
360
|
+
elif not isinstance(child_type, type) or not issubclass(child_type, AstNode):
|
|
299
361
|
raise TypeError(f"Unexpected {child_type!r}")
|
|
300
|
-
|
|
362
|
+
|
|
363
|
+
def _get_allowed(known_tag_names, element):
|
|
364
|
+
return _tag_names_to_text(sorted(set(known_tag_names) - set(silent_allowed)), element)
|
|
301
365
|
|
|
302
366
|
def _wrapper(func):
|
|
303
367
|
@wraps(func)
|
|
304
368
|
def _expect_children_decorator(cls, element: NSElement, *args, **kwargs):
|
|
369
|
+
known_tag_names = _get_allowed_tag_names(*expect_types, *silent_allowed)
|
|
370
|
+
|
|
305
371
|
if len(element) < min_child_nodes:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
f"
|
|
310
|
-
f"
|
|
372
|
+
allowed = _get_allowed(known_tag_names, element)
|
|
373
|
+
raise InvalidXmlElement(
|
|
374
|
+
f"<{element.qname}> should have {min_child_nodes} child nodes,"
|
|
375
|
+
f" got only {len(element)}."
|
|
376
|
+
f" Allowed types are: {allowed}."
|
|
311
377
|
)
|
|
378
|
+
for child in element:
|
|
379
|
+
if child.tag not in known_tag_names:
|
|
380
|
+
allowed = _get_allowed(known_tag_names, element)
|
|
381
|
+
raise InvalidXmlElement(
|
|
382
|
+
f"<{element.qname}> does not support a <{child.qname}> child node."
|
|
383
|
+
f" Allowed types are: {allowed}."
|
|
384
|
+
)
|
|
312
385
|
|
|
313
386
|
return func(cls, element, *args, **kwargs)
|
|
314
387
|
|
|
@@ -317,4 +390,37 @@ def expect_children(min_child_nodes, *expect_types: str | type[BaseNode]):
|
|
|
317
390
|
return _wrapper
|
|
318
391
|
|
|
319
392
|
|
|
393
|
+
def _get_allowed_tag_names(*expect_types: type[AstNode] | str) -> list[str]:
|
|
394
|
+
# Resolve arguments later, as get_tag_names() depends on __subclasses__()
|
|
395
|
+
# which may not be completely known at this point.
|
|
396
|
+
tag_names = []
|
|
397
|
+
for child_type in expect_types:
|
|
398
|
+
if isinstance(child_type, type) and issubclass(child_type, AstNode):
|
|
399
|
+
tag_names.extend(child_type.get_tag_names())
|
|
400
|
+
elif isinstance(child_type, str):
|
|
401
|
+
if not child_type.startswith("{"):
|
|
402
|
+
raise ValueError(
|
|
403
|
+
f"String arguments should be fully qualified XML namespaces, not {child_type!r}"
|
|
404
|
+
)
|
|
405
|
+
tag_names.append(child_type)
|
|
406
|
+
else:
|
|
407
|
+
raise TypeError(f"Unexpected {child_type!r}")
|
|
408
|
+
return tag_names
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _tag_names_to_text(tag_names: Iterable[str], user_element: NSElement) -> str:
|
|
412
|
+
body = _replace_common_ns(">, <".join(tag_names), user_element)
|
|
413
|
+
return f"<{body}>"
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _replace_common_ns(text: str, user_element: NSElement):
|
|
417
|
+
"""In error messages, replace the full XML names with QName/prefixed versions.
|
|
418
|
+
The chosen prefixes reference the tags that the client submitted in their XML body.
|
|
419
|
+
"""
|
|
420
|
+
for prefix, ns in user_element.ns_aliases.items():
|
|
421
|
+
text = text.replace(f"{{{ns}}}", f"{prefix}:")
|
|
422
|
+
return text
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
#: The tag registry to register new parsing classes at.
|
|
320
426
|
tag_registry = TagRegistry()
|
|
@@ -13,16 +13,88 @@ can be very helpful to see which options each object type should support.
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
from . import lookups # noqa: F401 (need to register ORM lookups)
|
|
16
|
-
from .expressions import
|
|
16
|
+
from .expressions import (
|
|
17
|
+
BinaryOperator,
|
|
18
|
+
BinaryOperatorType,
|
|
19
|
+
Expression,
|
|
20
|
+
Function,
|
|
21
|
+
Literal,
|
|
22
|
+
ValueReference,
|
|
23
|
+
)
|
|
17
24
|
from .filters import Filter
|
|
18
|
-
from .identifiers import ResourceId
|
|
19
|
-
from .operators import
|
|
20
|
-
|
|
25
|
+
from .identifiers import Id, ResourceId, VersionActionTokens
|
|
26
|
+
from .operators import (
|
|
27
|
+
BetweenComparisonOperator,
|
|
28
|
+
BinaryComparisonName,
|
|
29
|
+
BinaryComparisonOperator,
|
|
30
|
+
BinaryLogicOperator,
|
|
31
|
+
BinaryLogicType,
|
|
32
|
+
BinarySpatialOperator,
|
|
33
|
+
ComparisonOperator,
|
|
34
|
+
DistanceOperator,
|
|
35
|
+
DistanceOperatorName,
|
|
36
|
+
ExtensionOperator,
|
|
37
|
+
IdOperator,
|
|
38
|
+
LikeOperator,
|
|
39
|
+
LogicalOperator,
|
|
40
|
+
MatchAction,
|
|
41
|
+
Measure,
|
|
42
|
+
NilOperator,
|
|
43
|
+
NonIdOperator,
|
|
44
|
+
NullOperator,
|
|
45
|
+
Operator,
|
|
46
|
+
SpatialOperator,
|
|
47
|
+
SpatialOperatorName,
|
|
48
|
+
TemporalOperator,
|
|
49
|
+
TemporalOperatorName,
|
|
50
|
+
UnaryLogicOperator,
|
|
51
|
+
UnaryLogicType,
|
|
52
|
+
)
|
|
53
|
+
from .sorting import SortBy, SortOrder, SortProperty
|
|
21
54
|
|
|
22
55
|
__all__ = [
|
|
23
56
|
"Filter",
|
|
57
|
+
# Expressions
|
|
58
|
+
"Expression",
|
|
59
|
+
"Function",
|
|
60
|
+
"Literal",
|
|
24
61
|
"ValueReference",
|
|
62
|
+
# WFS 1.0 expressions
|
|
63
|
+
"BinaryOperator",
|
|
64
|
+
"BinaryOperatorType",
|
|
65
|
+
# Identifiers
|
|
66
|
+
"Id",
|
|
25
67
|
"ResourceId",
|
|
68
|
+
"VersionActionTokens",
|
|
69
|
+
# Operators
|
|
70
|
+
"BetweenComparisonOperator",
|
|
71
|
+
"BinaryComparisonName",
|
|
72
|
+
"BinaryComparisonOperator",
|
|
73
|
+
"BinaryLogicOperator",
|
|
74
|
+
"BinaryLogicType",
|
|
75
|
+
"BinarySpatialOperator",
|
|
76
|
+
"ComparisonOperator",
|
|
77
|
+
"DistanceOperator",
|
|
78
|
+
"DistanceOperatorName",
|
|
79
|
+
"ExtensionOperator",
|
|
26
80
|
"IdOperator",
|
|
81
|
+
"LikeOperator",
|
|
82
|
+
"LogicalOperator",
|
|
83
|
+
"MatchAction",
|
|
84
|
+
"Measure",
|
|
85
|
+
"NilOperator",
|
|
86
|
+
"NonIdOperator",
|
|
87
|
+
"NullOperator",
|
|
88
|
+
"Operator",
|
|
89
|
+
"SpatialOperator",
|
|
90
|
+
"SpatialOperatorName",
|
|
91
|
+
"TemporalOperator",
|
|
92
|
+
"TemporalOperatorName",
|
|
93
|
+
"UnaryLogicOperator",
|
|
94
|
+
"UnaryLogicType",
|
|
95
|
+
# Sorting
|
|
96
|
+
"SortBy",
|
|
97
|
+
"SortOrder",
|
|
98
|
+
"SortProperty",
|
|
27
99
|
"SortBy",
|
|
28
100
|
]
|