django-gisserver 2.0__py3-none-any.whl → 2.1.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.
Files changed (56) hide show
  1. {django_gisserver-2.0.dist-info → django_gisserver-2.1.1.dist-info}/METADATA +27 -10
  2. django_gisserver-2.1.1.dist-info/RECORD +68 -0
  3. {django_gisserver-2.0.dist-info → django_gisserver-2.1.1.dist-info}/WHEEL +1 -1
  4. gisserver/__init__.py +1 -1
  5. gisserver/conf.py +23 -1
  6. gisserver/crs.py +452 -0
  7. gisserver/db.py +78 -6
  8. gisserver/exceptions.py +106 -2
  9. gisserver/extensions/functions.py +122 -28
  10. gisserver/extensions/queries.py +15 -10
  11. gisserver/features.py +46 -33
  12. gisserver/geometries.py +64 -306
  13. gisserver/management/commands/loadgeojson.py +41 -21
  14. gisserver/operations/base.py +11 -7
  15. gisserver/operations/wfs20.py +31 -93
  16. gisserver/output/__init__.py +6 -2
  17. gisserver/output/base.py +28 -13
  18. gisserver/output/csv.py +18 -6
  19. gisserver/output/geojson.py +7 -6
  20. gisserver/output/gml32.py +86 -27
  21. gisserver/output/results.py +25 -39
  22. gisserver/output/utils.py +9 -2
  23. gisserver/parsers/ast.py +177 -68
  24. gisserver/parsers/fes20/__init__.py +76 -4
  25. gisserver/parsers/fes20/expressions.py +97 -27
  26. gisserver/parsers/fes20/filters.py +9 -6
  27. gisserver/parsers/fes20/identifiers.py +27 -7
  28. gisserver/parsers/fes20/lookups.py +8 -6
  29. gisserver/parsers/fes20/operators.py +101 -49
  30. gisserver/parsers/fes20/sorting.py +14 -6
  31. gisserver/parsers/gml/__init__.py +10 -19
  32. gisserver/parsers/gml/base.py +32 -14
  33. gisserver/parsers/gml/geometries.py +54 -21
  34. gisserver/parsers/ows/kvp.py +10 -2
  35. gisserver/parsers/ows/requests.py +6 -4
  36. gisserver/parsers/query.py +6 -2
  37. gisserver/parsers/values.py +61 -4
  38. gisserver/parsers/wfs20/__init__.py +2 -0
  39. gisserver/parsers/wfs20/adhoc.py +28 -18
  40. gisserver/parsers/wfs20/base.py +12 -7
  41. gisserver/parsers/wfs20/projection.py +3 -3
  42. gisserver/parsers/wfs20/requests.py +1 -0
  43. gisserver/parsers/wfs20/stored.py +3 -2
  44. gisserver/parsers/xml.py +12 -0
  45. gisserver/projection.py +17 -7
  46. gisserver/static/gisserver/index.css +27 -6
  47. gisserver/templates/gisserver/base.html +15 -0
  48. gisserver/templates/gisserver/index.html +10 -16
  49. gisserver/templates/gisserver/service_description.html +12 -6
  50. gisserver/templates/gisserver/wfs/feature_field.html +1 -1
  51. gisserver/templates/gisserver/wfs/feature_type.html +44 -13
  52. gisserver/types.py +152 -82
  53. gisserver/views.py +47 -24
  54. django_gisserver-2.0.dist-info/RECORD +0 -66
  55. {django_gisserver-2.0.dist-info → django_gisserver-2.1.1.dist-info/licenses}/LICENSE +0 -0
  56. {django_gisserver-2.0.dist-info → django_gisserver-2.1.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:`BaseNode` and register themselves as the parser/handler
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:`BaseNode.from_xml` class method.
9
- The custom `from_xml()` method should copy the XML data into local attributes.
8
+ as long as it has an :meth:`AstNode.from_xml` class method.
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:`SomeNode.child_from_xml()` will also
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, one should avoid
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 gisserver.exceptions import ExternalParsingError
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
- "BaseNode",
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 enumeration of XML tag names.
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 BaseNode:
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)
@@ -85,24 +90,33 @@ class BaseNode:
85
90
  an XML tag into a Python (data) class.
86
91
  """
87
92
 
93
+ #: Default namespace of the element and subclasses, if not given by ``@tag_registry.register()``.
88
94
  xml_ns: xmlns | str | None = None
89
- xml_tags = []
95
+
96
+ _xml_tags = []
97
+
98
+ @classproperty
99
+ def xml_name(cls) -> str:
100
+ """Tell the default tag by which this class is registered"""
101
+ return cls._xml_tags[0]
102
+
103
+ xml_name.__doc__ = "Tell the default tag by which this class is registered"
90
104
 
91
105
  def __init_subclass__(cls):
92
106
  # Each class level has a fresh list of supported child tags.
93
- cls.xml_tags = []
107
+ cls._xml_tags = []
94
108
 
95
109
  @classmethod
96
110
  def from_xml(cls, element: NSElement):
97
111
  """Initialize this Python class from the data of the corresponding XML tag.
98
- Each subclass overrides this to implement the XMl parsing of that particular XML tag.
112
+ Each subclass overrides this to implement the XML parsing of that particular XML tag.
99
113
  """
100
114
  raise NotImplementedError(
101
115
  f"{cls.__name__}.from_xml() is not implemented to parse <{element.tag}>"
102
116
  )
103
117
 
104
118
  @classmethod
105
- def child_from_xml(cls, element: NSElement) -> BaseNode:
119
+ def child_from_xml(cls, element: NSElement) -> AstNode:
106
120
  """Parse the element, returning the correct subclass of this tag.
107
121
 
108
122
  When ``Expression.child_from_xml(some_node)`` is given, it may
@@ -112,12 +126,23 @@ class BaseNode:
112
126
  return sub_class.from_xml(element)
113
127
 
114
128
  @classmethod
115
- def get_tag_names(cls) -> Iterable[str]:
116
- """Provide all known XMl tags that this code can parse."""
117
- return chain.from_iterable(sub_type.xml_tags for sub_type in cls.__subclasses__())
129
+ def get_tag_names(cls) -> list[str]:
130
+ """Provide all known XML tags that this code can parse."""
131
+ try:
132
+ # Because a cached class property is hard to build
133
+ return _KNOWN_TAG_NAMES[cls]
134
+ except KeyError:
135
+ all_xml_tags = cls._xml_tags.copy()
136
+ for sub_cls in cls.__subclasses__():
137
+ all_xml_tags.extend(sub_cls.get_tag_names())
138
+ _KNOWN_TAG_NAMES[cls] = all_xml_tags
139
+ return all_xml_tags
140
+
118
141
 
142
+ _KNOWN_TAG_NAMES = {}
119
143
 
120
- BN = TypeVar("BN", bound=BaseNode)
144
+ BaseNode = AstNode # keep old name around
145
+ A = TypeVar("A", bound=AstNode)
121
146
 
122
147
 
123
148
  class TagRegistry:
@@ -126,7 +151,7 @@ class TagRegistry:
126
151
  The same class can be registered multiple times for different tag names.
127
152
  """
128
153
 
129
- parsers: dict[str, type[BaseNode]]
154
+ parsers: dict[str, type[AstNode]]
130
155
 
131
156
  def __init__(self):
132
157
  self.parsers = {}
@@ -141,9 +166,11 @@ class TagRegistry:
141
166
 
142
167
  Usage:
143
168
 
169
+ .. code-block:: python
170
+
144
171
  @dataclass
145
172
  @tag_registry.register()
146
- class SomeXmlTag(BaseNode):
173
+ class SomeXmlTag(AstNode):
147
174
  xml_ns = FES
148
175
 
149
176
  @classmethod
@@ -159,7 +186,7 @@ class TagRegistry:
159
186
  each member name is assumed to be an XML tag name.
160
187
  """
161
188
 
162
- def _dec(node_class: type[BaseNode]) -> type[BaseNode]:
189
+ def _dec(node_class: type[AstNode]) -> type[AstNode]:
163
190
  if tag is None or isinstance(tag, str):
164
191
  # Single tag name for the class.
165
192
  self._register_tag_parser(
@@ -170,7 +197,9 @@ class TagRegistry:
170
197
  # Note using __members__, not _member_names_.
171
198
  # The latter will skip aliased items (like BBOX/Within).
172
199
  for member_name in tag.__members__:
173
- self._register_tag_parser(node_class, tag=member_name, namespace=namespace)
200
+ self._register_tag_parser(
201
+ node_class, tag=member_name, namespace=namespace, hidden=hidden
202
+ )
174
203
  else:
175
204
  raise TypeError("tag type incorrect")
176
205
 
@@ -180,14 +209,14 @@ class TagRegistry:
180
209
 
181
210
  def _register_tag_parser(
182
211
  self,
183
- node_class: type[BaseNode],
212
+ node_class: type[AstNode],
184
213
  tag: str,
185
214
  namespace: xmlns | str | None = None,
186
215
  hidden: bool = False,
187
216
  ):
188
217
  """Register a Python (data) class as parser for an XML node."""
189
- if not issubclass(node_class, BaseNode):
190
- raise TypeError(f"{node_class} must be a subclass of BaseNode")
218
+ if not issubclass(node_class, AstNode):
219
+ raise TypeError(f"{node_class} must be a subclass of AstNode")
191
220
 
192
221
  if namespace is None and node_class.xml_ns is None:
193
222
  raise RuntimeError(
@@ -202,49 +231,46 @@ class TagRegistry:
202
231
 
203
232
  self.parsers[xml_name] = node_class # Track this parser to resolve the tag.
204
233
  if not hidden:
205
- node_class.xml_tags.append(xml_name) # Allow fetching all names later
234
+ node_class._xml_tags.append(xml_name) # Allow fetching all names later
206
235
 
207
- def node_from_xml(
208
- self, element: NSElement, allowed_types: tuple[type[BN]] | None = None
209
- ) -> BN:
210
- """Find the ``BaseNode`` subclass that corresponds to the given XML element,
236
+ def node_from_xml(self, element: NSElement, allowed_types: tuple[type[A]] | None = None) -> A:
237
+ """Find the ``AstNode`` subclass that corresponds to the given XML element,
211
238
  and initialize it with the element. This is a convenience shortcut.
212
- ``"""
239
+ """
213
240
  node_class = self.resolve_class(element, allowed_types)
214
241
  return node_class.from_xml(element)
215
242
 
216
243
  def resolve_class(
217
- self, element: NSElement, allowed_types: tuple[type[BN]] | None = None
218
- ) -> type[BN]:
219
- """Find the ``BaseNode`` subclass that corresponds to the given XML element."""
244
+ self, element: NSElement, allowed_types: tuple[type[A]] | None = None
245
+ ) -> type[A]:
246
+ """Find the :class:`AstNode` subclass that corresponds to the given XML element."""
220
247
  try:
221
248
  node_class = self.parsers[element.tag]
222
249
  except KeyError:
223
- msg = f"Unsupported tag: <{element.tag}>"
250
+ msg = f"Unsupported tag: <{element.qname}>"
224
251
  if "{" not in element.tag:
225
252
  msg = f"{msg} without an XML namespace"
226
253
  if allowed_types:
227
- # Show better exception message
228
- types = ", ".join(chain.from_iterable(c.get_tag_names() for c in allowed_types))
229
- msg = f"{msg}, expected one of: {types}"
254
+ allowed = _tag_names_to_text(_get_allowed_tag_names(*allowed_types), element)
255
+ msg = f"{msg}, expected one of: {allowed}."
230
256
 
231
- raise ExternalParsingError(msg) from None
257
+ raise XmlElementNotSupported(msg) from None
232
258
 
233
259
  # Check whether the resolved class is indeed a valid option here.
234
260
  if allowed_types is not None and not issubclass(node_class, allowed_types):
235
261
  types = ", ".join(c.__name__ for c in allowed_types)
236
- raise ExternalParsingError(
237
- f"Unexpected {node_class.__name__} for <{element.tag}> node, "
262
+ raise InvalidXmlElement(
263
+ f"Unexpected {node_class.__name__} for <{element.qname}> node, "
238
264
  f"expected one of: {types}"
239
265
  )
240
266
 
241
267
  return node_class
242
268
 
243
- def get_parser_class(self, xml_qname) -> type[BaseNode]:
269
+ def get_parser_class(self, xml_qname) -> type[AstNode]:
244
270
  """Provide the parser class for a given XML Qualified name."""
245
271
  return self.parsers[xml_qname]
246
272
 
247
- def find_subclasses(self, node_type: type[BN]) -> list[type[BN]]:
273
+ def find_subclasses(self, node_type: type[A]) -> list[type[A]]:
248
274
  """Find all registered parsers for a given node."""
249
275
  return {
250
276
  tag: node_class
@@ -254,7 +280,19 @@ class TagRegistry:
254
280
 
255
281
 
256
282
  def expect_tag(namespace: xmlns | str, *tag_names: str):
257
- """Validate whether a given tag is need."""
283
+ """Decorator for ``from_xml()`` methods that validate whether a given tag is provided.
284
+
285
+ For example:
286
+
287
+ .. code-block:: python
288
+
289
+ @classmethod
290
+ @expect_tag(xmlns.fes20, "Literal")
291
+ def from_xml(cls, element):
292
+ ...
293
+
294
+ This guard is needed when nodes are passed directly to a ``from_xml()`` method.
295
+ """
258
296
  valid_tags = {QName(namespace, name).text for name in tag_names}
259
297
  expect0 = QName(namespace, tag_names[0]).text
260
298
 
@@ -262,8 +300,9 @@ def expect_tag(namespace: xmlns | str, *tag_names: str):
262
300
  @wraps(func)
263
301
  def _expect_tag_decorator(cls, element: NSElement, *args, **kwargs):
264
302
  if element.tag not in valid_tags:
265
- raise ExternalParsingError(
266
- f"{cls.__name__} parser expects an <{expect0}> node, got <{element.tag}>"
303
+ raise InvalidXmlElement(
304
+ f"{cls.__name__} parser expects an <{_replace_common_ns(expect0, element)}> node,"
305
+ f" got <{element.qname}>"
267
306
  )
268
307
  return func(cls, element, *args, **kwargs)
269
308
 
@@ -273,13 +312,25 @@ def expect_tag(namespace: xmlns | str, *tag_names: str):
273
312
 
274
313
 
275
314
  def expect_no_children(from_xml_func):
276
- """Validate that the XML tag has no child nodes."""
315
+ """Decorator for ``from_xml()`` methods that validate that the XML tag has no child nodes.
316
+
317
+ For example:
318
+
319
+ .. code-block:: python
320
+
321
+ @classmethod
322
+ @expect_tag(xmlns.fes20, "ResourceId")
323
+ @expect_no_children
324
+ def from_xml(cls, element):
325
+ ...
326
+ """
277
327
 
278
328
  @wraps(from_xml_func)
279
329
  def _expect_no_children_decorator(cls, element: NSElement, *args, **kwargs):
280
330
  if len(element):
281
- raise ExternalParsingError(
282
- f"Unsupported child element for {element.tag} element: {element[0].tag}."
331
+ raise InvalidXmlElement(
332
+ f"Element <{element.qname}> does not support child elements,"
333
+ f" found <{element[0].qname}>."
283
334
  )
284
335
 
285
336
  return from_xml_func(cls, element, *args, **kwargs)
@@ -287,28 +338,53 @@ def expect_no_children(from_xml_func):
287
338
  return _expect_no_children_decorator
288
339
 
289
340
 
290
- def expect_children(min_child_nodes, *expect_types: str | type[BaseNode]):
291
- """Validate whether an element has enough children to continue parsing."""
292
- known_tag_names = set()
293
- for child_type in expect_types:
294
- if isinstance(child_type, type) and issubclass(child_type, BaseNode):
295
- known_tag_names.update(child_type.get_tag_names())
296
- elif isinstance(child_type, str):
297
- known_tag_names.add(child_type)
298
- else:
341
+ def expect_children( # noqa: C901
342
+ min_child_nodes, *expect_types: str | type[AstNode], silent_allowed: tuple[str] = ()
343
+ ):
344
+ """Decorator for ``from_xml()`` methods to validate whether an element has the expected children.
345
+
346
+ For example:
347
+
348
+ .. code-block:: python
349
+
350
+ @classmethod
351
+ @expect_children(2, Expression)
352
+ def from_xml(cls, element):
353
+ ...
354
+ """
355
+ # Validate arguments early
356
+ for child_type in expect_types + silent_allowed:
357
+ if isinstance(child_type, str):
358
+ if not child_type.startswith("{"):
359
+ raise ValueError(
360
+ f"String arguments to @expect_children() should be"
361
+ f" fully qualified XML namespaces, not {child_type!r}"
362
+ )
363
+ elif not isinstance(child_type, type) or not issubclass(child_type, AstNode):
299
364
  raise TypeError(f"Unexpected {child_type!r}")
300
- known_tag_names = sorted(known_tag_names)
365
+
366
+ def _get_allowed(known_tag_names, element):
367
+ return _tag_names_to_text(sorted(set(known_tag_names) - set(silent_allowed)), element)
301
368
 
302
369
  def _wrapper(func):
303
370
  @wraps(func)
304
371
  def _expect_children_decorator(cls, element: NSElement, *args, **kwargs):
372
+ known_tag_names = _get_allowed_tag_names(*expect_types, *silent_allowed)
373
+
305
374
  if len(element) < min_child_nodes:
306
- type_names = ", ".join(known_tag_names)
307
- suffix = f" (possible tags: {type_names})" if type_names else ""
308
- raise ExternalParsingError(
309
- f"<{element.tag}> should have {min_child_nodes} child nodes, "
310
- f"got {len(element)}{suffix}"
375
+ allowed = _get_allowed(known_tag_names, element)
376
+ raise InvalidXmlElement(
377
+ f"<{element.qname}> should have {min_child_nodes} child nodes,"
378
+ f" got only {len(element)}."
379
+ f" Allowed types are: {allowed}."
311
380
  )
381
+ for child in element:
382
+ if child.tag not in known_tag_names:
383
+ allowed = _get_allowed(known_tag_names, element)
384
+ raise InvalidXmlElement(
385
+ f"<{element.qname}> does not support a <{child.qname}> child node."
386
+ f" Allowed types are: {allowed}."
387
+ )
312
388
 
313
389
  return func(cls, element, *args, **kwargs)
314
390
 
@@ -317,4 +393,37 @@ def expect_children(min_child_nodes, *expect_types: str | type[BaseNode]):
317
393
  return _wrapper
318
394
 
319
395
 
396
+ def _get_allowed_tag_names(*expect_types: type[AstNode] | str) -> list[str]:
397
+ # Resolve arguments later, as get_tag_names() depends on __subclasses__()
398
+ # which may not be completely known at this point.
399
+ tag_names = []
400
+ for child_type in expect_types:
401
+ if isinstance(child_type, type) and issubclass(child_type, AstNode):
402
+ tag_names.extend(child_type.get_tag_names())
403
+ elif isinstance(child_type, str):
404
+ if not child_type.startswith("{"):
405
+ raise ValueError(
406
+ f"String arguments should be fully qualified XML namespaces, not {child_type!r}"
407
+ )
408
+ tag_names.append(child_type)
409
+ else:
410
+ raise TypeError(f"Unexpected {child_type!r}")
411
+ return tag_names
412
+
413
+
414
+ def _tag_names_to_text(tag_names: Iterable[str], user_element: NSElement) -> str:
415
+ body = _replace_common_ns(">, <".join(tag_names), user_element)
416
+ return f"<{body}>"
417
+
418
+
419
+ def _replace_common_ns(text: str, user_element: NSElement):
420
+ """In error messages, replace the full XML names with QName/prefixed versions.
421
+ The chosen prefixes reference the tags that the client submitted in their XML body.
422
+ """
423
+ for prefix, ns in user_element.ns_aliases.items():
424
+ text = text.replace(f"{{{ns}}}", f"{prefix}:")
425
+ return text
426
+
427
+
428
+ #: The tag registry to register new parsing classes at.
320
429
  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 ValueReference
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 IdOperator
20
- from .sorting import SortBy
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
  ]