jupytergis-core 0.5.0__py3-none-any.whl → 0.6.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 (124) hide show
  1. jupytergis_core/_version.py +1 -1
  2. jupytergis_core/handler.py +301 -25
  3. jupytergis_core/schema/__init__.py +7 -7
  4. jupytergis_core/schema/interfaces/__init__.py +2 -2
  5. jupytergis_core/schema/interfaces/export/__init__.py +2 -2
  6. jupytergis_core/schema/interfaces/export/{exportGeojson.py → exportGeoJson.py} +2 -2
  7. jupytergis_core/schema/interfaces/export/{exportGeotiff.py → exportGeoTiff.py} +2 -2
  8. jupytergis_core/schema/interfaces/geojson.py +1 -1
  9. jupytergis_core/schema/interfaces/processing/__init__.py +2 -2
  10. jupytergis_core/schema/interfaces/processing/boundingBoxes.py +14 -0
  11. jupytergis_core/schema/interfaces/processing/buffer.py +1 -1
  12. jupytergis_core/schema/interfaces/processing/centroids.py +14 -0
  13. jupytergis_core/schema/interfaces/processing/concaveHull.py +21 -0
  14. jupytergis_core/schema/interfaces/processing/convexHull.py +14 -0
  15. jupytergis_core/schema/interfaces/processing/dissolve.py +1 -1
  16. jupytergis_core/schema/interfaces/project/__init__.py +2 -2
  17. jupytergis_core/schema/interfaces/project/jgis.py +2 -1
  18. jupytergis_core/schema/interfaces/project/layers/__init__.py +2 -2
  19. jupytergis_core/schema/interfaces/project/layers/heatmapLayer.py +1 -1
  20. jupytergis_core/schema/interfaces/project/layers/hillshadeLayer.py +1 -1
  21. jupytergis_core/schema/interfaces/project/layers/imageLayer.py +1 -1
  22. jupytergis_core/schema/interfaces/project/layers/{rasterlayer.py → rasterLayer.py} +2 -2
  23. jupytergis_core/schema/interfaces/project/layers/stacLayer.py +19 -0
  24. jupytergis_core/schema/interfaces/project/layers/{vectorlayer.py → vectorLayer.py} +2 -9
  25. jupytergis_core/schema/interfaces/project/layers/vectorTileLayer.py +1 -9
  26. jupytergis_core/schema/interfaces/project/layers/webGlLayer.py +2 -1
  27. jupytergis_core/schema/interfaces/project/sources/__init__.py +2 -2
  28. jupytergis_core/schema/interfaces/{geojsonsource.py → project/sources/geoJsonSource.py} +3 -3
  29. jupytergis_core/schema/interfaces/project/sources/geoTiffSource.py +1 -1
  30. jupytergis_core/schema/interfaces/project/sources/imageSource.py +1 -1
  31. jupytergis_core/schema/interfaces/project/sources/rasterDemSource.py +1 -1
  32. jupytergis_core/schema/interfaces/project/sources/{rastersource.py → rasterSource.py} +2 -2
  33. jupytergis_core/schema/interfaces/project/sources/shapefileSource.py +1 -1
  34. jupytergis_core/schema/interfaces/project/sources/{vectortilesource.py → vectorTileSource.py} +2 -2
  35. jupytergis_core/schema/interfaces/project/sources/videoSource.py +1 -1
  36. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/package.json +4 -4
  37. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/schemas/@jupytergis/jupytergis-core/package.json.orig +3 -3
  38. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/27.aea9e291ba3dd16790fb.js → jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/27.6ee794baeb00791424c2.js +2 -2
  39. jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/397.60274eedc66443d2b6fc.js +1 -0
  40. jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/432.b81aa01fd224321ff376.js +1 -0
  41. jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/552.0936beb209d3ea5a1229.js +1 -0
  42. jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/671.a72bd824c3cfdbc7f570.js +1 -0
  43. jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/803.5f3759c98814aa602705.js +8 -0
  44. jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/970.50a70b651103fce194d2.js +8 -0
  45. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/729.2540a8113c71e8eb28ef.js.LICENSE.txt → jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/970.50a70b651103fce194d2.js.LICENSE.txt +50 -0
  46. jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/991.4f548de9fbf8e51a7f01.js +1 -0
  47. jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/remoteEntry.dbd710a76941e40c3898.js +1 -0
  48. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/third-party-licenses.json +310 -10
  49. {jupytergis_core-0.5.0.dist-info → jupytergis_core-0.6.1.dist-info}/METADATA +1 -1
  50. jupytergis_core-0.6.1.dist-info/RECORD +117 -0
  51. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/377.2ba30d7a93a2a2819b1b.js +0 -1
  52. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/432.df2bee6966bdd5a48317.js +0 -1
  53. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/552.3f267df0d8b2c9ef2c40.js +0 -1
  54. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/729.2540a8113c71e8eb28ef.js +0 -8
  55. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/803.8b9c4e2f8bed9130b6d0.js +0 -8
  56. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/893.0bf9ee057c7e4666c364.js +0 -1
  57. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/991.13deb14ad0c10be40859.js +0 -1
  58. jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/remoteEntry.6e9db18c72727800e9ea.js +0 -1
  59. jupytergis_core-0.5.0.dist-info/RECORD +0 -112
  60. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/etc/jupyter/jupyter_server_config.d/jupytergis_core.json +0 -0
  61. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/schemas/@jupytergis/jupytergis-core/jupytergis-settings.json +0 -0
  62. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/026680ab0cd1523edc87.png +0 -0
  63. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/02ff7d503bbd90b21fc4.png +0 -0
  64. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/062a9554f6b4caac9713.png +0 -0
  65. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/064f37cecb8130ad66e8.png +0 -0
  66. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/08da2741746ddab81d04.png +0 -0
  67. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/0c6a0352b82839119f95.png +0 -0
  68. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/13c485bb93f5567f02fd.png +0 -0
  69. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/1474207a6b3ca1001e78.png +0 -0
  70. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/14b98240613d5256c621.png +0 -0
  71. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/154.0d16fbe1d1182d138b2c.js +0 -0
  72. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/1b97ea0f2b3af717cffa.png +0 -0
  73. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/1d440270da19a2f22fee.png +0 -0
  74. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/1ed164e010f3c0306d15.png +0 -0
  75. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/2218dfba22fc2b08e948.png +0 -0
  76. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/234.2f0fc49f516ad354aa18.js +0 -0
  77. /jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/27.aea9e291ba3dd16790fb.js.LICENSE.txt → /jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/27.6ee794baeb00791424c2.js.LICENSE.txt +0 -0
  78. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/2ab791b60c4058e664f8.png +0 -0
  79. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/2b24b6a745c11511f055.png +0 -0
  80. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/2d676bc0a01c2cd2fccb.png +0 -0
  81. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/2f02309ea499725612ea.png +0 -0
  82. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/32c7a73662cceb5bb1d7.png +0 -0
  83. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/350eec4ce9ae4bc10bca.wasm +0 -0
  84. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/363ca7c5f78deb6fd033.png +0 -0
  85. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/376.58a6410b7089dea5b0d5.js +0 -0
  86. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/3ae0bf244442de7efc35.png +0 -0
  87. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/3d48be10ffea86eb15de.png +0 -0
  88. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/407.da5d00862f4879fe3c9c.js +0 -0
  89. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/415edc3fa381260cf31e.png +0 -0
  90. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/42cbddf5e883673bc4e2.png +0 -0
  91. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/472.b2ea9ebc35d5ad4e1fcc.js +0 -0
  92. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/527ef171d5df15dc7da5.png +0 -0
  93. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/542.477a73b8682de0e8d45e.js +0 -0
  94. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/555.be80e60305924af2e139.js +0 -0
  95. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/5bb02252f243f8c7494f.png +0 -0
  96. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/5d181edc3c046e1454a1.png +0 -0
  97. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/5f32ad48aefe00e51312.png +0 -0
  98. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/618.86523276f857e0a3362e.js +0 -0
  99. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/6e4f6b4d0dfca3bd4450.png +0 -0
  100. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/71d436fb44627b6bfbd7.png +0 -0
  101. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/7b225dc2d37cd3582156.png +0 -0
  102. /jupytergis_core-0.5.0.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/803.8b9c4e2f8bed9130b6d0.js.LICENSE.txt → /jupytergis_core-0.6.1.data/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/803.5f3759c98814aa602705.js.LICENSE.txt +0 -0
  103. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/843ab141e62516b9df5c.png +0 -0
  104. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/8814e17f6b110e8f3e42.png +0 -0
  105. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/88b2ae0d29edb684eae5.png +0 -0
  106. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/971a42d174dd17b9451a.png +0 -0
  107. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/a3c609f5bff95a7a53be.png +0 -0
  108. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/a86d626c9ed2e222d190.png +0 -0
  109. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/a9e286b0c0264a9fc737.png +0 -0
  110. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/ab309078b494f850430a.png +0 -0
  111. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/b15e3989b7b90b5a8d9d.png +0 -0
  112. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/b36717fbb06f21d53b01.png +0 -0
  113. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/ba4b6e82fe5a816c40a5.png +0 -0
  114. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/bb2b310570da7a3587e9.png +0 -0
  115. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/be92bcf7bb99753b4b3d.png +0 -0
  116. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/c2ffa011d7f52a0ddf45.png +0 -0
  117. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/d83457b1b925c1718f6d.png +0 -0
  118. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/e473e1e9f20af114bbb4.data +0 -0
  119. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/f2617180c6907263a7ff.png +0 -0
  120. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/fe99a6dbf5a71d308989.png +0 -0
  121. {jupytergis_core-0.5.0.data → jupytergis_core-0.6.1.data}/data/share/jupyter/labextensions/@jupytergis/jupytergis-core/static/style.js +0 -0
  122. {jupytergis_core-0.5.0.dist-info → jupytergis_core-0.6.1.dist-info}/WHEEL +0 -0
  123. {jupytergis_core-0.5.0.dist-info → jupytergis_core-0.6.1.dist-info}/entry_points.txt +0 -0
  124. {jupytergis_core-0.5.0.dist-info → jupytergis_core-0.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '0.5.0'
4
+ __version__ = VERSION = '0.6.1'
@@ -1,45 +1,321 @@
1
- from os import environ
1
+ import json
2
+ import logging
3
+ import os
4
+ import time
5
+ from dataclasses import dataclass
6
+ from typing import Any, Dict, Optional, Set
7
+ from urllib.parse import urlparse
2
8
 
9
+ import tornado
3
10
  from jupyter_server.base.handlers import APIHandler
4
11
  from jupyter_server.utils import url_path_join
5
- import tornado
6
- import requests
12
+ from tornado.httpclient import AsyncHTTPClient, HTTPRequest, HTTPResponse
13
+
14
+
15
+ @dataclass
16
+ class ProxyConfig:
17
+ """Configuration for the proxy handler."""
18
+
19
+ default_timeout: int
20
+ max_redirects: int
21
+ max_body_size: int
22
+ rate_limit_requests: int
23
+ rate_limit_window: int
24
+ cors_origin: str
25
+ exempt_domains: Set[str]
26
+
27
+
28
+ def load_config() -> ProxyConfig:
29
+ """Load configuration from environment variables with defaults."""
30
+ return ProxyConfig(
31
+ default_timeout=int(os.environ.get("JGIS_TIMEOUT", "30")),
32
+ max_redirects=int(os.environ.get("JGIS_MAX_REDIRECTS", "3")),
33
+ max_body_size=int(os.environ.get("JGIS_MAX_BODY_SIZE", str(10 * 1024 * 1024))),
34
+ rate_limit_requests=int(os.environ.get("JGIS_RATE_LIMIT_REQUESTS", "100")),
35
+ rate_limit_window=int(os.environ.get("JGIS_RATE_LIMIT_WINDOW", "60")),
36
+ cors_origin=os.environ.get("JGIS_CORS_ORIGIN", "*"),
37
+ exempt_domains=os.environ.get(
38
+ "JGIS_EXEMPT_DOMAINS",
39
+ {
40
+ "https://geodes.cnes.fr",
41
+ "https://gdh-portal-prod.cnes.fr",
42
+ "https://geodes-portal.cnes.fr/api/stac/",
43
+ },
44
+ ),
45
+ )
46
+
47
+
48
+ # Configure logging
49
+ logger = logging.getLogger(__name__)
50
+
51
+
52
+ class ProxyError(Exception):
53
+ """Base exception for proxy-related errors."""
54
+
55
+ pass
56
+
57
+
58
+ class ValidationError(ProxyError):
59
+ """Raised when request validation fails."""
60
+
61
+ pass
62
+
63
+
64
+ class RateLimitError(ProxyError):
65
+ """Raised when rate limit is exceeded."""
66
+
67
+ pass
7
68
 
8
69
 
9
70
  class ProxyHandler(APIHandler):
71
+ """Secure proxy handler with enhanced validation and async processing."""
72
+
73
+ def initialize(self) -> None:
74
+ """Initialize the handler with configuration and HTTP client."""
75
+ self.proxy_config = load_config()
76
+ self._request_timestamps = []
77
+ self.http_client = AsyncHTTPClient(
78
+ defaults={
79
+ "connect_timeout": self.proxy_config.default_timeout,
80
+ "request_timeout": self.proxy_config.default_timeout,
81
+ "max_redirects": self.proxy_config.max_redirects,
82
+ "max_body_size": self.proxy_config.max_body_size,
83
+ }
84
+ )
85
+
86
+ def _check_rate_limit(self) -> None:
87
+ """Check if the current request exceeds rate limits.
88
+
89
+ Raises:
90
+ RateLimitError: If rate limit is exceeded
91
+ """
92
+ current_time = time.time()
93
+ window_start = current_time - self.proxy_config.rate_limit_window
94
+
95
+ # Remove old timestamps
96
+ self._request_timestamps = [
97
+ ts for ts in self._request_timestamps if ts > window_start
98
+ ]
99
+
100
+ if len(self._request_timestamps) >= self.proxy_config.rate_limit_requests:
101
+ raise RateLimitError("Rate limit exceeded")
102
+
103
+ self._request_timestamps.append(current_time)
104
+
10
105
  @tornado.web.authenticated
11
- def get(self):
12
- url = self.get_argument("url")
13
- print(f"Proxy request received for: {url}")
106
+ async def get(self) -> None:
107
+ """Process GET requests with validation and error handling."""
108
+ await self._handle_request("GET")
109
+
110
+ @tornado.web.authenticated
111
+ async def post(self) -> None:
112
+ """Process POST requests with validation and error handling."""
113
+ await self._handle_request("POST")
114
+
115
+ async def _handle_request(self, method: str) -> None:
116
+ """Central request handling method.
117
+
118
+ Args:
119
+ method: The HTTP method to use (GET or POST)
120
+
121
+ Raises:
122
+ tornado.web.HTTPError: If the request fails validation or processing
123
+ """
14
124
  try:
15
- response = requests.get(url)
16
- response.raise_for_status()
125
+ # Check rate limit
126
+ self._check_rate_limit()
127
+
128
+ # Validate and parse input
129
+ url = self._validate_url(self.get_argument("url"))
130
+ body = await self._validate_body(method)
131
+
132
+ logger.info("Proxying %s request to: %s", method, url)
133
+
134
+ # Make async HTTP request
135
+ response = await self._make_request(url, method, body)
136
+
137
+ # Forward response
138
+ self._set_response_headers(response)
139
+ self.finish(response.body)
17
140
 
18
- self.set_header("Access-Control-Allow-Origin", "*")
19
- self.set_header(
20
- "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"
141
+ except RateLimitError as e:
142
+ logger.warning("Rate limit exceeded: %s", e)
143
+ self.set_status(429)
144
+ self.finish(
145
+ json.dumps(
146
+ {
147
+ "error": "Rate limit exceeded",
148
+ "code": "rate_limit_error",
149
+ "message": str(e),
150
+ }
151
+ )
21
152
  )
22
- self.set_header(
23
- "Access-Control-Allow-Headers",
24
- "X-Requested-With, Content-Type, Authorization",
153
+
154
+ except ValidationError as e:
155
+ logger.warning("Validation error: %s", e)
156
+ self.set_status(400)
157
+ self.finish(
158
+ json.dumps(
159
+ {
160
+ "error": "Validation error",
161
+ "code": "validation_error",
162
+ "message": str(e),
163
+ }
164
+ )
25
165
  )
26
166
 
27
- self.set_header("Content-Type", response.headers["Content-Type"])
167
+ except tornado.web.HTTPError as e:
168
+ logger.warning("Client error: %s", e)
169
+ raise
28
170
 
29
- self.finish(response.content)
30
- except requests.exceptions.RequestException as e:
31
- self.set_status(500)
32
- self.finish(str(e))
171
+ except Exception as e:
172
+ logger.exception("Proxy request failed")
173
+ self._handle_error_response(e)
33
174
 
175
+ def _validate_url(self, url: str) -> str:
176
+ """Validate and sanitize target URL.
34
177
 
35
- def setup_handlers(web_app):
36
- host_pattern = ".*$"
178
+ Args:
179
+ url: The URL to validate
180
+
181
+ Returns:
182
+ The validated URL
183
+
184
+ Raises:
185
+ ValidationError: If the URL is invalid
186
+ """
187
+ parsed = urlparse(url)
188
+
189
+ if parsed.scheme not in ("http", "https"):
190
+ raise ValidationError("Invalid protocol")
191
+
192
+ return url
193
+
194
+ async def _validate_body(self, method: str) -> Optional[str]:
195
+ """Validate and prepare request body.
196
+
197
+ Args:
198
+ method: The HTTP method being used
199
+
200
+ Returns:
201
+ The validated and prepared request body, or None for GET requests
202
+
203
+ Raises:
204
+ ValidationError: If the body is invalid
205
+ """
206
+ if method == "POST":
207
+ try:
208
+ body = self.get_json_body()
209
+ if not body or not isinstance(body, Dict):
210
+ raise ValidationError("Invalid JSON body")
211
+
212
+ # Validate body size
213
+ body_str = json.dumps(body)
214
+ if len(body_str.encode("utf-8")) > self.proxy_config.max_body_size:
215
+ raise ValidationError("Request body too large")
216
+
217
+ return body_str
218
+ except json.JSONDecodeError as e:
219
+ raise ValidationError("Malformed JSON payload") from e
220
+
221
+ return None
222
+
223
+ async def _make_request(
224
+ self, url: str, method: str, body: Optional[str] = None
225
+ ) -> HTTPResponse:
226
+ """Execute proxy request with safety controls.
227
+
228
+ Args:
229
+ url: The target URL
230
+ method: The HTTP method to use
231
+ body: Optional request body
232
+
233
+ Returns:
234
+ The HTTP response
235
+
236
+ Raises:
237
+ tornado.web.HTTPError: If the request fails
238
+ """
239
+ try:
240
+ parsed_url = urlparse(url)
241
+ host = parsed_url.netloc.split(":")[0] # Remove port if present
242
+
243
+ # Disable SSL verification for exempt domains
244
+ validate_cert = host not in self.proxy_config.exempt_domains
245
+ logger.info("validate_cert: %s", validate_cert)
246
+
247
+ request = HTTPRequest(
248
+ url=url,
249
+ method=method,
250
+ body=body,
251
+ headers={"Content-Type": "application/json"} if body else None,
252
+ validate_cert=validate_cert,
253
+ allow_nonstandard_methods=False,
254
+ decompress_response=True,
255
+ )
256
+
257
+ return await self.http_client.fetch(request)
258
+
259
+ except tornado.httpclient.HTTPClientError as e:
260
+ logger.error("Upstream error: %d %s", e.code, e.message)
261
+ raise tornado.web.HTTPError(e.code, "Upstream service error") from e
262
+
263
+ except tornado.httpclient.HTTPError as e:
264
+ logger.error("Network error: %s", str(e))
265
+ raise tornado.web.HTTPError(503, "Service unavailable") from e
266
+
267
+ def _set_response_headers(self, response: HTTPResponse) -> None:
268
+ """Set secure CORS and content headers.
269
+
270
+ Args:
271
+ response: The HTTP response to get headers from
272
+ """
273
+ self.set_header("Access-Control-Allow-Origin", self.proxy_config.cors_origin)
274
+ self.set_header("Access-Control-Allow-Methods", "GET, POST")
275
+ self.set_header(
276
+ "Access-Control-Allow-Headers",
277
+ "Content-Type, Authorization, X-Requested-With",
278
+ )
279
+ self.set_header("Content-Security-Policy", "default-src 'none'")
280
+ self.set_header(
281
+ "Content-Type", response.headers.get("Content-Type", "application/json")
282
+ )
37
283
 
284
+ def _handle_error_response(self, error: Exception) -> None:
285
+ """Standardized error response handling.
286
+
287
+ Args:
288
+ error: The exception that occurred
289
+ """
290
+ self.set_status(500)
291
+ self.finish(
292
+ json.dumps(
293
+ {
294
+ "error": "Internal server error",
295
+ "code": "internal_error",
296
+ "message": str(error),
297
+ }
298
+ )
299
+ )
300
+
301
+
302
+ def setup_handlers(web_app: Any) -> None:
303
+ """Register handlers with configuration validation.
304
+
305
+ Args:
306
+ web_app: The Jupyter web application instance
307
+ """
308
+ host_pattern = ".*$"
38
309
  base_url = web_app.settings["base_url"]
39
- proxy_route_pattern = url_path_join(base_url, "jupytergis_core", "proxy")
40
310
 
41
- print(f"Setting up proxy handler at: {proxy_route_pattern}")
42
- handlers = [(proxy_route_pattern, ProxyHandler)]
43
- if environ.get("JGIS_EXPOSE_MAPS", False):
311
+ # Configure proxy route
312
+ proxy_route = url_path_join(base_url, "jupytergis_core", "proxy")
313
+ handlers = [(proxy_route, ProxyHandler)]
314
+
315
+ # Add feature flags
316
+ if os.environ.get("JGIS_EXPOSE_MAPS", False):
317
+ web_app.settings.setdefault("page_config_data", {})
44
318
  web_app.settings["page_config_data"]["jgis_expose_maps"] = True
319
+
45
320
  web_app.add_handlers(host_pattern, handlers)
321
+ logger.info("JupyterGIS proxy endpoint initialized at: %s", proxy_route)
@@ -1,16 +1,16 @@
1
1
  from .interfaces.project.jgis import * # noqa
2
2
 
3
- from .interfaces.project.layers.rasterlayer import IRasterLayer # noqa
4
- from .interfaces.project.layers.vectorlayer import IVectorLayer # noqa
3
+ from .interfaces.project.layers.rasterLayer import IRasterLayer # noqa
4
+ from .interfaces.project.layers.vectorLayer import IVectorLayer # noqa
5
5
  from .interfaces.project.layers.vectorTileLayer import IVectorTileLayer # noqa
6
6
  from .interfaces.project.layers.hillshadeLayer import IHillshadeLayer # noqa
7
7
  from .interfaces.project.layers.imageLayer import IImageLayer # noqa
8
8
  from .interfaces.project.layers.webGlLayer import IWebGlLayer # noqa
9
9
  from .interfaces.project.layers.heatmapLayer import IHeatmapLayer # noqa
10
10
 
11
- from .interfaces.project.sources.vectortilesource import IVectorTileSource # noqa
12
- from .interfaces.project.sources.rastersource import IRasterSource # noqa
13
- from .interfaces.geojsonsource import IGeoJSONSource # noqa
11
+ from .interfaces.project.sources.vectorTileSource import IVectorTileSource # noqa
12
+ from .interfaces.project.sources.rasterSource import IRasterSource # noqa
13
+ from .interfaces.project.sources.geoJsonSource import IGeoJSONSource # noqa
14
14
  from .interfaces.project.sources.videoSource import IVideoSource # noqa
15
15
  from .interfaces.project.sources.imageSource import IImageSource # noqa
16
16
  from .interfaces.project.sources.geoTiffSource import IGeoTiffSource # noqa
@@ -18,7 +18,7 @@ from .interfaces.project.sources.rasterDemSource import IRasterDemSource # noqa
18
18
 
19
19
  from .interfaces.processing.buffer import IBuffer # noqa
20
20
 
21
- from .interfaces.export.exportGeojson import IExportGeoJSON # noqa
22
- from .interfaces.export.exportGeotiff import IExportGeoTIFF # noqa
21
+ from .interfaces.export.exportGeoJson import IExportGeoJSON # noqa
22
+ from .interfaces.export.exportGeoTiff import IExportGeoTIFF # noqa
23
23
 
24
24
  SCHEMA_VERSION = IJGISContent.model_fields["schemaVersion"].default # noqa
@@ -1,3 +1,3 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: schema
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: temp-schema
3
+ # timestamp: 2025-07-15T16:15:24+00:00
@@ -1,3 +1,3 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: schema
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: temp-schema
3
+ # timestamp: 2025-07-15T16:15:24+00:00
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: export/exportGeojson.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: export/exportGeoJson.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: export/exportGeotiff.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: export/exportGeoTiff.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: geojson.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,3 +1,3 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: schema
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: temp-schema
3
+ # timestamp: 2025-07-15T16:15:24+00:00
@@ -0,0 +1,14 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: processing/boundingBoxes.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class IBoundingBoxes(BaseModel):
11
+ model_config = ConfigDict(
12
+ extra='forbid',
13
+ )
14
+ inputLayer: str = Field(..., description='The input layer for bounding boxes.')
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: processing/buffer.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -0,0 +1,14 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: processing/centroids.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class ICentroids(BaseModel):
11
+ model_config = ConfigDict(
12
+ extra='forbid',
13
+ )
14
+ inputLayer: str = Field(..., description='The input layer for centroids.')
@@ -0,0 +1,21 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: processing/concaveHull.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Optional
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field, confloat
10
+
11
+
12
+ class IConcaveHull(BaseModel):
13
+ model_config = ConfigDict(
14
+ extra='forbid',
15
+ )
16
+ inputLayer: str = Field(..., description='The input layer for Concave Hull.')
17
+ pctconvex: Optional[confloat(ge=0.0, le=1.0, multiple_of=0.1)] = Field(
18
+ 0.5,
19
+ description='Controls the concaveness of the computed hull, vary from 0 to 1, 0 corresponds to the maximum concavity.',
20
+ )
21
+ allowHoles: Optional[bool] = Field(False, title='Allow holes in the hull')
@@ -0,0 +1,14 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: processing/convexHull.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class IConvexHull(BaseModel):
11
+ model_config = ConfigDict(
12
+ extra='forbid',
13
+ )
14
+ inputLayer: str = Field(..., description='The input layer for Convex Hull.')
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: processing/dissolve.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,3 +1,3 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: schema
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: temp-schema
3
+ # timestamp: 2025-07-15T16:15:24+00:00
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: project/jgis.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -18,6 +18,7 @@ class LayerType(Enum):
18
18
  WebGlLayer = 'WebGlLayer'
19
19
  ImageLayer = 'ImageLayer'
20
20
  HeatmapLayer = 'HeatmapLayer'
21
+ StacLayer = 'StacLayer'
21
22
 
22
23
 
23
24
  class SourceType(Enum):
@@ -1,3 +1,3 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: schema
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: temp-schema
3
+ # timestamp: 2025-07-15T16:15:24+00:00
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: project/layers/heatmapLayer.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: project/layers/hillshadeLayer.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: project/layers/imageLayer.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: project/layers/rasterlayer.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: project/layers/rasterLayer.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -0,0 +1,19 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: project/layers/stacLayer.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any, Dict, Optional
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field, confloat
10
+
11
+
12
+ class IStacLayer(BaseModel):
13
+ model_config = ConfigDict(
14
+ extra='forbid',
15
+ )
16
+ data: Dict[str, Any] = Field(..., description='The data of the source')
17
+ opacity: Optional[confloat(ge=0.0, le=1.0, multiple_of=0.1)] = Field(
18
+ 1, description='The opacity of the source'
19
+ )
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: project/layers/vectorlayer.json
3
- # timestamp: 2025-05-08T01:23:34+00:00
2
+ # filename: project/layers/vectorLayer.json
3
+ # timestamp: 2025-07-15T16:15:24+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -10,12 +10,6 @@ from typing import Any, Dict, Optional
10
10
  from pydantic import BaseModel, ConfigDict, Field, confloat
11
11
 
12
12
 
13
- class Type(Enum):
14
- circle = 'circle'
15
- fill = 'fill'
16
- line = 'line'
17
-
18
-
19
13
  class RenderType(Enum):
20
14
  Single_Symbol = 'Single Symbol'
21
15
  Graduated = 'Graduated'
@@ -52,7 +46,6 @@ class IVectorLayer(BaseModel):
52
46
  extra='forbid',
53
47
  )
54
48
  source: str = Field(..., description='The id of the source')
55
- type: Type = Field(..., description='The type of vector layer')
56
49
  color: Optional[Dict[str, Any]] = Field(
57
50
  None, description='The color of the the object'
58
51
  )