c2cgeoportal-geoportal 2.5.0.100__py2.py3-none-any.whl → 2.7.1.83__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 +72 -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 +124 -96
  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 +104 -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 +158 -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 +66 -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 +107 -0
  96. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +111 -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 +1153 -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 +1412 -0
  105. c2cgeoportal_geoportal/scripts/__init__.py +17 -33
  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 +70 -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 +67 -53
  129. c2cgeoportal_geoportal/views/profile.py +25 -24
  130. c2cgeoportal_geoportal/views/proxy.py +97 -68
  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.83.dist-info}/METADATA +16 -11
  138. c2cgeoportal_geoportal-2.7.1.83.dist-info/RECORD +185 -0
  139. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.dist-info}/WHEEL +1 -1
  140. {c2cgeoportal_geoportal-2.5.0.100.dist-info → c2cgeoportal_geoportal-2.7.1.83.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.83.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-2023, 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,46 @@ 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
115
  get_ogc_server_wms_url_ids(request) if wms else get_ogc_server_wfs_url_ids(request)
112
- ).get(url)
116
+ ).get(url.url())
113
117
  gmf_private_layers = copy.copy(get_private_layers(ogc_server_ids))
114
118
  for id_ in list(get_protected_layers(request, ogc_server_ids).keys()):
115
119
  if id_ in gmf_private_layers:
116
120
  del gmf_private_layers[id_]
117
121
 
118
122
  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])
123
+ for gmf_layer in list(gmf_private_layers.values()):
124
+ for ogc_layer in gmf_layer.layer.split(","):
125
+ private_layers.add(ogc_layer)
126
+ if ogc_layer in wms_structure_:
127
+ private_layers.update(wms_structure_[ogc_layer])
128
+
129
+ LOG.debug(
130
+ "Filter capabilities of OGC server %s\nprivate_layers: %s",
131
+ ", ".join([str(e) for e in ogc_server_ids]),
132
+ ", ".join(private_layers),
133
+ )
124
134
 
125
135
  parser = defusedxml.expatreader.create_parser(forbid_external=False)
126
136
  # skip inclusion of DTDs
@@ -132,15 +142,24 @@ def filter_capabilities(content, wms, url, headers, request):
132
142
  filter_handler = _CapabilitiesFilter(
133
143
  parser, downstream_handler, "Layer" if wms else "FeatureType", layers_blacklist=private_layers
134
144
  )
135
- filter_handler.parse(StringIO(content))
145
+ filter_handler.parse(StringIO(content)) # type: ignore
136
146
  return result.getvalue()
137
147
 
138
148
 
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(",")
149
+ def filter_wfst_capabilities(content: str, wfs_url: Url, request: pyramid.request.Request) -> str:
150
+ """Filter the WTS capabilities."""
151
+
152
+ writable_layers: Set[str] = set()
153
+ ogc_server_ids = get_ogc_server_wfs_url_ids(request).get(wfs_url.url())
154
+
155
+ for gmf_layer in list(get_writable_layers(request, ogc_server_ids).values()):
156
+ writable_layers |= set(gmf_layer.layer.split(","))
157
+
158
+ LOG.debug(
159
+ "Filter WFS-T capabilities of OGC server %s\nlayers: %s",
160
+ ", ".join([str(e) for e in ogc_server_ids]),
161
+ ", ".join(writable_layers),
162
+ )
144
163
 
145
164
  parser = defusedxml.expatreader.create_parser(forbid_external=False)
146
165
  # skip inclusion of DTDs
@@ -152,13 +171,13 @@ def filter_wfst_capabilities(content, wfs_url, request):
152
171
  filter_handler = _CapabilitiesFilter(
153
172
  parser, downstream_handler, "FeatureType", layers_whitelist=writable_layers
154
173
  )
155
- filter_handler.parse(StringIO(content))
174
+ filter_handler.parse(StringIO(content)) # type: ignore
156
175
  return result.getvalue()
157
176
 
158
177
 
159
178
  class _Layer:
160
- def __init__(self, self_hidden=False):
161
- self.accumul: List[Callable] = []
179
+ def __init__(self, self_hidden: bool = False):
180
+ self.accumulator: List[Callable[[], None]] = []
162
181
  self.hidden = True
163
182
  self.self_hidden = self_hidden
164
183
  self.has_children = False
@@ -168,13 +187,20 @@ class _Layer:
168
187
  class _CapabilitiesFilter(XMLFilterBase):
169
188
  """
170
189
  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
190
+
191
+ The filter removes elements of type `tag_name` where the `name` attribute is part of the set
192
+ `layers_blacklist` (when `layers_blacklist` is given) or is not part of the set `layers_whitelist` (when
174
193
  `layers_whitelist` is given).
175
194
  """
176
195
 
177
- def __init__(self, upstream, downstream, tag_name, layers_blacklist=None, layers_whitelist=None):
196
+ def __init__(
197
+ self,
198
+ upstream: XMLFilterBase,
199
+ downstream: XMLGenerator,
200
+ tag_name: str,
201
+ layers_blacklist: Optional[Set[str]] = None,
202
+ layers_whitelist: Optional[Set[str]] = None,
203
+ ):
178
204
  XMLFilterBase.__init__(self, upstream)
179
205
  self._downstream = downstream
180
206
  self._accumulator: List[str] = []
@@ -187,9 +213,9 @@ class _CapabilitiesFilter(XMLFilterBase):
187
213
  ), "only either layers_blacklist OR layers_whitelist can be set"
188
214
 
189
215
  if layers_blacklist is not None:
190
- layers_blacklist = [layer.lower() for layer in layers_blacklist]
216
+ layers_blacklist = {layer.lower() for layer in layers_blacklist}
191
217
  if layers_whitelist is not None:
192
- layers_whitelist = [layer.lower() for layer in layers_whitelist]
218
+ layers_whitelist = {layer.lower() for layer in layers_whitelist}
193
219
  self.layers_blacklist = layers_blacklist
194
220
  self.layers_whitelist = layers_whitelist
195
221
 
@@ -200,39 +226,39 @@ class _CapabilitiesFilter(XMLFilterBase):
200
226
 
201
227
  def _complete_text_node(self) -> None:
202
228
  if self._accumulator:
203
- self._downstream.characters("".join(self._accumulator))
229
+ self._downstream.characters("".join(self._accumulator)) # type: ignore
204
230
  self._accumulator = []
205
231
 
206
- def _do(self, action):
232
+ def _do(self, action: Callable[[], Any]) -> None:
207
233
  if self.layers_path:
208
- self.layers_path[-1].accumul.append(action)
234
+ self.layers_path[-1].accumulator.append(action)
209
235
  else:
210
236
  self._complete_text_node()
211
237
  action()
212
238
 
213
- def _add_child(self, layer):
239
+ def _add_child(self, layer: _Layer) -> None:
214
240
  if not layer.hidden and not (layer.has_children and layer.children_nb == 0):
215
- for action in layer.accumul:
241
+ for action in layer.accumulator:
216
242
  self._complete_text_node()
217
243
  action()
218
- layer.accumul = []
244
+ layer.accumulator = []
219
245
 
220
- def setDocumentLocator(self, locator): # noqa
221
- self._downstream.setDocumentLocator(locator)
246
+ def setDocumentLocator(self, locator: str) -> None: # noqa: ignore=N802
247
+ self._downstream.setDocumentLocator(locator) # type: ignore
222
248
 
223
- def startDocument(self): # noqa
224
- self._downstream.startDocument()
249
+ def startDocument(self) -> None: # noqa: ignore=N802
250
+ self._downstream.startDocument() # type: ignore
225
251
 
226
- def endDocument(self): # noqa
227
- self._downstream.endDocument()
252
+ def endDocument(self) -> None: # noqa: ignore=N802
253
+ self._downstream.endDocument() # type: ignore
228
254
 
229
- def startPrefixMapping(self, prefix, uri): # pragma: no cover # noqa
230
- self._downstream.startPrefixMapping(prefix, uri)
255
+ def startPrefixMapping(self, prefix: str, uri: str) -> None: # noqa: ignore=N802
256
+ self._downstream.startPrefixMapping(prefix, uri) # type: ignore
231
257
 
232
- def endPrefixMapping(self, prefix): # pragma: no cover # noqa
233
- self._downstream.endPrefixMapping(prefix)
258
+ def endPrefixMapping(self, prefix: str) -> None: # noqa: ignore=N802
259
+ self._downstream.endPrefixMapping(prefix) # type: ignore
234
260
 
235
- def startElement(self, name, attrs): # noqa
261
+ def startElement(self, name: str, attrs: xml.sax.xmlreader.AttributesImpl) -> None: # noqa: ignore=N802
236
262
  if name == self.tag_name:
237
263
  self.level += 1
238
264
  if self.layers_path:
@@ -242,40 +268,40 @@ class _CapabilitiesFilter(XMLFilterBase):
242
268
  layer = _Layer(parent_layer.self_hidden) if len(self.layers_path) > 1 else _Layer()
243
269
  self.layers_path.append(layer)
244
270
 
245
- parent_layer.accumul.append(lambda: self._add_child(layer))
271
+ parent_layer.accumulator.append(lambda: self._add_child(layer))
246
272
  else:
247
273
  layer = _Layer()
248
274
  self.layers_path.append(layer)
249
275
  elif name == "Name" and self.layers_path:
250
276
  self.in_name = True
251
277
 
252
- self._do(lambda: self._downstream.startElement(name, attrs))
278
+ self._do(lambda: self._downstream.startElement(name, attrs)) # type: ignore
253
279
 
254
- def endElement(self, name): # noqa
255
- self._do(lambda: self._downstream.endElement(name))
280
+ def endElement(self, name: str) -> None: # noqa: ignore=N802
281
+ self._do(lambda: self._downstream.endElement(name)) # type: ignore
256
282
 
257
283
  if name == self.tag_name:
258
284
  self.level -= 1
259
285
  if self.level == 0 and not self.layers_path[0].hidden:
260
- for action in self.layers_path[0].accumul:
286
+ for action in self.layers_path[0].accumulator:
261
287
  self._complete_text_node()
262
288
  action()
263
289
  self.layers_path.pop()
264
290
  elif name == "Name":
265
291
  self.in_name = False
266
292
 
267
- def startElementNS(self, name, qname, attrs): # pragma: no cover # noqa
268
- self._do(lambda: self._downstream.startElementNS(name, qname, attrs))
293
+ def startElementNS(self, name: str, qname: str, attrs: Dict[str, str]) -> None: # noqa: ignore=N802
294
+ self._do(lambda: self._downstream.startElementNS(name, qname, attrs)) # type: ignore
269
295
 
270
- def endElementNS(self, name, qname): # pragma: no cover # noqa
271
- self._do(lambda: self._downstream.endElementNS(name, qname))
296
+ def endElementNS(self, name: str, qname: str) -> None: # noqa: ignore=N802
297
+ self._do(lambda: self._downstream.endElementNS(name, qname)) # type: ignore
272
298
 
273
- def _keep_layer(self, layer_name):
299
+ def _keep_layer(self, layer_name: str) -> bool:
274
300
  return (self.layers_blacklist is not None and layer_name not in self.layers_blacklist) or (
275
301
  self.layers_whitelist is not None and layer_name in self.layers_whitelist
276
302
  )
277
303
 
278
- def characters(self, content):
304
+ def characters(self, content: str) -> None:
279
305
  if self.in_name and self.layers_path and not self.layers_path[-1].self_hidden is True:
280
306
  layer_name = normalize_typename(content)
281
307
  if self._keep_layer(layer_name):
@@ -289,19 +315,20 @@ class _CapabilitiesFilter(XMLFilterBase):
289
315
 
290
316
  self._do(lambda: self._accumulator.append(content))
291
317
 
292
- def ignorableWhitespace(self, chars): # pragma: no cover # noqa
318
+ def ignorableWhitespace(self, chars: str) -> None: # noqa: ignore=N802
293
319
  self._do(lambda: self._accumulator.append(chars))
294
320
 
295
- def processingInstruction(self, target, data): # pragma: no cover # noqa
296
- self._do(lambda: self._downstream.processingInstruction(target, data))
321
+ def processingInstruction(self, target: str, data: str) -> None: # noqa: ignore=N802
322
+ self._do(lambda: self._downstream.processingInstruction(target, data)) # type: ignore
297
323
 
298
- def skippedEntity(self, name): # pragma: no cover # noqa
299
- self._downstream.skippedEntity(name)
324
+ def skippedEntity(self, name: str) -> None: # noqa: ignore=N802
325
+ self._downstream.skippedEntity(name) # type: ignore
300
326
 
301
327
 
302
- def normalize_tag(tag):
328
+ def normalize_tag(tag: str) -> str:
303
329
  """
304
- Drops the namespace from a tag and converts to lower case.
330
+ Drop the namespace from a tag and converts to lower case.
331
+
305
332
  e.g. '{https://....}TypeName' -> 'TypeName'
306
333
  """
307
334
  normalized = tag
@@ -311,9 +338,10 @@ def normalize_tag(tag):
311
338
  return normalized.lower()
312
339
 
313
340
 
314
- def normalize_typename(typename):
341
+ def normalize_typename(typename: str) -> str:
315
342
  """
316
- Drops the namespace from a type name and converts to lower case.
343
+ Drop the namespace from a type name and converts to lower case.
344
+
317
345
  e.g. 'tows:parks' -> 'parks'
318
346
  """
319
347
  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