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,266 @@
1
+ """Storage and registry for stored queries.
2
+ These definitions follow the WFS spec.
3
+
4
+ By using the :attr:`stored_query_registry`, custom stored queries can be registered in this server.
5
+ Out of the box, only the mandatory built-in :class:`GetFeatureById` query is present.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import typing
11
+ from collections.abc import Iterable, Iterator
12
+ from dataclasses import dataclass, field
13
+ from functools import partial
14
+ from xml.etree.ElementTree import Element
15
+
16
+ from django.db.models import Q
17
+
18
+ from gisserver.exceptions import InvalidParameterValue, NotFound, OperationNotSupported
19
+ from gisserver.features import FeatureType
20
+ from gisserver.parsers import fes20
21
+ from gisserver.parsers.query import CompiledQuery
22
+ from gisserver.types import XsdTypes
23
+
24
+ if typing.TYPE_CHECKING:
25
+ from gisserver.output import SimpleFeatureCollection
26
+
27
+ __all__ = (
28
+ "GetFeatureById",
29
+ "QueryExpressionText",
30
+ "StoredQueryDescription",
31
+ "StoredQueryRegistry",
32
+ "stored_query_registry",
33
+ "WFS_LANGUAGE",
34
+ "FES_LANGUAGE",
35
+ )
36
+
37
+ #: The query body is a ``<wfs:Query>``.
38
+ WFS_LANGUAGE = "urn:ogc:def:queryLanguage:OGC-WFS::WFS_QueryExpression"
39
+ #: The query body is a ``<fes:Filter>``.
40
+ FES_LANGUAGE = "urn:ogc:def:queryLanguage:OGC-FES:Filter"
41
+
42
+
43
+ @dataclass
44
+ class QueryExpressionText:
45
+ """Define the body of a stored query.
46
+
47
+ This object type is defined in the WFS spec.
48
+ It may contain a ``<wfs:Query>`` or ``<fes:Filter>`` element.
49
+ """
50
+
51
+ #: Which types the query will return.
52
+ return_feature_types: list[str] | None = None
53
+ #: The internal language of the query. Can be a WFS/FES-filter, or "python"
54
+ language: str = FES_LANGUAGE
55
+ #: Whether the implementation_text will be show or hidden from users.
56
+ is_private: bool = True
57
+
58
+ #: Body
59
+ implementation_text: str | Element | None = None
60
+
61
+
62
+ @dataclass
63
+ class StoredQueryDescription:
64
+ """WFS metadata of a stored query.
65
+ This is based on the ``<wfs:StoredQueryDescription>`` element,
66
+ and returned in ``DescribeStoredQueries``.
67
+
68
+ While it's possible to define multiple :class:`QueryExpressionText` nodes
69
+ as metadata to describe a query, there is still only one implementation.
70
+ Note there is no 'typeNames=...' parameter for stored queries.
71
+ Only direct parameters act as input.
72
+ """
73
+
74
+ #: The ID of the query
75
+ id: str
76
+ #: User-visible title
77
+ title: str
78
+ #: User-visible description
79
+ abstract: str
80
+ #: Parameter declaration
81
+ parameters: dict[str, XsdTypes]
82
+
83
+ #: Metadata describing the query body
84
+ expressions: list[QueryExpressionText] = field(
85
+ default_factory=lambda: [QueryExpressionText(language=FES_LANGUAGE)]
86
+ )
87
+
88
+ #: Python-based implementation for the query.
89
+ implementation_class: type[StoredQueryImplementation] = field(init=False, default=None)
90
+
91
+
92
+ class StoredQueryImplementation:
93
+ """A custom stored query.
94
+
95
+ This receives the parameters as init arguments,
96
+ and should implement :meth:`build_query`.
97
+ The function is registered using ``StoredQueryRegistry.register()``.
98
+ """
99
+
100
+ # Registered metadata
101
+ _meta: StoredQueryDescription
102
+
103
+ # Allow queries to return only the XML nodes, without any <wfs:FeatureCollection> wrapper.
104
+ has_standalone_output: bool = False
105
+
106
+ def __repr__(self):
107
+ return f"<{self.__class__.__name__} implementing '{self._meta.id}'>"
108
+
109
+ def bind(self, source_query, feature_types: list[FeatureType]):
110
+ """Associate this query with the application data."""
111
+ self.source_query = source_query
112
+ self.feature_types = feature_types
113
+ if len(feature_types) > 1:
114
+ raise OperationNotSupported("Join queries are not supported", locator="typeNames")
115
+
116
+ def get_type_names(self) -> list[str]:
117
+ """Tell which type names this query applies to."""
118
+ raise NotImplementedError()
119
+
120
+ def build_query(self, compiler: CompiledQuery) -> Q | None:
121
+ """Contribute our filter expression to the internal query.
122
+
123
+ This should add the filter expressions to the internal query compiler.
124
+ The top-level ``<wfs:StoredQuery>`` object will add
125
+ the ``<wfs:PropertyName>`` logic and other elements.
126
+ """
127
+ raise NotImplementedError()
128
+
129
+ def finalize_results(self, result: SimpleFeatureCollection):
130
+ """Hook to allow subclasses to inspect the results."""
131
+
132
+
133
+ class StoredQueryRegistry:
134
+ """Registry of functions to be callable by ``<wfs:StoredQuery>`` and ``STOREDQUERY_ID=...``."""
135
+
136
+ def __init__(self):
137
+ self.stored_queries: dict[str, type(StoredQueryImplementation)] = {}
138
+
139
+ def __bool__(self):
140
+ return bool(self.stored_queries)
141
+
142
+ def __iter__(self) -> Iterator[StoredQueryDescription]:
143
+ return iter(self.stored_queries.values())
144
+
145
+ def get_queries(self) -> Iterable[StoredQueryDescription]:
146
+ """Find all descriptions for stored queries."""
147
+ return self.stored_queries.values()
148
+
149
+ def register(
150
+ self,
151
+ meta: StoredQueryDescription | None = None,
152
+ query_expression: type[StoredQueryImplementation] | None = None,
153
+ **meta_kwargs,
154
+ ):
155
+ """Register a custom class that handles a stored query.
156
+ This function can be used as decorator or normal call.
157
+ """
158
+ if meta is None:
159
+ meta = StoredQueryDescription(**meta_kwargs)
160
+ elif meta_kwargs:
161
+ raise TypeError("Either provide the 'meta' object or 'meta_kwargs'")
162
+
163
+ if query_expression is not None:
164
+ return self._register(meta, query_expression)
165
+ else:
166
+ return partial(self._register, meta) # decorator effect.
167
+
168
+ def _register(
169
+ self, meta: StoredQueryDescription, implementation_class: type[StoredQueryImplementation]
170
+ ):
171
+ """Internal registration method."""
172
+ if not issubclass(implementation_class, StoredQueryImplementation):
173
+ raise TypeError(f"Expecting {StoredQueryImplementation}' subclass")
174
+ if meta.implementation_class is not None:
175
+ raise RuntimeError("Can't register same StoredQueryDescription again.")
176
+
177
+ # for now link both. There is always a single implementation for the metadata.
178
+ meta.implementation_class = implementation_class
179
+ self.stored_queries[meta.id] = meta
180
+ return implementation_class # for decorator usage
181
+
182
+ def resolve_query(self, query_id) -> type[StoredQueryDescription]:
183
+ """Find the stored procedure using the ID."""
184
+ try:
185
+ return self.stored_queries[query_id]
186
+ except KeyError:
187
+ raise InvalidParameterValue(
188
+ f"Stored query does not exist: {query_id}",
189
+ locator="STOREDQUERY_ID",
190
+ ) from None
191
+
192
+
193
+ #: The stored query registry
194
+ stored_query_registry = StoredQueryRegistry()
195
+
196
+
197
+ @stored_query_registry.register(
198
+ id="urn:ogc:def:query:OGC-WFS::GetFeatureById",
199
+ title="Get feature by identifier",
200
+ abstract="Returns the single feature that corresponds with the ID argument",
201
+ parameters={"id": XsdTypes.string},
202
+ expressions=[QueryExpressionText(language=WFS_LANGUAGE)],
203
+ )
204
+ class GetFeatureById(StoredQueryImplementation):
205
+ """The stored query for GetFeatureById.
206
+
207
+ This can be called using::
208
+
209
+ <wfs:StoredQuery id="urn:ogc:def:query:OGC-WFS::GetFeatureById">
210
+ <wfs:Parameter name="ID">typename.ID</wfs:Parameter>
211
+ </wfs:StoredQuery>
212
+
213
+ or using KVP syntax::
214
+
215
+ ?...&REQUEST=GetFeature&STOREDQUERY_ID=urn:ogc:def:query:OGC-WFS::GetFeatureById&ID=typename.ID
216
+
217
+ The execution of the ``GetFeatureById`` query is essentially the same as::
218
+
219
+ <wfs:Query xmlns:wfs="..." xmlns:fes="...'>
220
+ <fes:Filter><fes:ResourceId rid='{ID}'/></fes:Filter>
221
+ </wfs:Query>
222
+
223
+ or::
224
+
225
+ <wfs:Query typeName="{typename}">
226
+ <fes:Filter>
227
+ <fes:PropertyIsEqualTo>
228
+ <fes:ValueReference>{primary-key-field}</fes:ValueReference>
229
+ <fes:Literal>{ID-value}</fes:Literal>
230
+ </fes:PropertyIsEqualTo>
231
+ </fes:Filter>
232
+ </wfs:Query>
233
+
234
+ Except that the response is supposed to contain only the item itself.
235
+ """
236
+
237
+ # Projection of GetFeatureById only returns the document nodes, not a <wfs:FeatureCollection> wrapper
238
+ has_standalone_output = True
239
+
240
+ def __init__(self, id: str, ns_aliases: dict[str, str]):
241
+ """Initialize the query with the request parameters."""
242
+ if "." not in id:
243
+ # Always report this as 404
244
+ raise NotFound("Expected typeName.id for ID parameter", locator="ID") from None
245
+
246
+ # GetFeatureById is essentially a ResourceId lookup, reuse that logic here.
247
+ self.resource_id = fes20.ResourceId.from_string(id, ns_aliases)
248
+
249
+ def get_type_names(self) -> list[str]:
250
+ """Tell which type names this query applies to."""
251
+ return [self.resource_id.get_type_name()]
252
+
253
+ def build_query(self, compiler: CompiledQuery) -> Q:
254
+ """Contribute our filter expression to the internal query."""
255
+ try:
256
+ return self.resource_id.build_query(compiler)
257
+ except InvalidParameterValue as e:
258
+ raise InvalidParameterValue(f"Invalid ID value: {e.__cause__}", locator="ID") from e
259
+
260
+ def finalize_results(self, results: SimpleFeatureCollection):
261
+ """Override to implement 404 checking."""
262
+ # Directly attempt to collect the data.
263
+ # Avoid having to do that in the output renderer.
264
+ if results.first() is None:
265
+ # WFS 2.0.2: Return NotFound instead of InvalidParameterValue
266
+ raise NotFound(f"Feature not found with ID {self.resource_id.rid}.", locator="ID")