django-gisserver 1.3.0__py3-none-any.whl → 1.4.0__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 (41) hide show
  1. {django_gisserver-1.3.0.dist-info → django_gisserver-1.4.0.dist-info}/METADATA +12 -15
  2. django_gisserver-1.4.0.dist-info/RECORD +54 -0
  3. {django_gisserver-1.3.0.dist-info → django_gisserver-1.4.0.dist-info}/WHEEL +1 -1
  4. gisserver/__init__.py +1 -1
  5. gisserver/conf.py +9 -12
  6. gisserver/db.py +6 -10
  7. gisserver/exceptions.py +1 -0
  8. gisserver/features.py +18 -29
  9. gisserver/geometries.py +11 -25
  10. gisserver/operations/base.py +19 -40
  11. gisserver/operations/wfs20.py +8 -20
  12. gisserver/output/__init__.py +7 -2
  13. gisserver/output/base.py +4 -13
  14. gisserver/output/csv.py +13 -16
  15. gisserver/output/geojson.py +14 -17
  16. gisserver/output/gml32.py +309 -281
  17. gisserver/output/gml32_lxml.py +612 -0
  18. gisserver/output/results.py +105 -22
  19. gisserver/output/utils.py +15 -5
  20. gisserver/output/xmlschema.py +7 -8
  21. gisserver/parsers/base.py +2 -4
  22. gisserver/parsers/fes20/expressions.py +6 -13
  23. gisserver/parsers/fes20/filters.py +6 -5
  24. gisserver/parsers/fes20/functions.py +4 -4
  25. gisserver/parsers/fes20/identifiers.py +1 -0
  26. gisserver/parsers/fes20/operators.py +16 -43
  27. gisserver/parsers/fes20/query.py +1 -3
  28. gisserver/parsers/fes20/sorting.py +1 -3
  29. gisserver/parsers/gml/__init__.py +1 -0
  30. gisserver/parsers/gml/base.py +1 -0
  31. gisserver/parsers/values.py +1 -3
  32. gisserver/queries/__init__.py +1 -0
  33. gisserver/queries/adhoc.py +3 -6
  34. gisserver/queries/base.py +2 -6
  35. gisserver/queries/stored.py +3 -6
  36. gisserver/types.py +59 -46
  37. gisserver/views.py +7 -10
  38. django_gisserver-1.3.0.dist-info/RECORD +0 -54
  39. gisserver/output/buffer.py +0 -64
  40. {django_gisserver-1.3.0.dist-info → django_gisserver-1.4.0.dist-info}/LICENSE +0 -0
  41. {django_gisserver-1.3.0.dist-info → django_gisserver-1.4.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ such as the "FILTER", "BBOX" and "RESOURCEID" parameters.
5
5
 
6
6
  These definitions follow the WFS spec.
7
7
  """
8
+
8
9
  from __future__ import annotations
9
10
 
10
11
  import logging
@@ -95,9 +96,7 @@ class AdhocQuery(QueryExpression):
95
96
  if id_type_names:
96
97
  # Only test when the RESOURCEID has a typename.id format
97
98
  # Otherwise, this breaks the CITE RESOURCEID=test-UUID parameter.
98
- kvp_type_names = {
99
- feature_type.name for feature_type in params["typeNames"]
100
- }
99
+ kvp_type_names = {feature_type.name for feature_type in params["typeNames"]}
101
100
  if not kvp_type_names.issuperset(id_type_names):
102
101
  raise InvalidParameterValue(
103
102
  "resourceID",
@@ -133,9 +132,7 @@ class AdhocQuery(QueryExpression):
133
132
  def get_type_names(self):
134
133
  return self.typeNames
135
134
 
136
- def compile_query(
137
- self, feature_type: FeatureType, using=None
138
- ) -> fes20.CompiledQuery:
135
+ def compile_query(self, feature_type: FeatureType, using=None) -> fes20.CompiledQuery:
139
136
  """Return our internal CompiledQuery object that can be applied to the queryset."""
140
137
  if self.filter:
141
138
  # Generate the internal query object from the <fes:Filter>
gisserver/queries/base.py CHANGED
@@ -69,9 +69,7 @@ class QueryExpression:
69
69
  results=[
70
70
  # Include empty feature collections,
71
71
  # so the selected feature types are still known.
72
- SimpleFeatureCollection(
73
- feature_type=ft, queryset=qs.none(), start=0, stop=0
74
- )
72
+ SimpleFeatureCollection(feature_type=ft, queryset=qs.none(), start=0, stop=0)
75
73
  for ft, qs in querysets
76
74
  ],
77
75
  number_matched=sum(qs.count() for ft, qs in querysets),
@@ -139,9 +137,7 @@ class QueryExpression:
139
137
  f"{self.__class__.__name__}.get_type_names() should be implemented."
140
138
  )
141
139
 
142
- def compile_query(
143
- self, feature_type: FeatureType, using=None
144
- ) -> fes20.CompiledQuery:
140
+ def compile_query(self, feature_type: FeatureType, using=None) -> fes20.CompiledQuery:
145
141
  """Define the compiled query that filters the queryset.
146
142
 
147
143
  Subclasses need to define this method, unless
@@ -2,6 +2,7 @@
2
2
 
3
3
  These definitions follow the WFS spec.
4
4
  """
5
+
5
6
  from __future__ import annotations
6
7
 
7
8
  from dataclasses import dataclass
@@ -131,9 +132,7 @@ class StoredQueryParameter(Parameter):
131
132
  """Special parameter parsing for the 'STOREDQUERY_ID' parameter"""
132
133
 
133
134
  def __init__(self):
134
- super().__init__(
135
- name="STOREDQUERY_ID", parser=stored_query_registry.resolve_query
136
- )
135
+ super().__init__(name="STOREDQUERY_ID", parser=stored_query_registry.resolve_query)
137
136
 
138
137
  def value_from_query(self, KVP: dict):
139
138
  """Customize the request parsing to read custom parameters too."""
@@ -199,9 +198,7 @@ class GetFeatureById(StoredQuery):
199
198
 
200
199
  return collection
201
200
 
202
- def compile_query(
203
- self, feature_type: FeatureType, using=None
204
- ) -> fes20.CompiledQuery:
201
+ def compile_query(self, feature_type: FeatureType, using=None) -> fes20.CompiledQuery:
205
202
  """Create the internal query object that will be applied to the queryset."""
206
203
  compiler = fes20.CompiledQuery(feature_type=feature_type)
207
204
  compiler.add_lookups(Q(pk=self.id), type_name=self.type_name)
gisserver/types.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Internal XSD type definitions.
2
2
 
3
- These types are the internal schema definition on which all output is generated.
3
+ These types are the internal schema definition, and the foundation for all output generation.
4
4
 
5
5
  The end-users of this library typically create a WFS feature type definition by using
6
6
  the :class:~gisserver.features.FeatureType` / :class:`~gisserver.features.FeatureField` classes.
@@ -23,6 +23,7 @@ Each XMLSchema node defines it's "data type" as either:
23
23
 
24
24
  Custom field types could also generate these field types.
25
25
  """
26
+
26
27
  from __future__ import annotations
27
28
 
28
29
  import operator
@@ -30,6 +31,7 @@ import re
30
31
  from dataclasses import dataclass, field
31
32
  from decimal import Decimal as D
32
33
  from enum import Enum
34
+ from functools import cached_property
33
35
  from typing import TYPE_CHECKING
34
36
 
35
37
  from django.conf import settings
@@ -42,7 +44,6 @@ from django.db.models.fields.related import ( # Django 2 imports
42
44
  RelatedField,
43
45
  )
44
46
  from django.utils import dateparse
45
- from django.utils.functional import cached_property
46
47
 
47
48
  from gisserver.exceptions import ExternalParsingError, OperationProcessingFailed
48
49
  from gisserver.geometries import CRS, WGS84 # noqa: F401 / for backwards compatibility
@@ -146,7 +147,7 @@ class XsdTypes(XsdAnyType, Enum):
146
147
  gYear = "gYear"
147
148
  hexBinary = "hexBinary"
148
149
  base64Binary = "base64Binary"
149
- token = "token"
150
+ token = "token" # noqa: S105
150
151
  language = "language"
151
152
 
152
153
  # Types that contain a GML value as member:
@@ -193,9 +194,7 @@ class XsdTypes(XsdAnyType, Enum):
193
194
  try:
194
195
  return TYPES_TO_PYTHON[self]
195
196
  except KeyError:
196
- raise NotImplementedError(
197
- f'Casting to "{self}" is not implemented.'
198
- ) from None
197
+ raise NotImplementedError(f'Casting to "{self}" is not implemented.') from None
199
198
 
200
199
  def to_python(self, raw_value):
201
200
  """Convert a raw string value to this type representation"""
@@ -219,7 +218,9 @@ def _init_types_to_python():
219
218
  global TYPES_TO_PYTHON
220
219
  from gisserver.parsers import values # avoid cyclic import
221
220
 
222
- as_is = lambda v: v
221
+ def as_is(v):
222
+ return v
223
+
223
224
  TYPES_TO_PYTHON = {
224
225
  XsdTypes.date: dateparse.parse_date,
225
226
  XsdTypes.dateTime: values.parse_iso_datetime,
@@ -300,29 +301,40 @@ class XsdNode:
300
301
  model_attribute: str,
301
302
  field: models.Field | ForeignObjectRel | None,
302
303
  ):
303
- """Select the most efficient read function to retrieves the value."""
304
- if field is None:
305
- # No model field, can only use getattr(). The attrgetter function has
306
- # built-in support for traversing model attributes with dots.
307
- return operator.attrgetter(model_attribute)
308
- elif isinstance(field, ForeignObjectRel):
309
- # Special handling, has no value_from_object()
304
+ """Select the most efficient read function to retrieves the value.
305
+
306
+ Since reading a value can be called like 150000+ times, this is heavily optimized.
307
+
308
+ This attempts to use ``operator.attrgetter()`` whenever possible,
309
+ since this will be much faster than using ``getattr()``.
310
+ The custom ``value_from_object()`` is fully supported too.
311
+ """
312
+ if field is None or isinstance(field, ForeignObjectRel):
313
+ # No model field, can only use getattr(). The attrgetter() function is both faster,
314
+ # and has built-in support for traversing model attributes with dots.
310
315
  return operator.attrgetter(model_attribute)
311
- elif "." not in model_attribute:
312
- # Shortcut, can just use Django's value_from_object.
313
- # This allows Django fields to override the object retrieval.
314
- # Not using value_to_string() as different output formats may serialize differently.
315
- return field.value_from_object
316
- else:
317
- # Need to traverse foreign key relations before using value_from_objec().
316
+
317
+ if field.value_from_object.__func__ is models.Field.value_from_object:
318
+ # No custom value_from_object(), this can be fully emulated with attrgetter() too.
319
+ # Still allow the final node to have a custom attname,
320
+ # # which is what Field.value_from_object() does.
318
321
  names = model_attribute.split(".")
322
+ names[-1] = field.attname
323
+ return operator.attrgetter(".".join(names))
324
+
325
+ if "." not in model_attribute:
326
+ # Single level, use the custom value_from_object() directly.
327
+ return field.value_from_object
328
+
329
+ # Need to traverse the related field path, and use value_from_object() on the final object.
330
+ names = model_attribute.split(".")
331
+ get_related_instance = operator.attrgetter(".".join(names[:-1]))
319
332
 
320
- def _related_get_value_from_object(instance):
321
- for name in names[:-1]:
322
- instance = getattr(instance, name)
323
- return field.value_from_object(instance)
333
+ def _related_get_value_from_object(instance):
334
+ related_instance = get_related_instance(instance)
335
+ return field.value_from_object(related_instance)
324
336
 
325
- return _related_get_value_from_object
337
+ return _related_get_value_from_object
326
338
 
327
339
  @cached_property
328
340
  def is_geometry(self) -> bool:
@@ -397,13 +409,13 @@ class XsdNode:
397
409
  value = value.all()
398
410
  return value
399
411
  else:
400
- # Support value_from_object() on custom fields.
401
- return self.format_value(self._valuegetter(instance))
412
+ # the _valuegetter() supports value_from_object() on custom fields.
413
+ return self._valuegetter(instance)
402
414
  except (AttributeError, ObjectDoesNotExist):
403
415
  # E.g. Django foreign keys that point to a non-existing member.
404
416
  return None
405
417
 
406
- def format_value(self, value):
418
+ def format_raw_value(self, value):
407
419
  """Allow to apply some final transformations on a value.
408
420
  This is mainly used to support @gml:id which includes a prefix.
409
421
  """
@@ -426,9 +438,7 @@ class XsdNode:
426
438
  code=e.code,
427
439
  ) from e
428
440
  except (ValueError, TypeError) as e:
429
- raise ValidationError(
430
- f"Invalid data for the '{self.name}' property: {e}"
431
- ) from e
441
+ raise ValidationError(f"Invalid data for the '{self.name}' property: {e}") from e
432
442
 
433
443
  return raw_value
434
444
 
@@ -493,9 +503,7 @@ class XsdElement(XsdNode):
493
503
  source: models.Field | ForeignObjectRel | None = None,
494
504
  model_attribute: str | None = None,
495
505
  ):
496
- super().__init__(
497
- name, type, prefix=prefix, source=source, model_attribute=model_attribute
498
- )
506
+ super().__init__(name, type, prefix=prefix, source=source, model_attribute=model_attribute)
499
507
  self.nillable = nillable
500
508
  self.min_occurs = min_occurs
501
509
  self.max_occurs = max_occurs
@@ -560,9 +568,7 @@ class XsdAttribute(XsdNode):
560
568
  source: models.Field | ForeignObjectRel | None = None,
561
569
  model_attribute: str | None = None,
562
570
  ):
563
- super().__init__(
564
- name, type, prefix=prefix, source=source, model_attribute=model_attribute
565
- )
571
+ super().__init__(name, type, prefix=prefix, source=source, model_attribute=model_attribute)
566
572
  self.use = use
567
573
 
568
574
 
@@ -579,16 +585,14 @@ class GmlIdAttribute(XsdAttribute):
579
585
  source: models.Field | ForeignObjectRel | None = None,
580
586
  model_attribute="pk",
581
587
  ):
582
- super().__init__(
583
- prefix="gml", name="id", source=source, model_attribute=model_attribute
584
- )
588
+ super().__init__(prefix="gml", name="id", source=source, model_attribute=model_attribute)
585
589
  object.__setattr__(self, "type_name", type_name)
586
590
 
587
591
  def get_value(self, instance: models.Model):
588
592
  pk = super().get_value(instance) # handle dotted-name notations
589
593
  return f"{self.type_name}.{pk}"
590
594
 
591
- def format_value(self, value):
595
+ def format_raw_value(self, value):
592
596
  """Format the value as retrieved from the database."""
593
597
  return f"{self.type_name}.{value}"
594
598
 
@@ -671,7 +675,7 @@ class XsdComplexType(XsdAnyType):
671
675
 
672
676
  Typically, this maps into a Django model, with each element pointing to a model field.
673
677
 
674
- The complex can hold multiple :class:`XsdElement` and :class:`XsdAttribute`
678
+ A complex type can hold multiple :class:`XsdElement` and :class:`XsdAttribute`
675
679
  nodes as children, composing an object. The elements themselves can point
676
680
  to a complex type themselves, to create a nested class structure.
677
681
  That also allows embedding models with their relations into a single response.
@@ -719,6 +723,17 @@ class XsdComplexType(XsdAnyType):
719
723
  def is_complex_type(self):
720
724
  return True # a property to avoid being used as field.
721
725
 
726
+ @cached_property
727
+ def all_elements(self) -> list[XsdElement]:
728
+ """All elements, including inherited elements."""
729
+ if self.base.is_complex_type:
730
+ # Add all base class members, in their correct ordering
731
+ # By having these as XsdElement objects instead of hard-coded writes,
732
+ # the query/filter logic also works for these elements.
733
+ return self.base.elements + self.elements
734
+ else:
735
+ return self.elements
736
+
722
737
  @cached_property
723
738
  def geometry_elements(self) -> list[XsdElement]:
724
739
  """Shortcut to get all geometry elements"""
@@ -890,9 +905,7 @@ class XPathMatch(ORMPath):
890
905
  if "[" in self.query:
891
906
  # If there is an element[@attr=...]/field tag,
892
907
  # the build_...() logic should return a Q() object.
893
- raise NotImplementedError(
894
- f"Complex XPath queries are not supported yet: {self.query}"
895
- )
908
+ raise NotImplementedError(f"Complex XPath queries are not supported yet: {self.query}")
896
909
 
897
910
  @cached_property
898
911
  def orm_path(self) -> str:
gisserver/views.py CHANGED
@@ -1,12 +1,12 @@
1
1
  """The view layer parses the request, and dispatches it to an operation."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import re
5
6
  from urllib.parse import urlencode
6
7
 
7
- from django.core.exceptions import ImproperlyConfigured
8
+ from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
8
9
  from django.core.exceptions import PermissionDenied as Django_PermissionDenied
9
- from django.core.exceptions import SuspiciousOperation
10
10
  from django.http import HttpResponse
11
11
  from django.shortcuts import render
12
12
  from django.views import View
@@ -86,6 +86,8 @@ class GISView(View):
86
86
  status=exc.status_code,
87
87
  reason=exc.reason,
88
88
  )
89
+ else:
90
+ return None
89
91
 
90
92
  def get(self, request, *args, **kwargs):
91
93
  """Entry point to handle HTTP GET requests.
@@ -129,9 +131,7 @@ class GISView(View):
129
131
  """Render the index page."""
130
132
  # Not using the whole TemplateResponseMixin config with configurable parameters.
131
133
  # If this really needs more configuration, overriding is likely just as easy.
132
- return render(
133
- self.request, self.get_index_template_names(), self.get_index_context_data()
134
- )
134
+ return render(self.request, self.get_index_template_names(), self.get_index_context_data())
135
135
 
136
136
  def get_index_context_data(self, **kwargs):
137
137
  """Provide the context data for the index page."""
@@ -143,8 +143,7 @@ class GISView(View):
143
143
  base_qs = {
144
144
  key: value
145
145
  for key, value in self.request.GET.items()
146
- if key.upper()
147
- not in ("SERVICE", "REQUEST", "VERSION", "OUTPUTFORMAT", "TYPENAMES")
146
+ if key.upper() not in ("SERVICE", "REQUEST", "VERSION", "OUTPUTFORMAT", "TYPENAMES")
148
147
  }
149
148
  base_query = urlencode(base_qs) + "&" if base_qs else ""
150
149
 
@@ -173,9 +172,7 @@ class GISView(View):
173
172
 
174
173
  if self.index_template_name:
175
174
  # Allow the same substitutions for a manually configured template.
176
- return [
177
- self.index_template_name.format(service=service, version=self.version)
178
- ]
175
+ return [self.index_template_name.format(service=service, version=self.version)]
179
176
  else:
180
177
  return [
181
178
  f"gisserver/{service}/{self.version}/index.html",
@@ -1,54 +0,0 @@
1
- gisserver/__init__.py,sha256=BfdKG3T6jfPrJ5AF8bDHgTsS70jBTw-Eg3IjeDKAlPQ,40
2
- gisserver/conf.py,sha256=DC3TNBhA2kcLxTwmzhmrW5Noiv2QsPQms_EAAZLtO-4,2108
3
- gisserver/db.py,sha256=4NSxzwCLsTON2wrLBKZweg6kOlZelV4fzdsrMGpC7V8,5329
4
- gisserver/exceptions.py,sha256=kf6o-Vk9CrAAcjk4uGlQVvXnuYOBVks4pcQly3pKRPU,4596
5
- gisserver/features.py,sha256=XKLs0WrrT-7Hwc2ByUYuOMOInzFONP9KpfGS065mIKI,32232
6
- gisserver/geometries.py,sha256=PEmYKV8yw1fjwL6ortc1w72UOByRHz88NDEyPuO3qFo,12619
7
- gisserver/types.py,sha256=dfybev3kq1IO3jaIy9uETOqHwqQOzEG94fKEbQB-AEQ,34202
8
- gisserver/views.py,sha256=Z55WA8Qc5OpP0ONPMPGqs-66w0ZR9XpDs9fJ8toqda4,13252
9
- gisserver/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- gisserver/operations/base.py,sha256=XFW9Y9oOHzdPou3cfZQ2UWCZbDba23MBydbN6owfomc,17012
11
- gisserver/operations/wfs20.py,sha256=J54vnxEE_8Myz5uwnjltElun_H1MvKQNeWZNjIWiDF4,18635
12
- gisserver/output/__init__.py,sha256=kWdJHxKtQNKK4Bld8uNdVqk3cjgpiagBL_FlMjsbjQc,1950
13
- gisserver/output/base.py,sha256=W4017N2qlEuPCbXLl17SQUVfTpTZphiSURd75NR9Hac,7954
14
- gisserver/output/buffer.py,sha256=Jqw94wa1YFcKbFB9ZLiYx2L0TRD1MDZNLF5te3QBqgU,1542
15
- gisserver/output/csv.py,sha256=sVj1U1dVkMqm-cwjGbnDg-YLQL1QcRMXyH6LqgC7tN4,6179
16
- gisserver/output/geojson.py,sha256=a8A628p1PEF-fmKdm3CrGDyCRPZdukMZRNzjiqDosWY,11217
17
- gisserver/output/gml32.py,sha256=_IN5ehNLGPk5z3euIxavukjYsoHn6liPqY__Ge7LpjY,26894
18
- gisserver/output/results.py,sha256=JdOznMx1xDxSmekMWW5YyF_muzrJXd6DVy0qqs2ewKI,9224
19
- gisserver/output/utils.py,sha256=fMUPnAKXiT5IX3GlO0amZKzd_8ixNG_g1lgmZ3aNyGo,6113
20
- gisserver/output/xmlschema.py,sha256=JVYx5ZXeIt4v8HLsEcRg5dR9WcrOREctlGaHcn31rEw,4603
21
- gisserver/parsers/__init__.py,sha256=g72D8EtR-QkhcemzY5-Qty-nhHPKza83Sz3KZm8bfq0,290
22
- gisserver/parsers/base.py,sha256=-XI2_ryoZ8tOIgckp71eEiJDoLxCXN2fQK8yY6gKNJ0,4864
23
- gisserver/parsers/tags.py,sha256=NyJhbt-U2zTewfhghH4LRslTaDK2UOq9Vgk83pWfJh0,3553
24
- gisserver/parsers/values.py,sha256=YtkP2QFkV6T8vOWcze7b5goydXgzYuwMIjuMFdGRrA4,1038
25
- gisserver/parsers/fes20/__init__.py,sha256=l8OutRicZ8E2cpyFtJj6wTFNArYoFaLiXABGyZYyKvQ,624
26
- gisserver/parsers/fes20/expressions.py,sha256=aweq4NnFvbkqe5Ba5zazvkMkpoBZqI0jNeDAG2QYqJE,9747
27
- gisserver/parsers/fes20/filters.py,sha256=HBiGh_4Ddw3rY5TKxHtXFnkJe_80fbtVvfmR_QVM52Q,3507
28
- gisserver/parsers/fes20/functions.py,sha256=GWiOmixNXqP2NvZ70GBnRAjqILwpfYKJggVc-LhnsyY,8726
29
- gisserver/parsers/fes20/identifiers.py,sha256=KRkYj-fWJUZJlde7GY8uG2Ii5f5DERodXbaJMBQg2TQ,2863
30
- gisserver/parsers/fes20/operators.py,sha256=eZu9Wx2ZTTKpy61FiA3XmO6RzRBhRw78Gxmy69vOmiY,23601
31
- gisserver/parsers/fes20/query.py,sha256=_jWNfVEiis_JwJwOshPwqRTecm6zJ8Dm5CKssDiKtio,10083
32
- gisserver/parsers/fes20/sorting.py,sha256=jBz52eaY0-JS6q16D2eGCA5IvvjwlQv3oHsxEFqYsH0,2176
33
- gisserver/parsers/gml/__init__.py,sha256=4t15m7VxqgOOAh9ybAVEISiZyKaDL4xyBEJfPCil6eA,1462
34
- gisserver/parsers/gml/base.py,sha256=ct2bl5Q2P9gDtXg4gJcsm02cmc6fiv_x3n0R6IgJfW8,1065
35
- gisserver/parsers/gml/geometries.py,sha256=cgV3WogA7LyC_H_NF7dGoGLvLdQRACG6AlThRkX_jhU,3171
36
- gisserver/queries/__init__.py,sha256=HI_J_kv60fc7wYaaNLz7_yeLz-9Y1Q_p7PDmMa2sIiw,892
37
- gisserver/queries/adhoc.py,sha256=gVgi7cel0pjQo8AyiDOd53qHpFSZxCnXDuMP4N1IXSw,7146
38
- gisserver/queries/base.py,sha256=-vf6bufYbTDq41PBjoV7N7R9sdPBPta8tKIBVrUY1Nc,5943
39
- gisserver/queries/stored.py,sha256=ncxiDo-kc5v4WKyPTAd4uOnyFPN-y1N9SW6Zg1Xazvg,7095
40
- gisserver/static/gisserver/index.css,sha256=fiQuCMuiWUhhrNQD6bbUJaX5vH5OpG9qgPdELPB8cpI,163
41
- gisserver/templates/gisserver/index.html,sha256=yliku8jDqAOcenkbe1YnvJBnuS13MvzzsoDV8sQZ3GM,866
42
- gisserver/templates/gisserver/service_description.html,sha256=3EHLnQx27_nTDZFD2RH6E10EWnTN90s1DGuEj9PeBrg,993
43
- gisserver/templates/gisserver/wfs/feature_field.html,sha256=H5LsTAxGVGQqEpOZdEUTszQmFAZSpB9ggRJUsTdjYnw,538
44
- gisserver/templates/gisserver/wfs/feature_type.html,sha256=pIfa1cMsTlNIrzNiEOaq9LjzzF7_vZ9C7eN6KDBLOHE,788
45
- gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml,sha256=4Hxw1kCnxfWXTVPbHf8IW9_qiSeIEtnxHK6z6TS3-zA,1041
46
- gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml,sha256=KKrI0YYQqZWtTOyOkm-ZHuNyfwipdx0i0N8p7fggPU8,8675
47
- gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml,sha256=CIBVQrKXmGMQ6BKbX4_0ru4lXtWnW5EIgHUbIi1V0Vc,636
48
- gisserver/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
- gisserver/templatetags/gisserver_tags.py,sha256=RFGqEj-EQi9FrJiFJ7HHAQqlU4fDKVKTtZrPNwURXgA,204
50
- django_gisserver-1.3.0.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
51
- django_gisserver-1.3.0.dist-info/METADATA,sha256=N9SdE0qRO4HBR8MW1XiTo45KOvPeFWTA_k3p0q_iH5s,5834
52
- django_gisserver-1.3.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
53
- django_gisserver-1.3.0.dist-info/top_level.txt,sha256=8zFCEMmkpixE4TPiOAlxSq3PD2EXvAeFKwG4yZoIIOQ,10
54
- django_gisserver-1.3.0.dist-info/RECORD,,
@@ -1,64 +0,0 @@
1
- """Output buffering, to write data in chunks.
2
-
3
- This builds on top of StringIO/BytesIO to write data and regularly flush it.
4
- """
5
- from __future__ import annotations
6
-
7
- import io
8
- from typing import Generic, TypeVar
9
-
10
- V = TypeVar("V", str, bytes)
11
-
12
-
13
- class BaseBuffer(Generic[V]):
14
- """Fast buffer to write data in chunks.
15
- This avoids performing too many yields in the output writing.
16
- Especially for GeoJSON, that slows down the response times.
17
- """
18
-
19
- buffer_class = None
20
-
21
- def __init__(self, chunk_size=8192):
22
- self.data = self.buffer_class()
23
- self.chunk_size = chunk_size
24
-
25
- def is_full(self):
26
- # Calling data.seek() is faster than doing self.size += len(value)
27
- # because each integer increment is a new object allocation.
28
- return self.data.tell() >= self.chunk_size
29
-
30
- def write(self, value: V):
31
- if value is None:
32
- return
33
- self.data.write(value)
34
-
35
- def flush(self) -> V:
36
- """Empty the buffer and return it."""
37
- data = self.getvalue()
38
- self.clear()
39
- return data
40
-
41
- def getvalue(self) -> V:
42
- return self.data.getvalue()
43
-
44
- def clear(self):
45
- self.data.seek(0)
46
- self.data.truncate(0)
47
-
48
-
49
- class BytesBuffer(BaseBuffer[bytes]):
50
- """Collect the data as bytes."""
51
-
52
- buffer_class = io.BytesIO
53
-
54
- def __bytes__(self):
55
- return self.getvalue()
56
-
57
-
58
- class StringBuffer(BaseBuffer[str]):
59
- """Collect the data as string"""
60
-
61
- buffer_class = io.StringIO
62
-
63
- def __str__(self):
64
- return self.getvalue()