django-gisserver 1.5.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.
Files changed (77) hide show
  1. {django_gisserver-1.5.0.dist-info → django_gisserver-2.1.dist-info}/METADATA +34 -8
  2. django_gisserver-2.1.dist-info/RECORD +68 -0
  3. {django_gisserver-1.5.0.dist-info → django_gisserver-2.1.dist-info}/WHEEL +1 -1
  4. gisserver/__init__.py +1 -1
  5. gisserver/compat.py +23 -0
  6. gisserver/conf.py +7 -0
  7. gisserver/crs.py +401 -0
  8. gisserver/db.py +126 -51
  9. gisserver/exceptions.py +132 -4
  10. gisserver/extensions/__init__.py +4 -0
  11. gisserver/{parsers/fes20 → extensions}/functions.py +131 -31
  12. gisserver/extensions/queries.py +266 -0
  13. gisserver/features.py +253 -181
  14. gisserver/geometries.py +64 -311
  15. gisserver/management/__init__.py +0 -0
  16. gisserver/management/commands/__init__.py +0 -0
  17. gisserver/management/commands/loadgeojson.py +311 -0
  18. gisserver/operations/base.py +130 -312
  19. gisserver/operations/wfs20.py +399 -375
  20. gisserver/output/__init__.py +14 -49
  21. gisserver/output/base.py +198 -144
  22. gisserver/output/csv.py +78 -75
  23. gisserver/output/geojson.py +37 -37
  24. gisserver/output/gml32.py +287 -259
  25. gisserver/output/iters.py +207 -0
  26. gisserver/output/results.py +73 -61
  27. gisserver/output/stored.py +143 -0
  28. gisserver/output/utils.py +81 -169
  29. gisserver/output/xmlschema.py +85 -46
  30. gisserver/parsers/__init__.py +10 -10
  31. gisserver/parsers/ast.py +426 -0
  32. gisserver/parsers/fes20/__init__.py +89 -31
  33. gisserver/parsers/fes20/expressions.py +172 -58
  34. gisserver/parsers/fes20/filters.py +116 -45
  35. gisserver/parsers/fes20/identifiers.py +66 -28
  36. gisserver/parsers/fes20/lookups.py +146 -0
  37. gisserver/parsers/fes20/operators.py +417 -161
  38. gisserver/parsers/fes20/sorting.py +113 -34
  39. gisserver/parsers/gml/__init__.py +17 -25
  40. gisserver/parsers/gml/base.py +36 -15
  41. gisserver/parsers/gml/geometries.py +105 -44
  42. gisserver/parsers/ows/__init__.py +25 -0
  43. gisserver/parsers/ows/kvp.py +198 -0
  44. gisserver/parsers/ows/requests.py +160 -0
  45. gisserver/parsers/query.py +179 -0
  46. gisserver/parsers/values.py +87 -4
  47. gisserver/parsers/wfs20/__init__.py +39 -0
  48. gisserver/parsers/wfs20/adhoc.py +253 -0
  49. gisserver/parsers/wfs20/base.py +148 -0
  50. gisserver/parsers/wfs20/projection.py +103 -0
  51. gisserver/parsers/wfs20/requests.py +483 -0
  52. gisserver/parsers/wfs20/stored.py +193 -0
  53. gisserver/parsers/xml.py +261 -0
  54. gisserver/projection.py +367 -0
  55. gisserver/static/gisserver/index.css +20 -4
  56. gisserver/templates/gisserver/base.html +12 -0
  57. gisserver/templates/gisserver/index.html +9 -15
  58. gisserver/templates/gisserver/service_description.html +12 -6
  59. gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml +9 -9
  60. gisserver/templates/gisserver/wfs/feature_field.html +3 -3
  61. gisserver/templates/gisserver/wfs/feature_type.html +35 -13
  62. gisserver/templatetags/gisserver_tags.py +20 -0
  63. gisserver/types.py +445 -313
  64. gisserver/views.py +227 -62
  65. django_gisserver-1.5.0.dist-info/RECORD +0 -54
  66. gisserver/parsers/base.py +0 -149
  67. gisserver/parsers/fes20/query.py +0 -285
  68. gisserver/parsers/tags.py +0 -102
  69. gisserver/queries/__init__.py +0 -37
  70. gisserver/queries/adhoc.py +0 -185
  71. gisserver/queries/base.py +0 -186
  72. gisserver/queries/projection.py +0 -240
  73. gisserver/queries/stored.py +0 -206
  74. gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml +0 -20
  75. gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml +0 -14
  76. {django_gisserver-1.5.0.dist-info → django_gisserver-2.1.dist-info/licenses}/LICENSE +0 -0
  77. {django_gisserver-1.5.0.dist-info → django_gisserver-2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,253 @@
1
+ """Handle adhoc-query objects.
2
+
3
+ The adhoc query is based on incoming request parameters,
4
+ such as the "FILTER", "BBOX" and "RESOURCEID" parameters.
5
+
6
+ These definitions follow the WFS spec.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from dataclasses import dataclass
13
+ from functools import cached_property
14
+
15
+ from django.db.models import Q
16
+
17
+ from gisserver.crs import CRS
18
+ from gisserver.exceptions import (
19
+ InvalidParameterValue,
20
+ MissingParameterValue,
21
+ OperationNotSupported,
22
+ )
23
+ from gisserver.parsers import fes20
24
+ from gisserver.parsers.ast import tag_registry
25
+ from gisserver.parsers.ows import KVPRequest
26
+ from gisserver.parsers.query import CompiledQuery
27
+ from gisserver.parsers.xml import NSElement, xmlns
28
+ from gisserver.projection import FeatureProjection
29
+
30
+ from .base import QueryExpression
31
+ from .projection import PropertyName
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ ADHOC_QUERY_ELEMENTS = (PropertyName, fes20.Filter, fes20.SortBy)
36
+
37
+
38
+ @dataclass
39
+ @tag_registry.register("Query", xmlns.wfs)
40
+ class AdhocQuery(QueryExpression):
41
+ """The Ad hoc query expression parameters.
42
+
43
+ This parses and handles the syntax::
44
+
45
+ <wfs:Query typeNames="...">
46
+ <wfs:PropertyName>...</wfs:PropertyName>
47
+ <fes:Filter>...</fes:Filter>
48
+ <fes:SortBy>...</fes:SortBy>
49
+ </wfs:Query>
50
+
51
+ And supports the KVP syntax::
52
+
53
+ ?SERVICE=WFS&...&TYPENAMES=ns:myType&FILTER=...&SORTBY=...&SRSNAME=...&PROPERTYNAME=...
54
+ ?SERVICE=WFS&...&TYPENAMES=ns:myType&BBOX=...&SORTBY=...&SRSNAME=...&PROPERTYNAME=...
55
+ ?SERVICE=WFS&...&TYPENAMES=ns:myType&RESOURCEID=...
56
+
57
+ This represents all dynamic queries received as request (hence "adhoc"),
58
+ such as the "FILTER" and "BBOX" arguments from an HTTP GET.
59
+
60
+ The WFS Spec has 3 class levels for this:
61
+
62
+ - AdhocQueryExpression (types, projection, selection, sorting)
63
+ - Query (adds srsName, featureVersion)
64
+ - StoredQuery (adds storedQueryID)
65
+
66
+ For KVP requests, this class seems almost identifical to the provided parameters.
67
+ However, the KVP format allows to provide parameter lists,
68
+ to perform support multiple queries in a single request!
69
+
70
+ .. seealso::
71
+ https://www.mediamaps.ch/ogc/schemas-xsdoc/sld/1.2/query_xsd.html#AbstractAdhocQueryExpressionType
72
+ """
73
+
74
+ # Tag attributes (implements ``fes:AbstractAdhocQueryExpression``)
75
+ # WFS allows multiple names to construct JOIN queries.
76
+ # See https://docs.ogc.org/is/09-025r2/09-025r2.html#107
77
+ # and https://docs.ogc.org/is/09-025r2/09-025r2.html#190
78
+
79
+ #: The 'typeNames' value if the request provided them. use :meth:`get_type_names` instead.
80
+ typeNames: list[str]
81
+ #: Aliases for typeNames are used for joining the same table twice. (JOIN statements are not supported yet).
82
+ aliases: list[str] | None = None
83
+ #: For XML POST requests, this handle value is returned in the ``<ows:Exception>``.
84
+ handle: str = ""
85
+
86
+ # part of the <wfs:Query> tag attributes:
87
+ #: The Coordinate Reference System to render the tag in
88
+ srsName: CRS | None = None
89
+
90
+ #: Projection clause (implements ``fes:AbstractProjectionClause``)
91
+ property_names: list[PropertyName] | None = None
92
+
93
+ #: Selection clause (implements ``fes:AbstractSelectionClause``).
94
+ #: - for XML POST this is encoded in a <fes:Filter> tag.
95
+ #: - for HTTP GET, this is encoded as FILTER, FILTER_LANGUAGE, RESOURCEID, BBOX.
96
+ filter: fes20.Filter | None = None
97
+
98
+ #: Sorting Clause (implements ``fes:AbstractSortingClause``)
99
+ sortBy: fes20.SortBy | None = None
100
+
101
+ def __post_init__(self):
102
+ if len(self.typeNames) > 1:
103
+ raise OperationNotSupported("Join queries are not supported", locator="typeNames")
104
+ if self.aliases:
105
+ raise OperationNotSupported("Join queries are not supported", locator="aliases")
106
+
107
+ @classmethod
108
+ def from_xml(cls, element: NSElement) -> AdhocQuery:
109
+ """Parse the XML element of the Query tag."""
110
+ type_names = [
111
+ element.parse_qname(qname)
112
+ for qname in element.get_str_attribute("typeNames").split(" ")
113
+ ]
114
+ aliases = element.attrib.get("aliases", None)
115
+ srsName = element.attrib.get("srsName", None)
116
+ property_names = []
117
+ filter = None
118
+ sortBy = None
119
+
120
+ for child in element:
121
+ # The FES XSD dictates the element ordering, but this is ignored here.
122
+ node = tag_registry.node_from_xml(child, allowed_types=ADHOC_QUERY_ELEMENTS)
123
+ if isinstance(node, PropertyName):
124
+ property_names.append(node)
125
+ elif isinstance(node, fes20.Filter):
126
+ filter = node
127
+ elif isinstance(node, fes20.SortBy):
128
+ sortBy = node
129
+ else:
130
+ raise NotImplementedError(
131
+ f"Parsing {node.__class__} not handled in AdhocQuery.from_xml()"
132
+ )
133
+
134
+ return AdhocQuery(
135
+ typeNames=type_names,
136
+ aliases=aliases.split(" ") if aliases is not None else None,
137
+ handle=element.attrib.get("handle", ""),
138
+ property_names=property_names or None,
139
+ filter=filter,
140
+ sortBy=sortBy,
141
+ srsName=CRS.from_string(srsName) if srsName else None,
142
+ )
143
+
144
+ @classmethod
145
+ def from_kvp_request(cls, kvp: KVPRequest):
146
+ """Build this object from an HTTP GET (key-value-pair) request.
147
+
148
+ Note the caller should have split the KVP request parameter lists.
149
+ This class only handles a single parameter pair.
150
+ """
151
+ # Parse attributes
152
+ typeNames = [
153
+ kvp.parse_qname(qname)
154
+ for qname in kvp.get_list("typeNames", alias="TYPENAME", default=[])
155
+ ]
156
+ aliases = kvp.get_list("aliases", default=None)
157
+ srsName = kvp.get_custom("srsName", default=None, parser=CRS.from_string)
158
+
159
+ # KVP requests may omit the typenames if RESOURCEID=... is given.
160
+ if not typeNames and "RESOURCEID" not in kvp:
161
+ raise MissingParameterValue("Empty TYPENAMES parameter", locator="typeNames")
162
+
163
+ # Parse elements
164
+ filter = fes20.Filter.from_kvp_request(kvp)
165
+ sort_by = fes20.SortBy.from_kvp_request(kvp)
166
+
167
+ # Parse projection
168
+ property_names = None
169
+ if "PROPERTYNAME" in kvp:
170
+ names = kvp.get_list("propertyName", default=[])
171
+ # Check for WFS 1.x syntax of ?PROPERTYNAME=*
172
+ if names != ["*"]:
173
+ property_names = [
174
+ PropertyName(xpath=name, xpath_ns_aliases=kvp.ns_aliases) for name in names
175
+ ]
176
+
177
+ return AdhocQuery(
178
+ # Attributes
179
+ typeNames=typeNames,
180
+ aliases=aliases,
181
+ srsName=srsName,
182
+ # Elements
183
+ property_names=property_names,
184
+ filter=filter,
185
+ sortBy=sort_by,
186
+ )
187
+
188
+ @cached_property
189
+ def query_locator(self):
190
+ """Overrides the 'query_locator' attribute, so the 'locator' argument is correctly set."""
191
+ if self.filter is not None and self.filter.get_resource_id_types():
192
+ return "resourceId"
193
+ else:
194
+ return "filter"
195
+
196
+ def get_type_names(self) -> list[str]:
197
+ """Tell which type names this query uses.
198
+ Multiple values means a JOIN is made (not supported yet).
199
+ """
200
+ if not self.typeNames and self.filter is not None:
201
+ # Also make the behavior consistent, always supply the type name.
202
+ return self.filter.get_resource_id_types() or []
203
+ else:
204
+ return self.typeNames
205
+
206
+ def get_projection(self) -> FeatureProjection:
207
+ """Tell how the ``<wfs:Query>`` element should be displayed."""
208
+ return FeatureProjection(
209
+ self.feature_types,
210
+ self.property_names,
211
+ self.value_reference,
212
+ output_crs=self.srsName,
213
+ )
214
+
215
+ def bind(self, *args, **kwargs):
216
+ """Override to make sure the 'locator' points to the actual object that defined the type."""
217
+ try:
218
+ super().bind(*args, **kwargs)
219
+ except InvalidParameterValue as e:
220
+ if not self.typeNames:
221
+ e.locator = "resourceId"
222
+ raise
223
+
224
+ # Validate the srsName too
225
+ if self.srsName is not None:
226
+ self.srsName = self.feature_types[0].resolve_crs(self.srsName, locator="srsName")
227
+
228
+ def build_query(self, compiler: CompiledQuery) -> Q | None:
229
+ """Apply our collected filter data to the compiler."""
230
+ # Add the sorting
231
+ if self.sortBy is not None:
232
+ self.sortBy.build_ordering(compiler)
233
+
234
+ if self.filter is not None:
235
+ # Generate the internal query object from the <fes:Filter>,
236
+ # this can return a Q object.
237
+ return self.filter.build_query(compiler)
238
+ else:
239
+ return None
240
+
241
+ def as_kvp(self) -> dict:
242
+ """Translate the POST request into KVP GET parameters. This is needed for pagination."""
243
+ params = super().as_kvp()
244
+ params["TYPENAMES"] = ",".join(self.typeNames)
245
+ if self.srsName is not None:
246
+ params["SRSNAME"] = str(self.srsName)
247
+
248
+ if self.filter is not None:
249
+ raise NotImplementedError() # not going to parse that, nor does mapserver
250
+ if self.sortBy is not None:
251
+ params["SORTBY"] = self.sortBy.as_kvp()
252
+
253
+ return params
@@ -0,0 +1,148 @@
1
+ """Entry point to handle queries.
2
+
3
+ WFS defines 2 query types:
4
+ - Adhoc queries are constructed directly from request parameters.
5
+ - Stored queries are defined first, and executed later.
6
+
7
+ Both use the FES (Filter Encoding Syntax) filtering logic internally.
8
+
9
+ The objects in this module closely follow the WFS spec.
10
+ By using the same type definitions, a lot of code logic follows naturally.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import ClassVar
16
+
17
+ from django.db.models import Q, QuerySet
18
+
19
+ from gisserver.exceptions import (
20
+ ExternalValueError,
21
+ InvalidParameterValue,
22
+ OperationNotSupported,
23
+ )
24
+ from gisserver.features import FeatureType
25
+ from gisserver.parsers import fes20, wfs20
26
+ from gisserver.parsers.ast import AstNode
27
+ from gisserver.parsers.query import CompiledQuery
28
+ from gisserver.projection import FeatureProjection
29
+
30
+
31
+ class QueryExpression(AstNode):
32
+ """WFS base class for all queries.
33
+ This object type is defined in the WFS spec (as ``<fes:AbstractQueryExpression>``).
34
+
35
+ The WFS server can initiate queries in multiple ways.
36
+ This class provides the common interface for all these query types;
37
+ whether the request provided "ad-hoc" parameters or called a stored procedure.
38
+ Each query type has its own way of generating the actual database statement to perform.
39
+
40
+ The subclasses can override the following logic:
41
+
42
+ * :meth:`get_type_names` defines which types this query applies to.
43
+ * :meth:`build_query` defines how to filter the queryset.
44
+
45
+ For full control, these methods can also be overwritten instead:
46
+
47
+ * :meth:`get_queryset` defines the full results.
48
+ """
49
+
50
+ #: Configuration for the 'locator' argument in exceptions
51
+ query_locator: ClassVar[str] = None
52
+
53
+ # QueryExpression
54
+ #: The 'handle' that will be returned in exceptions.
55
+ handle: str = ""
56
+
57
+ #: Projection parameters (overwritten by subclasses)
58
+ #: In the WFS spec, this is only part of the operation/presentation.
59
+ #: For Django, we'd like to make this part of the query too.
60
+ property_names: list[wfs20.PropertyName] | None = None # PropertyName
61
+
62
+ #: The valueReference for the GetPropertyValue call, provided here for extra ORM filtering.
63
+ value_reference: fes20.ValueReference | None = None
64
+
65
+ def bind(
66
+ self,
67
+ feature_types: list[FeatureType],
68
+ value_reference: fes20.ValueReference | None = None,
69
+ ):
70
+ """Bind the query to context outside its tag definition.
71
+
72
+ :param feature_types: The corresponding feature types for :meth:`get_type_names`.
73
+ :param value_reference: Which field is returned (by ``GetPropertyValue``)
74
+ """
75
+ # Store the resolved feature types
76
+ self.feature_types = feature_types
77
+
78
+ # for GetPropertyValue
79
+ if value_reference is not None:
80
+ self.value_reference = value_reference
81
+
82
+ def get_queryset(self) -> QuerySet:
83
+ """Generate the queryset for the specific feature type.
84
+
85
+ This method can be overwritten in subclasses to define the returned data.
86
+ However, consider overwriting :meth:`build_query` instead of simple data.
87
+ """
88
+ if len(self.feature_types) > 1:
89
+ raise OperationNotSupported("Join queries are not supported", locator="typeNames")
90
+
91
+ # To apply the filters, an internal CompiledQuery object is created.
92
+ # This collects all steps, to create the final QuerySet object.
93
+ # The build_query() method may return an Q object, or fill the compiler itself.
94
+ compiler = CompiledQuery(self.feature_types)
95
+ q_object = self.build_query(compiler)
96
+ if q_object is not None:
97
+ compiler.add_lookups(q_object)
98
+
99
+ # While property names are projection, this hook should
100
+ # make it possible to perform extra query adjustments for complex expressions.
101
+ if self.property_names:
102
+ for property_name in self.property_names:
103
+ property_name.decorate_query(compiler)
104
+
105
+ if self.value_reference is not None:
106
+ try:
107
+ self.value_reference.parse_xpath(self.feature_types)
108
+ except ExternalValueError as e:
109
+ raise InvalidParameterValue(
110
+ f"Field '{self.value_reference.xpath}' does not exist.",
111
+ locator="valueReference",
112
+ ) from e
113
+
114
+ # For GetPropertyValue, adjust the query so only that value is requested.
115
+ # This makes sure XPath attribute selectors are already handled by the
116
+ # database query, instead of being a presentation-layer handling.
117
+ # This supports cases like: ``addresses/Address[street="Oxfordstrasse"]/number``
118
+ field = self.value_reference.build_rhs(compiler)
119
+
120
+ queryset = compiler.get_queryset()
121
+ return queryset.values("pk", member=field)
122
+ else:
123
+ return compiler.get_queryset()
124
+
125
+ def get_type_names(self) -> list[str]:
126
+ """Tell which type names this query applies to.
127
+ Multiple values means a JOIN is made (not supported yet).
128
+
129
+ This method needs to be defined in subclasses.
130
+ """
131
+ raise NotImplementedError(
132
+ f"{self.__class__.__name__}.get_type_names() should be implemented."
133
+ )
134
+
135
+ def get_projection(self) -> FeatureProjection:
136
+ """Tell how the query should be displayed."""
137
+ raise NotImplementedError()
138
+
139
+ def build_query(self, compiler: CompiledQuery) -> Q | None:
140
+ """Define the compiled query that filters the queryset."""
141
+ raise NotImplementedError()
142
+
143
+ def as_kvp(self) -> dict:
144
+ """Translate the POST request into KVP GET parameters. This is needed for pagination."""
145
+ params = {}
146
+ if self.property_names:
147
+ params["PROPERTYNAME"] = ",".join(p.xpath for p in self.property_names)
148
+ return params
@@ -0,0 +1,103 @@
1
+ """Projection tag parsing for WFS 2.0"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+
8
+ from gisserver.exceptions import (
9
+ InvalidParameterValue,
10
+ OperationParsingFailed,
11
+ OperationProcessingFailed,
12
+ )
13
+ from gisserver.parsers.ast import AstNode, tag_registry
14
+ from gisserver.parsers.query import CompiledQuery
15
+ from gisserver.parsers.xml import NSElement, xmlns
16
+ from gisserver.types import XPathMatch
17
+
18
+
19
+ class ResolveValue(Enum):
20
+ """The ``wfs:ResolveValueType`` enum, used by :class:`StandardResolveParameters`."""
21
+
22
+ local = "local"
23
+ remote = "remote"
24
+ all = "all"
25
+ none = "none"
26
+
27
+ @classmethod
28
+ def _missing_(cls, value):
29
+ raise OperationParsingFailed(f"Invalid resolve value: {value}", locator="resolve")
30
+
31
+
32
+ @dataclass
33
+ @tag_registry.register("PropertyName", xmlns.wfs)
34
+ class PropertyName(AstNode):
35
+ """The ``<wfs:PropertyName>`` element in the projection clause.
36
+
37
+ This parses and handles the syntax::
38
+
39
+ <wfs:PropertyName>ns0:tagname</wfs:PropertyName>
40
+
41
+ More advanced syntax is not supported, such as::
42
+
43
+ <wfs:PropertyName resolve="all" resolvePath="valueOf(relatedTo)">valueOf(registerEntry)</wfs:PropertyName>
44
+
45
+ Note this element exists in the WFS namespace, not the FES namespace!
46
+ The ``<fes:PropertyName>`` is an old FES 1.x element that has been replaced by ``<fes:ValueReference>``.
47
+ The old ``<fes:PropertyName>`` is still supported as an alias by this server.
48
+ """
49
+
50
+ xpath: str # Officially only a 'QName'
51
+ xpath_ns_aliases: dict[str, str]
52
+
53
+ # Unused, yet included for completeness.
54
+ # These are available in the XML syntax, and override
55
+ # the default given in the <wfs:GetFeature> element.
56
+ resolve: ResolveValue = ResolveValue.none
57
+ resolve_depth: int | None = None
58
+ resolve_path: str | None = None
59
+ resolve_timeout: int = 300
60
+
61
+ @classmethod
62
+ def from_xml(cls, element: NSElement):
63
+ """Parse the XML tag."""
64
+ depth = parse_resolve_depth(element.attrib.get("resolveDepth", None))
65
+ return cls(
66
+ xpath=element.text,
67
+ resolve=ResolveValue[element.attrib.get("resolve", "none")],
68
+ resolve_depth=depth,
69
+ resolve_path=element.attrib.get("resolvePath"),
70
+ resolve_timeout=element.get_int_attribute("resolveTimeout", 300),
71
+ xpath_ns_aliases=element.ns_aliases,
72
+ )
73
+
74
+ def parse_xpath(self, feature_types: list) -> XPathMatch:
75
+ """Convert the XPath into the required ORM query elements."""
76
+ if self.resolve_path:
77
+ raise OperationProcessingFailed("resolvePath is not supported", locator="propertyName")
78
+ if "valueOf(" in self.xpath or (self.resolve_path and "valueOf(" in self.resolve_path):
79
+ raise OperationProcessingFailed(
80
+ "valueOf(..) syntax is not supported", locator="propertyName"
81
+ )
82
+
83
+ # Can resolve against XSD paths, find the correct DB field name
84
+ return feature_types[0].resolve_element(self.xpath, self.xpath_ns_aliases)
85
+
86
+ def decorate_query(self, compiler: CompiledQuery):
87
+ """Update the low-level query based on this property projection."""
88
+ # This will also validate the name because it resolves the ORM path.
89
+ # The actual limiting of fields happens inside the decorate_queryset() of the renderer.
90
+ xpath_match = self.parse_xpath(compiler.feature_types)
91
+ if xpath_match.orm_filters:
92
+ # Apply any xpath [attr=value] lookups.
93
+ compiler.add_extra_lookup(xpath_match.orm_filters)
94
+
95
+
96
+ def parse_resolve_depth(depth: str | None) -> int | None:
97
+ """Parse the resolve depth value."""
98
+ try:
99
+ return None if depth is None or depth == "*" else int(depth)
100
+ except (ValueError, TypeError) as e:
101
+ raise InvalidParameterValue(
102
+ "ResolveDepth must be an integer or '*'", locator="resolveDepth"
103
+ ) from e