c2cgeoportal-geoportal 2.5.0.100__py2.py3-none-any.whl → 2.7.1.156__py2.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 (224) hide show
  1. c2cgeoportal_geoportal/__init__.py +261 -130
  2. c2cgeoportal_geoportal/lib/__init__.py +74 -120
  3. c2cgeoportal_geoportal/lib/authentication.py +170 -21
  4. c2cgeoportal_geoportal/lib/bashcolor.py +17 -13
  5. c2cgeoportal_geoportal/lib/cacheversion.py +19 -11
  6. c2cgeoportal_geoportal/lib/caching.py +66 -160
  7. c2cgeoportal_geoportal/lib/check_collector.py +17 -10
  8. c2cgeoportal_geoportal/lib/checker.py +62 -64
  9. c2cgeoportal_geoportal/lib/common_headers.py +170 -0
  10. c2cgeoportal_geoportal/lib/dbreflection.py +70 -31
  11. c2cgeoportal_geoportal/lib/filter_capabilities.py +127 -97
  12. c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
  13. c2cgeoportal_geoportal/lib/functionality.py +36 -21
  14. c2cgeoportal_geoportal/lib/headers.py +14 -5
  15. c2cgeoportal_geoportal/lib/i18n.py +39 -0
  16. c2cgeoportal_geoportal/lib/layers.py +29 -10
  17. c2cgeoportal_geoportal/lib/lingua_extractor.py +408 -211
  18. c2cgeoportal_geoportal/lib/loader.py +18 -16
  19. c2cgeoportal_geoportal/lib/metrics.py +29 -18
  20. c2cgeoportal_geoportal/lib/oauth2.py +1036 -0
  21. c2cgeoportal_geoportal/lib/wmstparsing.py +115 -90
  22. c2cgeoportal_geoportal/lib/xsd.py +29 -19
  23. c2cgeoportal_geoportal/resources.py +15 -9
  24. c2cgeoportal_geoportal/scaffolds/advance_create/ci/config.yaml +26 -0
  25. c2cgeoportal_geoportal/scaffolds/advance_create/cookiecutter.json +18 -0
  26. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.dockerignore +6 -0
  27. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.eslintrc.yaml +19 -0
  28. c2cgeoportal_geoportal/scaffolds/{create/+dot+prospector.yaml → advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml} +8 -4
  29. c2cgeoportal_geoportal/scaffolds/{create/geoportal/Dockerfile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Dockerfile} +24 -15
  30. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/alembic.ini +1 -0
  31. c2cgeoportal_geoportal/scaffolds/{create/geoportal/alembic.yaml_tmpl → advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml} +1 -1
  32. c2cgeoportal_geoportal/scaffolds/{create/geoportal/development.ini_tmpl → advance_create/{{cookiecutter.project}}/geoportal/development.ini} +34 -15
  33. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +102 -0
  34. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-client.cfg +1 -0
  35. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
  36. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +2 -0
  37. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/setup.py +25 -0
  38. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/tools/extract-messages.js +8 -6
  39. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/tsconfig.json +8 -0
  40. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +75 -0
  41. c2cgeoportal_geoportal/scaffolds/{create/geoportal/webpack.apps.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js} +31 -28
  42. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.commons.js +3 -7
  43. c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.config.js +4 -4
  44. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +47 -0
  45. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
  46. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
  47. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
  48. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
  49. c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/resources.py +4 -3
  50. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js +12 -0
  51. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js +25 -0
  52. c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/subscribers.py_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py} +3 -6
  53. c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
  54. c2cgeoportal_geoportal/scaffolds/{update/geoportal/CONST_Makefile_tmpl → advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile} +32 -20
  55. c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
  56. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
  57. c2cgeoportal_geoportal/scaffolds/create/{+dot+editorconfig → {{cookiecutter.project}}/.editorconfig} +4 -8
  58. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +43 -0
  59. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +46 -0
  60. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +65 -0
  61. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
  62. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
  63. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
  64. c2cgeoportal_geoportal/scaffolds/create/{Dockerfile_tmpl → {{cookiecutter.project}}/Dockerfile} +34 -24
  65. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +14 -0
  66. c2cgeoportal_geoportal/scaffolds/create/{README.rst_tmpl → {{cookiecutter.project}}/README.rst} +4 -4
  67. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +162 -0
  68. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +25 -0
  69. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +1 -0
  70. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +474 -0
  71. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +67 -0
  72. c2cgeoportal_geoportal/scaffolds/create/{docker-compose.yaml → {{cookiecutter.project}}/docker-compose.yaml} +43 -18
  73. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +82 -0
  74. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +60 -0
  75. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +396 -0
  76. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
  77. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-blue.png +0 -0
  78. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-gold.png +0 -0
  79. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-green.png +0 -0
  80. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker.png +0 -0
  81. c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/Readme.txt +1 -1
  82. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
  83. c2cgeoportal_geoportal/scaffolds/create/{mapserver/mapserver.map.tmpl_tmpl → {{cookiecutter.project}}/mapserver/mapserver.map.tmpl} +7 -15
  84. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Landscape.jrxml +17 -9
  85. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Portrait.jrxml +17 -9
  86. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Landscape.jrxml +17 -9
  87. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Portrait.jrxml +17 -9
  88. c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/config.yaml.tmpl +30 -27
  89. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/legend.jrxml +109 -0
  90. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
  91. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
  92. c2cgeoportal_geoportal/scaffolds/create/{project.yaml_tmpl → {{cookiecutter.project}}/project.yaml} +6 -6
  93. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +3 -0
  94. c2cgeoportal_geoportal/scaffolds/create/{qgisserver/pg_service.conf.tmpl_tmpl → {{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl} +2 -2
  95. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +110 -0
  96. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +114 -0
  97. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
  98. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +3 -0
  99. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
  100. c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
  101. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +191 -0
  102. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +1160 -0
  103. c2cgeoportal_geoportal/scaffolds/update/{geoportal → {{cookiecutter.project}}/geoportal}/CONST_config-schema.yaml +99 -47
  104. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1410 -0
  105. c2cgeoportal_geoportal/scripts/__init__.py +18 -32
  106. c2cgeoportal_geoportal/scripts/c2cupgrade.py +295 -200
  107. c2cgeoportal_geoportal/scripts/create_demo_theme.py +5 -6
  108. c2cgeoportal_geoportal/scripts/manage_users.py +34 -37
  109. c2cgeoportal_geoportal/scripts/pcreate.py +312 -0
  110. c2cgeoportal_geoportal/scripts/theme2fts.py +92 -25
  111. c2cgeoportal_geoportal/scripts/urllogin.py +23 -17
  112. c2cgeoportal_geoportal/templates/login.html +88 -84
  113. c2cgeoportal_geoportal/templates/notlogin.html +62 -0
  114. c2cgeoportal_geoportal/templates/testi18n.html +6 -8
  115. c2cgeoportal_geoportal/views/__init__.py +23 -4
  116. c2cgeoportal_geoportal/views/dev.py +9 -7
  117. c2cgeoportal_geoportal/views/dynamic.py +71 -40
  118. c2cgeoportal_geoportal/views/entry.py +93 -24
  119. c2cgeoportal_geoportal/views/fulltextsearch.py +36 -29
  120. c2cgeoportal_geoportal/views/geometry_processing.py +15 -7
  121. c2cgeoportal_geoportal/views/i18n.py +91 -9
  122. c2cgeoportal_geoportal/views/layers.py +173 -134
  123. c2cgeoportal_geoportal/views/login.py +206 -87
  124. c2cgeoportal_geoportal/views/mapserverproxy.py +59 -35
  125. c2cgeoportal_geoportal/views/memory.py +13 -13
  126. c2cgeoportal_geoportal/views/ogcproxy.py +48 -30
  127. c2cgeoportal_geoportal/views/pdfreport.py +31 -27
  128. c2cgeoportal_geoportal/views/printproxy.py +70 -54
  129. c2cgeoportal_geoportal/views/profile.py +25 -24
  130. c2cgeoportal_geoportal/views/proxy.py +100 -70
  131. c2cgeoportal_geoportal/views/raster.py +47 -29
  132. c2cgeoportal_geoportal/views/resourceproxy.py +13 -11
  133. c2cgeoportal_geoportal/views/shortener.py +31 -24
  134. c2cgeoportal_geoportal/views/theme.py +475 -365
  135. c2cgeoportal_geoportal/views/tinyowsproxy.py +46 -39
  136. c2cgeoportal_geoportal/views/vector_tiles.py +80 -0
  137. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.156.dist-info}/METADATA +17 -11
  138. c2cgeoportal_geoportal-2.7.1.156.dist-info/RECORD +185 -0
  139. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.156.dist-info}/WHEEL +1 -1
  140. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.156.dist-info}/entry_points.txt +3 -1
  141. tests/__init__.py +24 -3
  142. tests/test_cachebuster.py +1 -3
  143. tests/test_caching.py +19 -26
  144. tests/test_checker.py +2 -3
  145. tests/test_decimaljson.py +4 -4
  146. tests/test_headerstween.py +0 -3
  147. tests/test_i18n.py +31 -0
  148. tests/test_init.py +12 -27
  149. tests/test_locale_negociator.py +6 -6
  150. tests/test_mapserverproxy_route_predicate.py +0 -2
  151. tests/test_raster.py +18 -5
  152. tests/test_wmstparsing.py +7 -8
  153. c2cgeoportal_geoportal/scaffolds/__init__.py +0 -226
  154. c2cgeoportal_geoportal/scaffolds/create/+dot+dockerignore_tmpl +0 -11
  155. c2cgeoportal_geoportal/scaffolds/create/+dot+github/workflows/ci.yaml_tmpl +0 -56
  156. c2cgeoportal_geoportal/scaffolds/create/+dot+gitignore_tmpl +0 -12
  157. c2cgeoportal_geoportal/scaffolds/create/build_tmpl +0 -144
  158. c2cgeoportal_geoportal/scaffolds/create/docker-compose-lib.yaml_tmpl +0 -302
  159. c2cgeoportal_geoportal/scaffolds/create/docker-compose.override.sample.yaml_tmpl +0 -54
  160. c2cgeoportal_geoportal/scaffolds/create/env.default_tmpl +0 -49
  161. c2cgeoportal_geoportal/scaffolds/create/env.project_tmpl +0 -39
  162. c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+dockerignore_tmpl +0 -5
  163. c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+eslintrc_tmpl +0 -19
  164. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/__init__.py_tmpl +0 -48
  165. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/models.py_tmpl +0 -10
  166. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static/images/favicon.ico +0 -0
  167. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static/robot.txt +0 -3
  168. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static-ngeo/api/index.js_tmpl +0 -37
  169. c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static-ngeo/js/+package+module.js_tmpl +0 -22
  170. c2cgeoportal_geoportal/scaffolds/create/geoportal/production.ini_tmpl +0 -106
  171. c2cgeoportal_geoportal/scaffolds/create/geoportal/requirements.txt +0 -2
  172. c2cgeoportal_geoportal/scaffolds/create/geoportal/setup.py_tmpl +0 -18
  173. c2cgeoportal_geoportal/scaffolds/create/geoportal/tsconfig.json_tmpl +0 -9
  174. c2cgeoportal_geoportal/scaffolds/create/geoportal/vars.yaml_tmpl +0 -224
  175. c2cgeoportal_geoportal/scaffolds/create/geoportal/webpack.api.js_tmpl +0 -71
  176. c2cgeoportal_geoportal/scaffolds/create/mapserver/demo.map.tmpl_tmpl +0 -262
  177. c2cgeoportal_geoportal/scaffolds/create/mapserver/tinyows.xml +0 -36
  178. c2cgeoportal_geoportal/scaffolds/create/print/print-apps/+package+/config.yaml +0 -166
  179. c2cgeoportal_geoportal/scaffolds/create/print/print-apps/+package+/legend.jrxml +0 -27
  180. c2cgeoportal_geoportal/scaffolds/create/qgisserver/geomapfish.yaml.tmpl_tmpl +0 -29
  181. c2cgeoportal_geoportal/scaffolds/create/scripts/publish-docker +0 -124
  182. c2cgeoportal_geoportal/scaffolds/create/setup.cfg_tmpl +0 -3
  183. c2cgeoportal_geoportal/scaffolds/create/spell-ignore-words.txt +0 -1
  184. c2cgeoportal_geoportal/scaffolds/create/tilegeneration/config.yaml.tmpl_tmpl +0 -169
  185. c2cgeoportal_geoportal/scaffolds/create/yamllint.yaml +0 -11
  186. c2cgeoportal_geoportal/scaffolds/update/+dot+upgrade.yaml_tmpl +0 -171
  187. c2cgeoportal_geoportal/scaffolds/update/CONST_CHANGELOG.txt_tmpl +0 -64
  188. c2cgeoportal_geoportal/scaffolds/update/geoportal/CONST_vars.yaml_tmpl +0 -783
  189. c2cgeoportal_geoportal/templates/dynamic.js +0 -21
  190. c2cgeoportal_geoportal-2.5.0.100.dist-info/RECORD +0 -162
  191. tests/test_get_url.py +0 -96
  192. tests/test_lib.py +0 -77
  193. /c2cgeoportal_geoportal/{scaffolds/create/geoportal/+package+_geoportal/static/css/desktop.css → py.typed} +0 -0
  194. /c2cgeoportal_geoportal/scaffolds/{create/geoportal/Makefile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Makefile} +0 -0
  195. /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/language_mapping +0 -0
  196. /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-server.cfg +0 -0
  197. /c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/views/__init__.py +0 -0
  198. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/locale/en/LC_MESSAGES/+package+_geoportal-client.po → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po} +0 -0
  199. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/iframe_api.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css} +0 -0
  200. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/mobile.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css} +0 -0
  201. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_left.png +0 -0
  202. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_right.png +0 -0
  203. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/blank.png +0 -0
  204. /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/robot.txt.tmpl +0 -0
  205. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/TM_EUROPE_BORDERS-0.3.sql +0 -0
  206. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arial.ttf +0 -0
  207. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbd.ttf +0 -0
  208. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbi.ttf +0 -0
  209. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Ariali.ttf +0 -0
  210. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Bold.ttf +0 -0
  211. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-BoldItalic.ttf +0 -0
  212. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Italic.ttf +0 -0
  213. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Regular.ttf +0 -0
  214. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdana.ttf +0 -0
  215. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanab.ttf +0 -0
  216. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanai.ttf +0 -0
  217. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanaz.ttf +0 -0
  218. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts.conf +0 -0
  219. /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/tinyows.xml.tmpl +0 -0
  220. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/logo.png +0 -0
  221. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/north.svg +0 -0
  222. /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/results.jrxml +0 -0
  223. /c2cgeoportal_geoportal/scaffolds/create/{run_alembic.sh → {{cookiecutter.project}}/run_alembic.sh} +0 -0
  224. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.156.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2011-2019, Camptocamp SA
1
+ # Copyright (c) 2011-2021, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -26,15 +24,18 @@
26
24
  # The views and conclusions contained in the software and documentation are those
27
25
  # of the authors and should not be interpreted as representing official policies,
28
26
  # either expressed or implied, of the FreeBSD Project.
27
+
28
+ import random
29
29
  import threading
30
- from typing import Dict, Tuple # noqa, pylint: disable=unused-import
31
30
  import warnings
31
+ from typing import Dict, Iterable, List, Optional, Tuple, Union
32
32
 
33
+ import sqlalchemy.ext.declarative
33
34
  from papyrus.geo_interface import GeoInterface
34
35
  from sqlalchemy import Column, Integer, MetaData, Table
35
36
  from sqlalchemy.exc import SAWarning
36
- from sqlalchemy.ext.declarative.api import DeclarativeMeta # noqa, pylint: disable=unused-import
37
37
  from sqlalchemy.orm import relationship
38
+ from sqlalchemy.orm.session import Session
38
39
  from sqlalchemy.orm.util import class_mapper
39
40
 
40
41
  from c2cgeoportal_geoportal.lib.caching import get_region
@@ -53,12 +54,17 @@ SQL_GEOMETRY_COLUMNS = """
53
54
  class _AssociationProxy:
54
55
  # A specific "association proxy" implementation
55
56
 
56
- def __init__(self, target, value_attr, nullable=True):
57
+ def __init__(self, target: str, value_attr: str, nullable: bool = True, order_by: Optional[str] = None):
57
58
  self.target = target
58
59
  self.value_attr = value_attr
59
60
  self.nullable = nullable
61
+ self.order_by = order_by
60
62
 
61
- def __get__(self, obj, type_=None):
63
+ def __get__(
64
+ self,
65
+ obj: sqlalchemy.ext.declarative.ConcreteBase,
66
+ type_: sqlalchemy.ext.declarative.DeclarativeMeta = None,
67
+ ) -> Optional[Union["_AssociationProxy", str]]:
62
68
  if obj is None:
63
69
  # For "hybrid" descriptors that work both at the instance
64
70
  # and class levels we could return an SQL expression here.
@@ -68,7 +74,7 @@ class _AssociationProxy:
68
74
  target = getattr(obj, self.target)
69
75
  return getattr(target, self.value_attr) if target else None
70
76
 
71
- def __set__(self, obj, val):
77
+ def __set__(self, obj: str, val: str) -> None:
72
78
  from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
73
79
 
74
80
  o = getattr(obj, self.target)
@@ -82,7 +88,7 @@ class _AssociationProxy:
82
88
  setattr(obj, self.target, o)
83
89
 
84
90
 
85
- def _get_schema(tablename):
91
+ def _get_schema(tablename: str) -> Tuple[str, str]:
86
92
  if "." in tablename:
87
93
  schema, tablename = tablename.split(".", 1)
88
94
  else:
@@ -94,7 +100,13 @@ def _get_schema(tablename):
94
100
  _get_table_lock = threading.RLock()
95
101
 
96
102
 
97
- def get_table(tablename, schema=None, session=None, primary_key=None):
103
+ def get_table(
104
+ tablename: str,
105
+ schema: Optional[str] = None,
106
+ session: Optional[Session] = None,
107
+ primary_key: Optional[str] = None,
108
+ ) -> Table:
109
+ """Build an SQLAlchemy table."""
98
110
  if schema is None:
99
111
  tablename, schema = _get_schema(tablename)
100
112
 
@@ -102,8 +114,8 @@ def get_table(tablename, schema=None, session=None, primary_key=None):
102
114
  engine = session.bind.engine
103
115
  metadata = MetaData(bind=engine)
104
116
  else:
117
+ from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel
105
118
  from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
106
- from c2cgeoportal_commons.models.main import Base # pylint: disable=import-outside-toplevel
107
119
 
108
120
  engine = DBSession.bind.engine
109
121
  metadata = Base.metadata
@@ -120,16 +132,21 @@ def get_table(tablename, schema=None, session=None, primary_key=None):
120
132
  return table
121
133
 
122
134
 
123
- @CACHE_REGION_OBJ.cache_on_arguments()
135
+ @CACHE_REGION_OBJ.cache_on_arguments() # type: ignore
124
136
  def get_class(
125
- tablename, exclude_properties=None, primary_key=None, attributes_order=None, readonly_attributes=None
126
- ):
137
+ tablename: str,
138
+ exclude_properties: Optional[List[str]] = None,
139
+ primary_key: Optional[str] = None,
140
+ attributes_order: Optional[List[str]] = None,
141
+ enumerations_config: Optional[Dict[str, str]] = None,
142
+ readonly_attributes: Optional[List[str]] = None,
143
+ ) -> sqlalchemy.ext.declarative.DeclarativeMeta:
127
144
  """
128
- Get the SQLAlchemy mapped class for "tablename". If no class exists
129
- for "tablename" one is created, and added to the cache. "tablename"
130
- must reference a valid string. If there is no table identified by
131
- tablename in the database a NoSuchTableError SQLAlchemy exception
132
- is raised.
145
+ Get the SQLAlchemy mapped class for "tablename".
146
+
147
+ If no class exists for "tablename" one is created, and added to the cache. "tablename" must reference a
148
+ valid string. If there is no table identified by tablename in the database a NoSuchTableError SQLAlchemy
149
+ exception is raised.
133
150
  """
134
151
 
135
152
  tablename, schema = _get_schema(tablename)
@@ -141,6 +158,7 @@ def get_class(
141
158
  table,
142
159
  exclude_properties=exclude_properties,
143
160
  attributes_order=attributes_order,
161
+ enumerations_config=enumerations_config,
144
162
  readonly_attributes=readonly_attributes,
145
163
  )
146
164
 
@@ -148,19 +166,29 @@ def get_class(
148
166
 
149
167
 
150
168
  def _create_class(
151
- table, exclude_properties=None, attributes_order=None, readonly_attributes=None, pk_name=None
152
- ):
153
- from c2cgeoportal_commons.models.main import Base # pylint: disable=import-outside-toplevel
169
+ table: Table,
170
+ exclude_properties: Optional[Iterable[str]] = None,
171
+ attributes_order: Optional[List[str]] = None,
172
+ enumerations_config: Optional[Dict[str, str]] = None,
173
+ readonly_attributes: Optional[List[str]] = None,
174
+ pk_name: Optional[str] = None,
175
+ ) -> sqlalchemy.ext.declarative.DeclarativeMeta:
176
+ from c2cgeoportal_commons.models import Base # pylint: disable=import-outside-toplevel
154
177
 
155
178
  exclude_properties = exclude_properties or ()
156
179
  attributes = dict(
157
180
  __table__=table,
158
181
  __mapper_args__={"exclude_properties": exclude_properties},
159
182
  __attributes_order__=attributes_order,
183
+ __enumerations_config__=enumerations_config,
160
184
  )
161
185
  if pk_name is not None:
162
186
  attributes[pk_name] = Column(Integer, primary_key=True)
163
- cls = type(table.name.capitalize(), (GeoInterface, Base), attributes)
187
+ # The randint is to fix the SAWarning: This declarative base already contains a class with the same
188
+ # class name and module nam
189
+ cls = type(
190
+ f"{table.name.capitalize()}_{random.randint(0, 9999999)}", (GeoInterface, Base), attributes # nosec
191
+ )
164
192
 
165
193
  for col in table.columns:
166
194
  if col.name in (readonly_attributes or []):
@@ -171,8 +199,10 @@ def _create_class(
171
199
  return cls
172
200
 
173
201
 
174
- def _add_association_proxy(cls, col):
175
- if len(col.foreign_keys) != 1: # pragma: no cover
202
+ def _add_association_proxy(
203
+ cls: sqlalchemy.ext.declarative.DeclarativeMeta, col: sqlalchemy.sql.schema.Column
204
+ ) -> None:
205
+ if len(col.foreign_keys) != 1:
176
206
  raise NotImplementedError
177
207
 
178
208
  fk = next(iter(col.foreign_keys))
@@ -180,10 +210,8 @@ def _add_association_proxy(cls, col):
180
210
  child_cls = get_class(child_tablename)
181
211
 
182
212
  try:
183
- # fmt: off
184
- proxy = col.name[0:col.name.rindex("_id")]
185
- # fmt: on
186
- except ValueError: # pragma: no cover
213
+ proxy = col.name[0 : col.name.rindex("_id")]
214
+ except ValueError:
187
215
  proxy = col.name + "_"
188
216
 
189
217
  # The following is a workaround for a bug in the geojson lib. The
@@ -191,7 +219,7 @@ def _add_association_proxy(cls, col):
191
219
  # (this is a GeoJSON keyword), and produces a UnicodeEncodeError
192
220
  # if the property includes non-ascii characters.
193
221
  if proxy == "type":
194
- proxy = "type_" # pragma: no cover
222
+ proxy = "type_"
195
223
 
196
224
  rel = proxy + "_"
197
225
  primaryjoin = getattr(cls, col.name) == getattr(child_cls, child_pk)
@@ -203,7 +231,18 @@ def _add_association_proxy(cls, col):
203
231
  for column in cls_column_property.columns:
204
232
  nullable = nullable and column.nullable
205
233
 
206
- setattr(cls, proxy, _AssociationProxy(rel, "name", nullable=nullable))
234
+ value_attr = "name"
235
+ order_by = value_attr
236
+
237
+ if cls.__enumerations_config__ and col.name in cls.__enumerations_config__:
238
+ enumeration_config = cls.__enumerations_config__[col.name]
239
+ if "value" in enumeration_config:
240
+ value_attr = enumeration_config["value"]
241
+ order_by = value_attr
242
+ if "order_by" in enumeration_config:
243
+ order_by = enumeration_config["order_by"]
244
+
245
+ setattr(cls, proxy, _AssociationProxy(rel, value_attr, nullable=nullable, order_by=order_by))
207
246
 
208
247
  if cls.__add_properties__ is None:
209
248
  cls.__add_properties__ = [proxy]
@@ -1,6 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2014-2019, Camptocamp SA
1
+ # Copyright (c) 2014-2024, Camptocamp SA
4
2
  # All rights reserved.
5
3
 
6
4
  # Redistribution and use in source and binary forms, with or without
@@ -29,56 +27,59 @@
29
27
 
30
28
 
31
29
  import copy
32
- from io import StringIO
33
30
  import logging
34
- from typing import Callable, Dict, List
35
- from urllib.parse import urlsplit
36
- import xml.sax.handler
37
- from xml.sax.saxutils import XMLFilterBase, XMLGenerator
31
+ import xml.sax.handler # nosec
32
+ from io import StringIO
33
+ from typing import Any, Callable, Dict, List, Optional, Set, Union
34
+ from xml.sax.saxutils import XMLFilterBase, XMLGenerator # nosec
38
35
 
39
36
  import defusedxml.expatreader
37
+ import pyramid.request
38
+ import requests
39
+ from owslib.map.wms111 import ContentMetadata as ContentMetadata111
40
+ from owslib.map.wms130 import ContentMetadata as ContentMetadata130
40
41
  from owslib.wms import WebMapService
41
42
  from pyramid.httpexceptions import HTTPBadGateway
42
- import requests
43
43
 
44
- from c2cgeoportal_geoportal.lib import (
45
- add_url_params,
46
- caching,
47
- get_ogc_server_wfs_url_ids,
48
- get_ogc_server_wms_url_ids,
49
- )
44
+ from c2cgeoportal_commons.lib.url import Url
45
+ from c2cgeoportal_geoportal.lib import caching, get_ogc_server_wfs_url_ids, get_ogc_server_wms_url_ids
50
46
  from c2cgeoportal_geoportal.lib.layers import get_private_layers, get_protected_layers, get_writable_layers
51
47
 
52
48
  CACHE_REGION = caching.get_region("std")
53
49
  LOG = logging.getLogger(__name__)
50
+ ContentMetadata = Union[ContentMetadata111, ContentMetadata130]
54
51
 
55
52
 
56
- @CACHE_REGION.cache_on_arguments()
57
- def wms_structure(wms_url, host, request):
58
- url = urlsplit(wms_url)
59
- wms_url = add_url_params(wms_url, {"SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetCapabilities"})
53
+ @CACHE_REGION.cache_on_arguments() # type: ignore
54
+ def wms_structure(wms_url: Url, host: str, request: pyramid.request.Request) -> Dict[str, List[str]]:
55
+ """Get a simple serializable structure of the WMS capabilities."""
56
+ url = wms_url.clone().add_query({"SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetCapabilities"})
60
57
 
61
58
  # Forward request to target (without Host Header)
62
- headers = dict()
63
- if url.hostname == "localhost" and host is not None: # pragma: no cover
59
+ headers = {}
60
+ if url.hostname == "localhost" and host is not None:
64
61
  headers["Host"] = host
65
62
  try:
66
- response = requests.get(wms_url, headers=headers, **request.registry.settings.get("http_options", {}))
67
- except Exception: # pragma: no cover
68
- raise HTTPBadGateway("Unable to GetCapabilities from wms_url {0!s}".format(wms_url))
63
+ response = requests.get(
64
+ url.url(), headers=headers, **request.registry.settings.get("http_options", {})
65
+ )
66
+ except Exception:
67
+ LOG.exception("Unable to GetCapabilities from wms_url '%s'", wms_url)
68
+ raise HTTPBadGateway( # pylint: disable=raise-missing-from
69
+ "Unable to GetCapabilities, see logs for details"
70
+ )
69
71
 
70
- if not response.ok: # pragma: no cover
72
+ if not response.ok:
71
73
  raise HTTPBadGateway(
72
- "GetCapabilities from wms_url {0!s} return the error: {1:d} {2!s}".format(
73
- wms_url, response.status_code, response.reason
74
- )
74
+ f"GetCapabilities from wms_url {url.url()} return the error: "
75
+ f"{response.status_code:d} {response.reason}"
75
76
  )
76
77
 
77
78
  try:
78
79
  wms = WebMapService(None, xml=response.content)
79
80
  result: Dict[str, List[str]] = {}
80
81
 
81
- def _fill(name, parent):
82
+ def _fill(name: str, parent: ContentMetadata) -> None:
82
83
  if parent is None:
83
84
  return
84
85
  if parent.name not in result:
@@ -90,37 +91,48 @@ def wms_structure(wms_url, host, request):
90
91
  _fill(layer.name, layer.parent)
91
92
  return result
92
93
 
93
- except AttributeError: # pragma: no cover
94
- error = "WARNING! an error occurred while trying to " "read the mapfile and recover the themes."
95
- error = "{0!s}\nurl: {1!s}\nxml:\n{2!s}".format(error, wms_url, response.text)
94
+ except AttributeError:
95
+ error = "WARNING! an error occurred while trying to read the mapfile and recover the themes."
96
+ error = f"{error}\nurl: {wms_url}\nxml:\n{response.text}"
96
97
  LOG.exception(error)
97
- raise HTTPBadGateway(error)
98
+ raise HTTPBadGateway(error) # pylint: disable=raise-missing-from
98
99
 
99
- except SyntaxError: # pragma: no cover
100
- error = "WARNING! an error occurred while trying to " "read the mapfile and recover the themes."
101
- error = "{0!s}\nurl: {1!s}\nxml:\n{2!s}".format(error, wms_url, response.text)
100
+ except SyntaxError:
101
+ error = "WARNING! an error occurred while trying to read the mapfile and recover the themes."
102
+ error = f"{error}\nurl: {wms_url}\nxml:\n{response.text}"
102
103
  LOG.exception(error)
103
- raise HTTPBadGateway(error)
104
+ raise HTTPBadGateway(error) # pylint: disable=raise-missing-from
104
105
 
105
106
 
106
- def filter_capabilities(content, wms, url, headers, request):
107
+ def filter_capabilities(
108
+ content: str, wms: bool, url: Url, headers: Dict[str, str], request: pyramid.request.Request
109
+ ) -> str:
110
+ """Filter the WMS/WFS capabilities."""
107
111
 
108
112
  wms_structure_ = wms_structure(url, headers.get("Host"), request)
109
113
 
110
114
  ogc_server_ids = (
111
- get_ogc_server_wms_url_ids(request) if wms else get_ogc_server_wfs_url_ids(request)
112
- ).get(url)
115
+ get_ogc_server_wms_url_ids(request, request.host)
116
+ if wms
117
+ else get_ogc_server_wfs_url_ids(request, request.host)
118
+ ).get(url.url())
113
119
  gmf_private_layers = copy.copy(get_private_layers(ogc_server_ids))
114
120
  for id_ in list(get_protected_layers(request, ogc_server_ids).keys()):
115
121
  if id_ in gmf_private_layers:
116
122
  del gmf_private_layers[id_]
117
123
 
118
124
  private_layers = set()
119
- for gmflayer in list(gmf_private_layers.values()):
120
- for ogclayer in gmflayer.layer.split(","):
121
- private_layers.add(ogclayer)
122
- if ogclayer in wms_structure_:
123
- private_layers.update(wms_structure_[ogclayer])
125
+ for gmf_layer in list(gmf_private_layers.values()):
126
+ for ogc_layer in gmf_layer.layer.split(","):
127
+ private_layers.add(ogc_layer)
128
+ if ogc_layer in wms_structure_:
129
+ private_layers.update(wms_structure_[ogc_layer])
130
+
131
+ LOG.debug(
132
+ "Filter capabilities of OGC server %s\nprivate_layers: %s",
133
+ ", ".join([str(e) for e in ogc_server_ids]),
134
+ ", ".join(private_layers),
135
+ )
124
136
 
125
137
  parser = defusedxml.expatreader.create_parser(forbid_external=False)
126
138
  # skip inclusion of DTDs
@@ -132,15 +144,24 @@ def filter_capabilities(content, wms, url, headers, request):
132
144
  filter_handler = _CapabilitiesFilter(
133
145
  parser, downstream_handler, "Layer" if wms else "FeatureType", layers_blacklist=private_layers
134
146
  )
135
- filter_handler.parse(StringIO(content))
147
+ filter_handler.parse(StringIO(content)) # type: ignore
136
148
  return result.getvalue()
137
149
 
138
150
 
139
- def filter_wfst_capabilities(content, wfs_url, request):
140
- writable_layers: List[str] = []
141
- ogc_server_ids = get_ogc_server_wfs_url_ids(request).get(wfs_url)
142
- for gmflayer in list(get_writable_layers(request, ogc_server_ids).values()):
143
- writable_layers += gmflayer.layer.split(",")
151
+ def filter_wfst_capabilities(content: str, wfs_url: Url, request: pyramid.request.Request) -> str:
152
+ """Filter the WTS capabilities."""
153
+
154
+ writable_layers: Set[str] = set()
155
+ ogc_server_ids = get_ogc_server_wfs_url_ids(request, request.host).get(wfs_url.url())
156
+
157
+ for gmf_layer in list(get_writable_layers(request, ogc_server_ids).values()):
158
+ writable_layers |= set(gmf_layer.layer.split(","))
159
+
160
+ LOG.debug(
161
+ "Filter WFS-T capabilities of OGC server %s\nlayers: %s",
162
+ ", ".join([str(e) for e in ogc_server_ids]),
163
+ ", ".join(writable_layers),
164
+ )
144
165
 
145
166
  parser = defusedxml.expatreader.create_parser(forbid_external=False)
146
167
  # skip inclusion of DTDs
@@ -152,13 +173,13 @@ def filter_wfst_capabilities(content, wfs_url, request):
152
173
  filter_handler = _CapabilitiesFilter(
153
174
  parser, downstream_handler, "FeatureType", layers_whitelist=writable_layers
154
175
  )
155
- filter_handler.parse(StringIO(content))
176
+ filter_handler.parse(StringIO(content)) # type: ignore
156
177
  return result.getvalue()
157
178
 
158
179
 
159
180
  class _Layer:
160
- def __init__(self, self_hidden=False):
161
- self.accumul: List[Callable] = []
181
+ def __init__(self, self_hidden: bool = False):
182
+ self.accumulator: List[Callable[[], None]] = []
162
183
  self.hidden = True
163
184
  self.self_hidden = self_hidden
164
185
  self.has_children = False
@@ -168,13 +189,20 @@ class _Layer:
168
189
  class _CapabilitiesFilter(XMLFilterBase):
169
190
  """
170
191
  SAX filter to show only the allowed layers in a GetCapabilities request.
171
- The filter removes elements of type `tag_name` where the `name`
172
- attribute is part of the set `layers_blacklist` (when `layers_blacklist`
173
- is given) or is not part of the set `layers_whitelist` (when
192
+
193
+ The filter removes elements of type `tag_name` where the `name` attribute is part of the set
194
+ `layers_blacklist` (when `layers_blacklist` is given) or is not part of the set `layers_whitelist` (when
174
195
  `layers_whitelist` is given).
175
196
  """
176
197
 
177
- def __init__(self, upstream, downstream, tag_name, layers_blacklist=None, layers_whitelist=None):
198
+ def __init__(
199
+ self,
200
+ upstream: XMLFilterBase,
201
+ downstream: XMLGenerator,
202
+ tag_name: str,
203
+ layers_blacklist: Optional[Set[str]] = None,
204
+ layers_whitelist: Optional[Set[str]] = None,
205
+ ):
178
206
  XMLFilterBase.__init__(self, upstream)
179
207
  self._downstream = downstream
180
208
  self._accumulator: List[str] = []
@@ -187,9 +215,9 @@ class _CapabilitiesFilter(XMLFilterBase):
187
215
  ), "only either layers_blacklist OR layers_whitelist can be set"
188
216
 
189
217
  if layers_blacklist is not None:
190
- layers_blacklist = [layer.lower() for layer in layers_blacklist]
218
+ layers_blacklist = {layer.lower() for layer in layers_blacklist}
191
219
  if layers_whitelist is not None:
192
- layers_whitelist = [layer.lower() for layer in layers_whitelist]
220
+ layers_whitelist = {layer.lower() for layer in layers_whitelist}
193
221
  self.layers_blacklist = layers_blacklist
194
222
  self.layers_whitelist = layers_whitelist
195
223
 
@@ -200,39 +228,39 @@ class _CapabilitiesFilter(XMLFilterBase):
200
228
 
201
229
  def _complete_text_node(self) -> None:
202
230
  if self._accumulator:
203
- self._downstream.characters("".join(self._accumulator))
231
+ self._downstream.characters("".join(self._accumulator)) # type: ignore
204
232
  self._accumulator = []
205
233
 
206
- def _do(self, action):
234
+ def _do(self, action: Callable[[], Any]) -> None:
207
235
  if self.layers_path:
208
- self.layers_path[-1].accumul.append(action)
236
+ self.layers_path[-1].accumulator.append(action)
209
237
  else:
210
238
  self._complete_text_node()
211
239
  action()
212
240
 
213
- def _add_child(self, layer):
241
+ def _add_child(self, layer: _Layer) -> None:
214
242
  if not layer.hidden and not (layer.has_children and layer.children_nb == 0):
215
- for action in layer.accumul:
243
+ for action in layer.accumulator:
216
244
  self._complete_text_node()
217
245
  action()
218
- layer.accumul = []
246
+ layer.accumulator = []
219
247
 
220
- def setDocumentLocator(self, locator): # noqa
221
- self._downstream.setDocumentLocator(locator)
248
+ def setDocumentLocator(self, locator: str) -> None: # noqa: ignore=N802
249
+ self._downstream.setDocumentLocator(locator) # type: ignore
222
250
 
223
- def startDocument(self): # noqa
224
- self._downstream.startDocument()
251
+ def startDocument(self) -> None: # noqa: ignore=N802
252
+ self._downstream.startDocument() # type: ignore
225
253
 
226
- def endDocument(self): # noqa
227
- self._downstream.endDocument()
254
+ def endDocument(self) -> None: # noqa: ignore=N802
255
+ self._downstream.endDocument() # type: ignore
228
256
 
229
- def startPrefixMapping(self, prefix, uri): # pragma: no cover # noqa
230
- self._downstream.startPrefixMapping(prefix, uri)
257
+ def startPrefixMapping(self, prefix: str, uri: str) -> None: # noqa: ignore=N802
258
+ self._downstream.startPrefixMapping(prefix, uri) # type: ignore
231
259
 
232
- def endPrefixMapping(self, prefix): # pragma: no cover # noqa
233
- self._downstream.endPrefixMapping(prefix)
260
+ def endPrefixMapping(self, prefix: str) -> None: # noqa: ignore=N802
261
+ self._downstream.endPrefixMapping(prefix) # type: ignore
234
262
 
235
- def startElement(self, name, attrs): # noqa
263
+ def startElement(self, name: str, attrs: xml.sax.xmlreader.AttributesImpl) -> None: # noqa: ignore=N802
236
264
  if name == self.tag_name:
237
265
  self.level += 1
238
266
  if self.layers_path:
@@ -242,40 +270,40 @@ class _CapabilitiesFilter(XMLFilterBase):
242
270
  layer = _Layer(parent_layer.self_hidden) if len(self.layers_path) > 1 else _Layer()
243
271
  self.layers_path.append(layer)
244
272
 
245
- parent_layer.accumul.append(lambda: self._add_child(layer))
273
+ parent_layer.accumulator.append(lambda: self._add_child(layer))
246
274
  else:
247
275
  layer = _Layer()
248
276
  self.layers_path.append(layer)
249
277
  elif name == "Name" and self.layers_path:
250
278
  self.in_name = True
251
279
 
252
- self._do(lambda: self._downstream.startElement(name, attrs))
280
+ self._do(lambda: self._downstream.startElement(name, attrs)) # type: ignore
253
281
 
254
- def endElement(self, name): # noqa
255
- self._do(lambda: self._downstream.endElement(name))
282
+ def endElement(self, name: str) -> None: # noqa: ignore=N802
283
+ self._do(lambda: self._downstream.endElement(name)) # type: ignore
256
284
 
257
285
  if name == self.tag_name:
258
286
  self.level -= 1
259
287
  if self.level == 0 and not self.layers_path[0].hidden:
260
- for action in self.layers_path[0].accumul:
288
+ for action in self.layers_path[0].accumulator:
261
289
  self._complete_text_node()
262
290
  action()
263
291
  self.layers_path.pop()
264
292
  elif name == "Name":
265
293
  self.in_name = False
266
294
 
267
- def startElementNS(self, name, qname, attrs): # pragma: no cover # noqa
268
- self._do(lambda: self._downstream.startElementNS(name, qname, attrs))
295
+ def startElementNS(self, name: str, qname: str, attrs: Dict[str, str]) -> None: # noqa: ignore=N802
296
+ self._do(lambda: self._downstream.startElementNS(name, qname, attrs)) # type: ignore
269
297
 
270
- def endElementNS(self, name, qname): # pragma: no cover # noqa
271
- self._do(lambda: self._downstream.endElementNS(name, qname))
298
+ def endElementNS(self, name: str, qname: str) -> None: # noqa: ignore=N802
299
+ self._do(lambda: self._downstream.endElementNS(name, qname)) # type: ignore
272
300
 
273
- def _keep_layer(self, layer_name):
301
+ def _keep_layer(self, layer_name: str) -> bool:
274
302
  return (self.layers_blacklist is not None and layer_name not in self.layers_blacklist) or (
275
303
  self.layers_whitelist is not None and layer_name in self.layers_whitelist
276
304
  )
277
305
 
278
- def characters(self, content):
306
+ def characters(self, content: str) -> None:
279
307
  if self.in_name and self.layers_path and not self.layers_path[-1].self_hidden is True:
280
308
  layer_name = normalize_typename(content)
281
309
  if self._keep_layer(layer_name):
@@ -289,19 +317,20 @@ class _CapabilitiesFilter(XMLFilterBase):
289
317
 
290
318
  self._do(lambda: self._accumulator.append(content))
291
319
 
292
- def ignorableWhitespace(self, chars): # pragma: no cover # noqa
320
+ def ignorableWhitespace(self, chars: str) -> None: # noqa: ignore=N802
293
321
  self._do(lambda: self._accumulator.append(chars))
294
322
 
295
- def processingInstruction(self, target, data): # pragma: no cover # noqa
296
- self._do(lambda: self._downstream.processingInstruction(target, data))
323
+ def processingInstruction(self, target: str, data: str) -> None: # noqa: ignore=N802
324
+ self._do(lambda: self._downstream.processingInstruction(target, data)) # type: ignore
297
325
 
298
- def skippedEntity(self, name): # pragma: no cover # noqa
299
- self._downstream.skippedEntity(name)
326
+ def skippedEntity(self, name: str) -> None: # noqa: ignore=N802
327
+ self._downstream.skippedEntity(name) # type: ignore
300
328
 
301
329
 
302
- def normalize_tag(tag):
330
+ def normalize_tag(tag: str) -> str:
303
331
  """
304
- Drops the namespace from a tag and converts to lower case.
332
+ Drop the namespace from a tag and converts to lower case.
333
+
305
334
  e.g. '{https://....}TypeName' -> 'TypeName'
306
335
  """
307
336
  normalized = tag
@@ -311,9 +340,10 @@ def normalize_tag(tag):
311
340
  return normalized.lower()
312
341
 
313
342
 
314
- def normalize_typename(typename):
343
+ def normalize_typename(typename: str) -> str:
315
344
  """
316
- Drops the namespace from a type name and converts to lower case.
345
+ Drop the namespace from a type name and converts to lower case.
346
+
317
347
  e.g. 'tows:parks' -> 'parks'
318
348
  """
319
349
  normalized = typename
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2020-2021, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+ import re
29
+ from typing import Any, Dict
30
+
31
+
32
+ class Normalize:
33
+ """Normalize a text for the Full text search."""
34
+
35
+ def __init__(self, config: Dict[str, Any]) -> None:
36
+ split = config.get("split_regex")
37
+ self.split_re = re.compile(split) if split is not None else None
38
+
39
+ self.word_replace = []
40
+ for search_regex, text in config.get("replace", {}).items():
41
+ self.word_replace.append((re.compile(search_regex), text))
42
+
43
+ def __call__(self, text: str) -> str:
44
+ if self.split_re is not None:
45
+ text = " ".join(self.split_re.split(text))
46
+
47
+ for search, replace in self.word_replace:
48
+ text = search.sub(replace, text)
49
+
50
+ return text