utils.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. # -*- coding: utf-8 -*-
  2. from flask import current_app
  3. from flask.globals import _request_ctx_stack
  4. from collections import OrderedDict
  5. from datetime import datetime
  6. import pytz
  7. import json
  8. import sys
  9. from . import db
  10. def dict_to_geojson(d_in):
  11. """
  12. Encode a dict representing a GeoJSON object into a JSON string.
  13. This is needed because spatialite's GeoJSON parser is not really
  14. JSON-compliant and it fails when keys are not in the right order.
  15. """
  16. d=OrderedDict()
  17. d['type']=d_in['type']
  18. if 'crs' in d_in:
  19. d['crs']=d_in['crs']
  20. # our spatialite geo column is defined with EPSG SRID 4326 (WGS 84)
  21. d['crs'] = {'type': 'name', 'properties': {'name': 'urn:ogc:def:crs:EPSG:4326'}}
  22. if 'bbox' in d_in:
  23. d['bbox']=d_in['bbox']
  24. d['coordinates']=d_in['coordinates']
  25. return json.dumps(d)
  26. def check_geojson_spatialite(_gjson):
  27. """
  28. Checks if a GeoJSON dict is understood by spatialite
  29. >>> check_geojson_spatialite({'type': 'NOPE', 'coordinates': []})
  30. False
  31. >>> check_geojson_spatialite({'type': 'Polygon', 'coordinates': [
  32. ... [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]]
  33. ... ]})
  34. True
  35. """
  36. gjson=dict_to_geojson(_gjson)
  37. return bool(db.session.query(db.func.GeomFromGeoJSON(gjson) != None).first()[0])
  38. def utcnow():
  39. """
  40. Return the current UTC date and time as a datetime object with proper tzinfo.
  41. """
  42. return datetime.utcnow().replace(tzinfo=pytz.utc)
  43. def tosystemtz(d):
  44. """
  45. Convert the UTC datetime ``d`` to the system time zone defined in the settings
  46. """
  47. return d.astimezone(pytz.timezone(current_app.config['SYSTEM_TIME_ZONE']))
  48. def filesize_fmt(num):
  49. fmt = lambda num, unit: "%s %s" % (format(num, '.2f').rstrip('0').rstrip('.'), unit)
  50. for x in ['bytes', 'KiB', 'MiB', 'GiB']:
  51. if num < 1024.0:
  52. return fmt(num, x)
  53. num /= 1024.0
  54. return fmt(num, 'TiB')
  55. def stream_with_ctx_and_exc(generator_or_function):
  56. """
  57. taken from flask's code, added exception logging
  58. """
  59. try:
  60. gen = iter(generator_or_function)
  61. except TypeError:
  62. def decorator(*args, **kwargs):
  63. gen = generator_or_function()
  64. return stream_with_context(gen)
  65. return update_wrapper(decorator, generator_or_function)
  66. def generator():
  67. ctx = _request_ctx_stack.top
  68. if ctx is None:
  69. raise RuntimeError('Attempted to stream with context but '
  70. 'there was no context in the first place to keep around.')
  71. with ctx:
  72. # Dummy sentinel. Has to be inside the context block or we're
  73. # not actually keeping the context around.
  74. yield None
  75. # The try/finally is here so that if someone passes a WSGI level
  76. # iterator in we're still running the cleanup logic. Generators
  77. # don't need that because they are closed on their destruction
  78. # automatically.
  79. try:
  80. for item in gen:
  81. yield item
  82. except Exception as e:
  83. exc_type, exc_value, tb = sys.exc_info()
  84. current_app.log_exception((exc_type, exc_value, tb))
  85. finally:
  86. if hasattr(gen, 'close'):
  87. gen.close()
  88. # The trick is to start the generator. Then the code execution runs until
  89. # the first dummy None is yielded at which point the context was already
  90. # pushed. This item is discarded. Then when the iteration continues the
  91. # real generator is executed.
  92. wrapped_g = generator()
  93. next(wrapped_g)
  94. return wrapped_g