geonode-pycsw 3.0.0b2__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 (382) hide show
  1. geonode_pycsw-3.0.0b2.dist-info/METADATA +73 -0
  2. geonode_pycsw-3.0.0b2.dist-info/RECORD +382 -0
  3. geonode_pycsw-3.0.0b2.dist-info/WHEEL +5 -0
  4. geonode_pycsw-3.0.0b2.dist-info/entry_points.txt +2 -0
  5. geonode_pycsw-3.0.0b2.dist-info/licenses/LICENSE.txt +26 -0
  6. geonode_pycsw-3.0.0b2.dist-info/top_level.txt +1 -0
  7. pycsw/__init__.py +33 -0
  8. pycsw/core/__init__.py +29 -0
  9. pycsw/core/admin.py +703 -0
  10. pycsw/core/config.py +621 -0
  11. pycsw/core/etree.py +43 -0
  12. pycsw/core/formats/__init__.py +29 -0
  13. pycsw/core/formats/fmt_json.py +69 -0
  14. pycsw/core/log.py +71 -0
  15. pycsw/core/metadata.py +1996 -0
  16. pycsw/core/pygeofilter_evaluate.py +83 -0
  17. pycsw/core/repository.py +941 -0
  18. pycsw/core/schemas/ogc/cat/csw/3.0/_wrapper.xsd +14 -0
  19. pycsw/core/schemas/ogc/cat/csw/3.0/cswAll.xsd +33 -0
  20. pycsw/core/schemas/ogc/cat/csw/3.0/cswCommon.xsd +71 -0
  21. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetCapabilities.xsd +80 -0
  22. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetDomain.xsd +146 -0
  23. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecordById.xsd +58 -0
  24. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecords.xsd +391 -0
  25. pycsw/core/schemas/ogc/cat/csw/3.0/cswHarvest.xsd +95 -0
  26. pycsw/core/schemas/ogc/cat/csw/3.0/cswTransaction.xsd +187 -0
  27. pycsw/core/schemas/ogc/cat/csw/3.0/cswUnHarvest.xsd +77 -0
  28. pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcmes.xsd +245 -0
  29. pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcterms.xsd +101 -0
  30. pycsw/core/schemas/ogc/cat/csw/3.0/record.xsd +170 -0
  31. pycsw/core/schemas/ogc/csw/2.0.2/CSW-discovery.xsd +494 -0
  32. pycsw/core/schemas/ogc/csw/2.0.2/CSW-publication.xsd +242 -0
  33. pycsw/core/schemas/ogc/csw/2.0.2/rec-dcmes.xsd +199 -0
  34. pycsw/core/schemas/ogc/csw/2.0.2/rec-dcterms.xsd +94 -0
  35. pycsw/core/schemas/ogc/csw/2.0.2/record.xsd +138 -0
  36. pycsw/core/schemas/ogc/filter/1.1.0/expr.xsd +67 -0
  37. pycsw/core/schemas/ogc/filter/1.1.0/filter.xsd +265 -0
  38. pycsw/core/schemas/ogc/filter/1.1.0/filterCapabilities.xsd +171 -0
  39. pycsw/core/schemas/ogc/filter/1.1.0/sort.xsd +46 -0
  40. pycsw/core/schemas/ogc/filter/2.0/_wrapper.xsd +5 -0
  41. pycsw/core/schemas/ogc/filter/2.0/expr.xsd +44 -0
  42. pycsw/core/schemas/ogc/filter/2.0/filter.xsd +396 -0
  43. pycsw/core/schemas/ogc/filter/2.0/filterAll.xsd +23 -0
  44. pycsw/core/schemas/ogc/filter/2.0/filterCapabilities.xsd +286 -0
  45. pycsw/core/schemas/ogc/filter/2.0/query.xsd +70 -0
  46. pycsw/core/schemas/ogc/filter/2.0/sort.xsd +49 -0
  47. pycsw/core/schemas/ogc/gml/3.1.1/base/basicTypes.xsd +278 -0
  48. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateOperations.xsd +789 -0
  49. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateReferenceSystems.xsd +429 -0
  50. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateSystems.xsd +408 -0
  51. pycsw/core/schemas/ogc/gml/3.1.1/base/coverage.xsd +451 -0
  52. pycsw/core/schemas/ogc/gml/3.1.1/base/dataQuality.xsd +129 -0
  53. pycsw/core/schemas/ogc/gml/3.1.1/base/datums.xsd +484 -0
  54. pycsw/core/schemas/ogc/gml/3.1.1/base/defaultStyle.xsd +454 -0
  55. pycsw/core/schemas/ogc/gml/3.1.1/base/dictionary.xsd +137 -0
  56. pycsw/core/schemas/ogc/gml/3.1.1/base/direction.xsd +72 -0
  57. pycsw/core/schemas/ogc/gml/3.1.1/base/dynamicFeature.xsd +115 -0
  58. pycsw/core/schemas/ogc/gml/3.1.1/base/feature.xsd +199 -0
  59. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryAggregates.xsd +430 -0
  60. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic0d1d.xsd +602 -0
  61. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic2d.xsd +213 -0
  62. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryComplexes.xsd +141 -0
  63. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryPrimitives.xsd +1609 -0
  64. pycsw/core/schemas/ogc/gml/3.1.1/base/gml.xsd +22 -0
  65. pycsw/core/schemas/ogc/gml/3.1.1/base/gmlBase.xsd +294 -0
  66. pycsw/core/schemas/ogc/gml/3.1.1/base/grids.xsd +76 -0
  67. pycsw/core/schemas/ogc/gml/3.1.1/base/measures.xsd +200 -0
  68. pycsw/core/schemas/ogc/gml/3.1.1/base/observation.xsd +96 -0
  69. pycsw/core/schemas/ogc/gml/3.1.1/base/referenceSystems.xsd +211 -0
  70. pycsw/core/schemas/ogc/gml/3.1.1/base/temporal.xsd +332 -0
  71. pycsw/core/schemas/ogc/gml/3.1.1/base/temporalReferenceSystems.xsd +251 -0
  72. pycsw/core/schemas/ogc/gml/3.1.1/base/temporalTopology.xsd +186 -0
  73. pycsw/core/schemas/ogc/gml/3.1.1/base/topology.xsd +459 -0
  74. pycsw/core/schemas/ogc/gml/3.1.1/base/units.xsd +170 -0
  75. pycsw/core/schemas/ogc/gml/3.1.1/base/valueObjects.xsd +361 -0
  76. pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20-language.xsd +117 -0
  77. pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20.xsd +234 -0
  78. pycsw/core/schemas/ogc/gml/3.2.1/basicTypes.xsd +268 -0
  79. pycsw/core/schemas/ogc/gml/3.2.1/coordinateOperations.xsd +525 -0
  80. pycsw/core/schemas/ogc/gml/3.2.1/coordinateReferenceSystems.xsd +373 -0
  81. pycsw/core/schemas/ogc/gml/3.2.1/coordinateSystems.xsd +297 -0
  82. pycsw/core/schemas/ogc/gml/3.2.1/coverage.xsd +292 -0
  83. pycsw/core/schemas/ogc/gml/3.2.1/datums.xsd +287 -0
  84. pycsw/core/schemas/ogc/gml/3.2.1/defaultStyle.xsd +453 -0
  85. pycsw/core/schemas/ogc/gml/3.2.1/deprecatedTypes.xsd +1133 -0
  86. pycsw/core/schemas/ogc/gml/3.2.1/dictionary.xsd +90 -0
  87. pycsw/core/schemas/ogc/gml/3.2.1/direction.xsd +84 -0
  88. pycsw/core/schemas/ogc/gml/3.2.1/dynamicFeature.xsd +109 -0
  89. pycsw/core/schemas/ogc/gml/3.2.1/feature.xsd +94 -0
  90. pycsw/core/schemas/ogc/gml/3.2.1/geometryAggregates.xsd +197 -0
  91. pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic0d1d.xsd +277 -0
  92. pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic2d.xsd +124 -0
  93. pycsw/core/schemas/ogc/gml/3.2.1/geometryComplexes.xsd +95 -0
  94. pycsw/core/schemas/ogc/gml/3.2.1/geometryPrimitives.xsd +846 -0
  95. pycsw/core/schemas/ogc/gml/3.2.1/gml.xsd +20 -0
  96. pycsw/core/schemas/ogc/gml/3.2.1/gmlBase.xsd +185 -0
  97. pycsw/core/schemas/ogc/gml/3.2.1/grids.xsd +64 -0
  98. pycsw/core/schemas/ogc/gml/3.2.1/measures.xsd +68 -0
  99. pycsw/core/schemas/ogc/gml/3.2.1/observation.xsd +95 -0
  100. pycsw/core/schemas/ogc/gml/3.2.1/referenceSystems.xsd +70 -0
  101. pycsw/core/schemas/ogc/gml/3.2.1/temporal.xsd +269 -0
  102. pycsw/core/schemas/ogc/gml/3.2.1/temporalReferenceSystems.xsd +189 -0
  103. pycsw/core/schemas/ogc/gml/3.2.1/temporalTopology.xsd +119 -0
  104. pycsw/core/schemas/ogc/gml/3.2.1/topology.xsd +386 -0
  105. pycsw/core/schemas/ogc/gml/3.2.1/units.xsd +162 -0
  106. pycsw/core/schemas/ogc/gml/3.2.1/valueObjects.xsd +205 -0
  107. pycsw/core/schemas/ogc/ogcapi/records/part1/1.0/ogcapi-records-1.yaml +932 -0
  108. pycsw/core/schemas/ogc/ows/1.0.0/ows19115subset.xsd +222 -0
  109. pycsw/core/schemas/ogc/ows/1.0.0/owsAll.xsd +20 -0
  110. pycsw/core/schemas/ogc/ows/1.0.0/owsCommon.xsd +155 -0
  111. pycsw/core/schemas/ogc/ows/1.0.0/owsDataIdentification.xsd +112 -0
  112. pycsw/core/schemas/ogc/ows/1.0.0/owsExceptionReport.xsd +67 -0
  113. pycsw/core/schemas/ogc/ows/1.0.0/owsGetCapabilities.xsd +108 -0
  114. pycsw/core/schemas/ogc/ows/1.0.0/owsOperationsMetadata.xsd +161 -0
  115. pycsw/core/schemas/ogc/ows/1.0.0/owsServiceIdentification.xsd +55 -0
  116. pycsw/core/schemas/ogc/ows/1.0.0/owsServiceProvider.xsd +46 -0
  117. pycsw/core/schemas/ogc/ows/1.1.0/ows19115subset.xsd +236 -0
  118. pycsw/core/schemas/ogc/ows/1.1.0/owsAll.xsd +23 -0
  119. pycsw/core/schemas/ogc/ows/1.1.0/owsCommon.xsd +158 -0
  120. pycsw/core/schemas/ogc/ows/1.1.0/owsContents.xsd +87 -0
  121. pycsw/core/schemas/ogc/ows/1.1.0/owsDataIdentification.xsd +128 -0
  122. pycsw/core/schemas/ogc/ows/1.1.0/owsDomainType.xsd +280 -0
  123. pycsw/core/schemas/ogc/ows/1.1.0/owsExceptionReport.xsd +77 -0
  124. pycsw/core/schemas/ogc/ows/1.1.0/owsGetCapabilities.xsd +113 -0
  125. pycsw/core/schemas/ogc/ows/1.1.0/owsGetResourceByID.xsd +52 -0
  126. pycsw/core/schemas/ogc/ows/1.1.0/owsInputOutputData.xsd +60 -0
  127. pycsw/core/schemas/ogc/ows/1.1.0/owsManifest.xsd +125 -0
  128. pycsw/core/schemas/ogc/ows/1.1.0/owsOperationsMetadata.xsd +141 -0
  129. pycsw/core/schemas/ogc/ows/1.1.0/owsServiceIdentification.xsd +61 -0
  130. pycsw/core/schemas/ogc/ows/1.1.0/owsServiceProvider.xsd +48 -0
  131. pycsw/core/schemas/ogc/ows/2.0/ows19115subset.xsd +364 -0
  132. pycsw/core/schemas/ogc/ows/2.0/owsAdditionalParameters.xsd +114 -0
  133. pycsw/core/schemas/ogc/ows/2.0/owsAll.xsd +29 -0
  134. pycsw/core/schemas/ogc/ows/2.0/owsCommon.xsd +275 -0
  135. pycsw/core/schemas/ogc/ows/2.0/owsContents.xsd +163 -0
  136. pycsw/core/schemas/ogc/ows/2.0/owsDataIdentification.xsd +202 -0
  137. pycsw/core/schemas/ogc/ows/2.0/owsDomainType.xsd +388 -0
  138. pycsw/core/schemas/ogc/ows/2.0/owsExceptionReport.xsd +126 -0
  139. pycsw/core/schemas/ogc/ows/2.0/owsGetCapabilities.xsd +220 -0
  140. pycsw/core/schemas/ogc/ows/2.0/owsGetResourceByID.xsd +83 -0
  141. pycsw/core/schemas/ogc/ows/2.0/owsInputOutputData.xsd +98 -0
  142. pycsw/core/schemas/ogc/ows/2.0/owsManifest.xsd +181 -0
  143. pycsw/core/schemas/ogc/ows/2.0/owsOperationsMetadata.xsd +234 -0
  144. pycsw/core/schemas/ogc/ows/2.0/owsServiceIdentification.xsd +98 -0
  145. pycsw/core/schemas/ogc/ows/2.0/owsServiceProvider.xsd +64 -0
  146. pycsw/core/schemas/w3c/1999/xlink.xsd +271 -0
  147. pycsw/core/schemas/w3c/2001/xml.xsd +287 -0
  148. pycsw/core/util.py +552 -0
  149. pycsw/oaipmh.py +311 -0
  150. pycsw/ogc/__init__.py +29 -0
  151. pycsw/ogc/api/__init__.py +28 -0
  152. pycsw/ogc/api/oapi.py +558 -0
  153. pycsw/ogc/api/records.py +1414 -0
  154. pycsw/ogc/api/templates/_base.html +76 -0
  155. pycsw/ogc/api/templates/collection.html +44 -0
  156. pycsw/ogc/api/templates/collections.html +45 -0
  157. pycsw/ogc/api/templates/conformance.html +21 -0
  158. pycsw/ogc/api/templates/exception.html +8 -0
  159. pycsw/ogc/api/templates/item.html +228 -0
  160. pycsw/ogc/api/templates/items.html +337 -0
  161. pycsw/ogc/api/templates/landing_page.html +27 -0
  162. pycsw/ogc/api/templates/openapi.html +58 -0
  163. pycsw/ogc/api/templates/queryables.html +50 -0
  164. pycsw/ogc/api/templates/stac_items.html +173 -0
  165. pycsw/ogc/api/templates/static/favicon.ico +0 -0
  166. pycsw/ogc/api/templates/static/logo-horizontal.png +0 -0
  167. pycsw/ogc/api/templates/static/logo-vertical-darkbg.png +0 -0
  168. pycsw/ogc/api/util.py +252 -0
  169. pycsw/ogc/csw/__init__.py +29 -0
  170. pycsw/ogc/csw/cql.py +133 -0
  171. pycsw/ogc/csw/csw2.py +2042 -0
  172. pycsw/ogc/csw/csw3.py +2193 -0
  173. pycsw/ogc/fes/__init__.py +29 -0
  174. pycsw/ogc/fes/fes1.py +433 -0
  175. pycsw/ogc/fes/fes2.py +451 -0
  176. pycsw/ogc/gml/__init__.py +29 -0
  177. pycsw/ogc/gml/gml3.py +240 -0
  178. pycsw/ogc/gml/gml32.py +243 -0
  179. pycsw/opensearch.py +857 -0
  180. pycsw/plugins/__init__.py +29 -0
  181. pycsw/plugins/outputschemas/__init__.py +31 -0
  182. pycsw/plugins/outputschemas/atom.py +143 -0
  183. pycsw/plugins/outputschemas/datacite.py +375 -0
  184. pycsw/plugins/outputschemas/dif.py +213 -0
  185. pycsw/plugins/outputschemas/fgdc.py +180 -0
  186. pycsw/plugins/outputschemas/gm03.py +240 -0
  187. pycsw/plugins/profiles/__init__.py +29 -0
  188. pycsw/plugins/profiles/apiso/__init__.py +29 -0
  189. pycsw/plugins/profiles/apiso/apiso.py +757 -0
  190. pycsw/plugins/profiles/apiso/schemas/ogc/csw/2.0.2/profiles/apiso/1.0.0/apiso.xsd +13 -0
  191. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/basicTypes.xsd +429 -0
  192. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gco.xsd +12 -0
  193. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gcoBase.xsd +61 -0
  194. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/applicationSchema.xsd +42 -0
  195. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/citation.xsd +275 -0
  196. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/constraints.xsd +106 -0
  197. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/content.xsd +188 -0
  198. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/dataQuality.xsd +554 -0
  199. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/distribution.xsd +202 -0
  200. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/extent.xsd +205 -0
  201. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/freeText.xsd +122 -0
  202. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/gmd.xsd +12 -0
  203. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/identification.xsd +348 -0
  204. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/maintenance.xsd +86 -0
  205. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataApplication.xsd +175 -0
  206. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataEntity.xsd +70 -0
  207. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataExtension.xsd +99 -0
  208. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/portrayalCatalogue.xsd +36 -0
  209. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/referenceSystem.xsd +100 -0
  210. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/spatialRepresentation.xsd +237 -0
  211. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/basicTypes.xsd +267 -0
  212. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateOperations.xsd +639 -0
  213. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateReferenceSystems.xsd +526 -0
  214. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateSystems.xsd +401 -0
  215. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coverage.xsd +462 -0
  216. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/datums.xsd +342 -0
  217. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dictionary.xsd +129 -0
  218. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/direction.xsd +80 -0
  219. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dynamicFeature.xsd +142 -0
  220. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/feature.xsd +215 -0
  221. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryAggregates.xsd +216 -0
  222. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic0d1d.xsd +304 -0
  223. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic2d.xsd +123 -0
  224. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryComplexes.xsd +89 -0
  225. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryPrimitives.xsd +892 -0
  226. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gml.xsd +14 -0
  227. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gmlBase.xsd +305 -0
  228. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/grids.xsd +58 -0
  229. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/measures.xsd +167 -0
  230. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/observation.xsd +90 -0
  231. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/referenceSystems.xsd +69 -0
  232. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporal.xsd +263 -0
  233. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalReferenceSystems.xsd +183 -0
  234. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalTopology.xsd +124 -0
  235. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/topology.xsd +372 -0
  236. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/units.xsd +156 -0
  237. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/valueObjects.xsd +212 -0
  238. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/catalogues.xsd +112 -0
  239. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/codelistItem.xsd +168 -0
  240. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/crsItem.xsd +1030 -0
  241. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/extendedTypes.xsd +75 -0
  242. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmx.xsd +2 -0
  243. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmxUsage.xsd +127 -0
  244. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/uomItem.xsd +162 -0
  245. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/gsr.xsd +12 -0
  246. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/spatialReferencing.xsd +24 -0
  247. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/geometry.xsd +35 -0
  248. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/gss.xsd +12 -0
  249. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/gts.xsd +12 -0
  250. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/temporalObjects.xsd +34 -0
  251. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceMetadata.xsd +197 -0
  252. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceModel.xsd +220 -0
  253. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/srv.xsd +13 -0
  254. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/basicTypes.xsd +431 -0
  255. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gco.xsd +12 -0
  256. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gcoBase.xsd +63 -0
  257. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/applicationSchema.xsd +43 -0
  258. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/citation.xsd +276 -0
  259. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/constraints.xsd +107 -0
  260. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/content.xsd +190 -0
  261. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/dataQuality.xsd +556 -0
  262. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/distribution.xsd +203 -0
  263. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/extent.xsd +206 -0
  264. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/freeText.xsd +123 -0
  265. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/gmd.xsd +12 -0
  266. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/identification.xsd +349 -0
  267. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/maintenance.xsd +87 -0
  268. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataApplication.xsd +176 -0
  269. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataEntity.xsd +71 -0
  270. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataExtension.xsd +100 -0
  271. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/portrayalCatalogue.xsd +37 -0
  272. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/referenceSystem.xsd +101 -0
  273. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/spatialRepresentation.xsd +238 -0
  274. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/catalogues.xsd +113 -0
  275. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/codelistItem.xsd +169 -0
  276. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/crsItem.xsd +1031 -0
  277. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/extendedTypes.xsd +76 -0
  278. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmx.xsd +12 -0
  279. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmxUsage.xsd +128 -0
  280. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/uomItem.xsd +163 -0
  281. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/gsr.xsd +12 -0
  282. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/spatialReferencing.xsd +25 -0
  283. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/geometry.xsd +36 -0
  284. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/gss.xsd +12 -0
  285. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/gts.xsd +12 -0
  286. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/temporalObjects.xsd +35 -0
  287. pycsw/plugins/profiles/ebrim/__init__.py +29 -0
  288. pycsw/plugins/profiles/ebrim/ebrim.py +174 -0
  289. pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim-iri.xsd +146 -0
  290. pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim.xsd +104 -0
  291. pycsw/plugins/profiles/iso19115p3/__init__.py +29 -0
  292. pycsw/plugins/profiles/iso19115p3/iso19115p3.py +854 -0
  293. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/cat.xsd +13 -0
  294. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/catalogues.xsd +53 -0
  295. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/codelistItem.xsd +54 -0
  296. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/crsItem.xsd +181 -0
  297. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/uomItem.xsd +38 -0
  298. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/cit.xsd +7 -0
  299. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/citation.xsd +514 -0
  300. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/cit.xsd +10 -0
  301. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/citation.xsd +523 -0
  302. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/baseTypes2014.xsd +530 -0
  303. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/gco.xsd +16 -0
  304. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes.xsd +92 -0
  305. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes_autoFromShapeChange.xsd +60 -0
  306. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/gcx.xsd +8 -0
  307. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/extent.xsd +241 -0
  308. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/gex.xsd +9 -0
  309. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmlWrapperTypes2014.xsd +159 -0
  310. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmw.xsd +14 -0
  311. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/lan.xsd +8 -0
  312. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/language.xsd +140 -0
  313. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/acquisitionInformationImagery.xsd +622 -0
  314. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/mac.xsd +11 -0
  315. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/acquisitionInformationImagery.xsd +590 -0
  316. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/event.xsd +108 -0
  317. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/mac.xsd +10 -0
  318. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/applicationSchema.xsd +61 -0
  319. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/mas.xsd +9 -0
  320. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/AbstractCommonClasses.xsd +358 -0
  321. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/commonClasses.xsd +220 -0
  322. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/mcc.xsd +8 -0
  323. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/constraints.xsd +188 -0
  324. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/mco.xsd +8 -0
  325. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/md1.xsd +9 -0
  326. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/metadataWExtendedType.xsd +4 -0
  327. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/md2.xsd +15 -0
  328. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/metadataWithExtensions.xsd +4 -0
  329. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/mda.xsd +8 -0
  330. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/metadataApplication.xsd +200 -0
  331. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/mdb.xsd +14 -0
  332. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/metadataBase.xsd +98 -0
  333. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/mdb.xsd +20 -0
  334. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/metadataBase.xsd +102 -0
  335. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/mds.xsd +22 -0
  336. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/metadataDataServices.xsd +4 -0
  337. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/mdt.xsd +13 -0
  338. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/metadataTransfer.xsd +111 -0
  339. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/metadataExtension.xsd +156 -0
  340. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/mex.xsd +8 -0
  341. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/maintenance.xsd +76 -0
  342. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/mmi.xsd +8 -0
  343. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/mpc.xsd +8 -0
  344. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/portrayalCatalogue.xsd +31 -0
  345. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/content.xsd +416 -0
  346. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/contentInformationImagery.xsd +185 -0
  347. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/mrc.xsd +11 -0
  348. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/content.xsd +419 -0
  349. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/contentInformationImagery.xsd +171 -0
  350. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/mrc.xsd +11 -0
  351. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/distribution.xsd +267 -0
  352. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/mrd.xsd +8 -0
  353. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/identification.xsd +517 -0
  354. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/mri.xsd +9 -0
  355. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineage.xsd +147 -0
  356. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineageImagery.xsd +236 -0
  357. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/mrl.xsd +9 -0
  358. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineage.xsd +147 -0
  359. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineageImagery.xsd +312 -0
  360. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/mrl.xsd +11 -0
  361. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/mrs.xsd +8 -0
  362. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/referenceSystem.xsd +47 -0
  363. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/msr.xsd +10 -0
  364. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentation.xsd +375 -0
  365. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentationImagery.xsd +119 -0
  366. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/msr.xsd +15 -0
  367. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentation.xsd +381 -0
  368. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentationImagery.xsd +120 -0
  369. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/serviceInformation.xsd +272 -0
  370. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/srv.xsd +6 -0
  371. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/serviceInformation.xsd +278 -0
  372. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/srv.xsd +6 -0
  373. pycsw/plugins/profiles/profile.py +141 -0
  374. pycsw/plugins/repository/__init__.py +29 -0
  375. pycsw/plugins/repository/odc/__init__.py +29 -0
  376. pycsw/plugins/repository/odc/odc.py +153 -0
  377. pycsw/server.py +938 -0
  378. pycsw/sru.py +217 -0
  379. pycsw/stac/__init__.py +29 -0
  380. pycsw/stac/api.py +571 -0
  381. pycsw/wsgi.py +233 -0
  382. pycsw/wsgi_flask.py +345 -0
pycsw/server.py ADDED
@@ -0,0 +1,938 @@
1
+ # -*- coding: utf-8 -*-
2
+ # =================================================================
3
+ #
4
+ # Authors: Tom Kralidis <tomkralidis@gmail.com>
5
+ # Angelos Tzotsos <tzotsos@gmail.com>
6
+ #
7
+ # Copyright (c) 2024 Tom Kralidis
8
+ # Copyright (c) 2015 Angelos Tzotsos
9
+ # Copyright (c) 2016 James Dickens
10
+ # Copyright (c) 2016 Ricardo Silva
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person
13
+ # obtaining a copy of this software and associated documentation
14
+ # files (the "Software"), to deal in the Software without
15
+ # restriction, including without limitation the rights to use,
16
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ # copies of the Software, and to permit persons to whom the
18
+ # Software is furnished to do so, subject to the following
19
+ # conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be
22
+ # included in all copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
26
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
28
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
29
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
30
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
31
+ # OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ # =================================================================
34
+
35
+ import logging
36
+ import os
37
+ from urllib.parse import parse_qsl, splitquery, urlparse
38
+ from io import StringIO
39
+ import sys
40
+ from time import time
41
+ import wsgiref.util
42
+
43
+ from pycsw.core.etree import etree
44
+ from pycsw import oaipmh, opensearch, sru
45
+ from pycsw.plugins.profiles import profile as pprofile
46
+ import pycsw.plugins.outputschemas
47
+ from pycsw.core import config, log, util
48
+ from pycsw.ogc.api.util import yaml_load
49
+ from pycsw.ogc.csw import csw2, csw3
50
+
51
+ LOGGER = logging.getLogger(__name__)
52
+
53
+
54
+ class Csw(object):
55
+ """ Base CSW server """
56
+ def __init__(self, rtconfig=None, env=None, version='3.0.0'):
57
+ """ Initialize CSW """
58
+
59
+ self.environ = env or os.environ
60
+ self.context = config.StaticContext()
61
+
62
+ # Lazy load this when needed
63
+ # (it will permanently update global cfg namespaces)
64
+ self.sruobj = None
65
+ self.opensearchobj = None
66
+ self.oaipmhobj = None
67
+
68
+ # init kvp
69
+ self.kvp = {}
70
+
71
+ self.mode = 'csw'
72
+ self.asynchronous = False
73
+ self.soap = False
74
+ self.request = None
75
+ self.exception = False
76
+ self.status = 'OK'
77
+ self.profiles = None
78
+ self.manager = False
79
+ self.outputschemas = {}
80
+ self.mimetype = 'application/xml; charset=UTF-8'
81
+ self.encoding = 'UTF-8'
82
+ self.pretty_print = 0
83
+ self.domainquerytype = 'list'
84
+ self.orm = 'django'
85
+ self.language = {'639_code': 'en', 'text': 'english'}
86
+ self.process_time_start = time()
87
+ self.xslts = []
88
+
89
+ # define CSW implementation object (default CSW3)
90
+ self.iface = csw3.Csw3(server_csw=self)
91
+ self.request_version = version
92
+
93
+ if self.request_version == '2.0.2':
94
+ self.iface = csw2.Csw2(server_csw=self)
95
+ self.context.set_model('csw')
96
+
97
+ # load user configuration
98
+ try:
99
+ LOGGER.info('Loading user configuration')
100
+ if isinstance(rtconfig, dict): # dictionary
101
+ self.config = rtconfig
102
+ else: # configuration file
103
+ with open(rtconfig, encoding='utf8') as fh:
104
+ self.config = yaml_load(fh)
105
+ except Exception as err:
106
+ msg = 'Could not load configuration'
107
+ LOGGER.exception('%s %s: %s', msg, rtconfig, err)
108
+ self.response = self.iface.exceptionreport(
109
+ 'NoApplicableCode', 'service', msg)
110
+ return
111
+
112
+ # set server.home safely
113
+ # TODO: make this more abstract
114
+ self.config['server']['home'] = os.path.dirname(os.path.join(os.path.dirname(__file__), '..'))
115
+
116
+ if 'PYCSW_IS_CSW' in self.environ and self.environ['PYCSW_IS_CSW']:
117
+ self.config['server']['url'] = self.config['server']['url'].rstrip('/') + '/csw'
118
+ if 'PYCSW_IS_OPENSEARCH' in self.environ and self.environ['PYCSW_IS_OPENSEARCH']:
119
+ self.config['server']['url'] = self.config['server']['url'].rstrip('/') + '/opensearch'
120
+ self.mode = 'opensearch'
121
+
122
+ self.context.pycsw_home = self.config['server'].get('home')
123
+ self.context.url = self.config['server']['url']
124
+
125
+ self.context.server = self
126
+
127
+ log.setup_logger(self.config.get('logging', {}))
128
+
129
+ LOGGER.info('running configuration %s', rtconfig)
130
+ LOGGER.debug('QUERY_STRING: %s', self.environ['QUERY_STRING'])
131
+
132
+ # set OGC schemas location
133
+ if 'ogc_schemas_base' not in self.config['server']:
134
+ self.config['server']['ogc_schemas_base'] = self.context.ogc_schemas_base
135
+
136
+ # set mimetype
137
+ if 'mimetype' in self.config['server']:
138
+ self.mimetype = self.config['server']['mimetype'].encode()
139
+
140
+ # set encoding
141
+ if 'encoding' in self.config['server']:
142
+ self.encoding = self.config['server']['encoding']
143
+
144
+ # set domainquerytype
145
+ if 'domainquerytype' in self.config['server']:
146
+ self.domainquerytype = self.config['server']['domainquerytype']
147
+
148
+ # set XML pretty print
149
+ if self.config['server'].get('pretty_print', False):
150
+ self.pretty_print = 1
151
+
152
+ # set Spatial Ranking option
153
+ if self.config['server'].get('spatial_ranking', False):
154
+ util.ranking_enabled = True
155
+
156
+ # set language default
157
+ if 'language' in self.config['server']:
158
+ try:
159
+ LOGGER.info('Setting language')
160
+ lang_code = self.config['server']['language'].split('-')[0]
161
+ self.language['639_code'] = lang_code
162
+ self.language['text'] = self.context.languages[lang_code]
163
+ except Exception as err:
164
+ LOGGER.exception('Could not set language: %s', err)
165
+ pass
166
+
167
+ LOGGER.debug('Configuration: %s.', self.config)
168
+ LOGGER.debug('Model: %s.', self.context.model)
169
+
170
+ # load user-defined mappings if they exist
171
+ custom_mappings_path = self.config['repository'].get('mappings')
172
+ if custom_mappings_path is not None:
173
+ md_core_model = util.load_custom_repo_mappings(custom_mappings_path)
174
+ if md_core_model is not None:
175
+ self.context.md_core_model = md_core_model
176
+ self.context.refresh_dc(md_core_model)
177
+ else:
178
+ LOGGER.exception('Could not load custom mappings: %s')
179
+ self.response = self.iface.exceptionreport(
180
+ 'NoApplicableCode', 'service',
181
+ 'Could not load repository.mappings')
182
+
183
+ # load user-defined max attempt to retry db connection
184
+ self.max_retries = int(self.config['repository'].get('max_retries', 5))
185
+
186
+ # load outputschemas
187
+ LOGGER.info('Loading outputschemas')
188
+
189
+ for osch in pycsw.plugins.outputschemas.__all__:
190
+ output_schema_module = __import__(
191
+ 'pycsw.plugins.outputschemas.%s' % osch)
192
+ mod = getattr(output_schema_module.plugins.outputschemas, osch)
193
+ self.outputschemas[mod.NAMESPACE] = mod
194
+
195
+ LOGGER.debug('Outputschemas loaded: %s.', self.outputschemas)
196
+ LOGGER.debug('Namespaces: %s', self.context.namespaces)
197
+
198
+ LOGGER.info('Loading XSLT transformations')
199
+
200
+ for x in self.config.get('xslt', []):
201
+ LOGGER.debug('Loading XSLT %s' % x['transform'])
202
+ input_os = x['input_os']
203
+ output_os = x['output_os']
204
+ self.xslts.append({
205
+ f'xslt:{input_os},{output_os}': x['transform']
206
+ })
207
+ # TODO: add output schemas to namespace prefixes
208
+
209
+ def expand_path(self, path):
210
+ """ return safe path for WSGI environments """
211
+ if 'local.app_root' in self.environ and not os.path.isabs(path):
212
+ return os.path.join(self.environ['local.app_root'], path)
213
+ else:
214
+ return path
215
+
216
+ def dispatch_wsgi(self):
217
+ """ WSGI handler """
218
+
219
+ if hasattr(self, 'response'):
220
+ return self._write_response()
221
+
222
+ LOGGER.debug('WSGI mode detected')
223
+
224
+ if self.environ['REQUEST_METHOD'] == 'POST':
225
+ try:
226
+ request_body_size = int(self.environ.get('CONTENT_LENGTH', 0))
227
+ except (ValueError):
228
+ request_body_size = 0
229
+
230
+ self.requesttype = 'POST'
231
+ self.request = self.environ['wsgi.input'].read(request_body_size)
232
+ LOGGER.debug('Request type: POST. Request:\n%s\n', self.request)
233
+
234
+ else: # it's a GET request
235
+ self.requesttype = 'GET'
236
+ self.request = wsgiref.util.request_uri(self.environ)
237
+ try:
238
+ if '{' in self.request or '%7D' in self.request:
239
+ LOGGER.debug('Looks like an OpenSearch URL template')
240
+ query_part = self.request.split('?', 1)[-1]
241
+ else:
242
+ query_part = splitquery(self.request)[-1]
243
+ self.kvp = dict(parse_qsl(query_part, keep_blank_values=True))
244
+ except AttributeError as err:
245
+ LOGGER.exception('Could not parse query string')
246
+ self.kvp = {}
247
+ LOGGER.debug('Request type: GET. Request:\n%s\n', self.request)
248
+ return self.dispatch()
249
+
250
+ def opensearch(self):
251
+ """ enable OpenSearch """
252
+ if not self.opensearchobj:
253
+ self.opensearchobj = opensearch.OpenSearch(self.context)
254
+
255
+ return self.opensearchobj
256
+
257
+ def sru(self):
258
+ """ enable SRU """
259
+ if not self.sruobj:
260
+ self.sruobj = sru.Sru(self.context)
261
+
262
+ return self.sruobj
263
+
264
+ def oaipmh(self):
265
+ """ enable OAI-PMH """
266
+ if not self.oaipmhobj:
267
+ self.oaipmhobj = oaipmh.OAIPMH(self.context, self.config)
268
+ return self.oaipmhobj
269
+
270
+ def dispatch(self, writer=sys.stdout, write_headers=True):
271
+ """ Handle incoming HTTP request """
272
+
273
+ error = 0
274
+ if self.requesttype == 'GET':
275
+ self.kvp = self.normalize_kvp(self.kvp)
276
+ version_202 = ('version' in self.kvp and
277
+ self.kvp['version'] == '2.0.2')
278
+ accept_version_202 = ('acceptversions' in self.kvp and
279
+ '2.0.2' in self.kvp['acceptversions'])
280
+ if version_202 or accept_version_202:
281
+ self.request_version = '2.0.2'
282
+ elif self.requesttype == 'POST':
283
+ if self.request.find(b'cat/csw/2.0.2') != -1:
284
+ self.request_version = '2.0.2'
285
+ elif self.request.find(b'cat/csw/3.0') != -1:
286
+ self.request_version = '3.0.0'
287
+
288
+ if 'PYCSW_IS_OAIPMH' in self.environ and self.environ['PYCSW_IS_OAIPMH']:
289
+ self.config['server']['url'] = self.config['server']['url'].rstrip('/') + '/oaipmh'
290
+ self.kvp['mode'] = 'oaipmh'
291
+ if 'PYCSW_IS_SRU' in self.environ and self.environ['PYCSW_IS_SRU']:
292
+ self.config['server']['url'] = self.config['server']['url'].rstrip('/') + '/sru'
293
+ self.kvp['mode'] = 'sru'
294
+
295
+ if (not isinstance(self.kvp, str) and 'mode' in self.kvp and
296
+ self.kvp['mode'] == 'sru'):
297
+ self.mode = 'sru'
298
+ self.request_version = '2.0.2'
299
+ LOGGER.info('SRU mode detected; processing request')
300
+ self.kvp = self.sru().request_sru2csw(self.kvp)
301
+
302
+ if (not isinstance(self.kvp, str) and 'mode' in self.kvp and
303
+ self.kvp['mode'] == 'oaipmh'):
304
+ self.mode = 'oaipmh'
305
+ self.request_version = '2.0.2'
306
+ LOGGER.info('OAI-PMH mode detected; processing request.')
307
+ self.oaiargs = dict((k, v) for k, v in self.kvp.items() if k)
308
+ self.kvp = self.oaipmh().request(self.kvp)
309
+
310
+ if self.request_version == '2.0.2':
311
+ self.iface = csw2.Csw2(server_csw=self)
312
+ self.context.set_model('csw')
313
+
314
+ # configure transaction support, if specified in config
315
+ self._gen_manager()
316
+
317
+ namespaces = self.context.namespaces
318
+ ops = self.context.model['operations']
319
+ constraints = self.context.model['constraints']
320
+ # generate domain model
321
+ # NOTE: We should probably avoid this sort of mutable state for WSGI
322
+ if 'GetDomain' not in ops:
323
+ ops['GetDomain'] = self.context.gen_domains()
324
+
325
+ # generate distributed search model, if specified in config
326
+ if 'federatedcatalogues' in self.config:
327
+ LOGGER.info('Configuring distributed search')
328
+
329
+ constraints['FederatedCatalogues'] = {'values': []}
330
+
331
+ for fedcat in self.config['federatedcatalogues']:
332
+ LOGGER.debug('federated catalogue: %s', fedcat)
333
+ constraints['FederatedCatalogues']['values'].append(fedcat)
334
+
335
+ for key, value in self.outputschemas.items():
336
+ get_records_params = ops['GetRecords']['parameters']
337
+ get_records_params['outputSchema']['values'].append(
338
+ value.NAMESPACE)
339
+ get_records_by_id_params = ops['GetRecordById']['parameters']
340
+ get_records_by_id_params['outputSchema']['values'].append(
341
+ value.NAMESPACE)
342
+ if 'Harvest' in ops:
343
+ harvest_params = ops['Harvest']['parameters']
344
+ harvest_params['ResourceType']['values'].append(
345
+ value.NAMESPACE)
346
+
347
+ LOGGER.info('Setting MaxRecordDefault')
348
+ if 'maxrecords' in self.config['server']:
349
+ constraints['MaxRecordDefault']['values'] = [
350
+ self.config['server']['maxrecords']]
351
+
352
+ # load profiles
353
+ if 'profiles' in self.config:
354
+ self.profiles = pprofile.load_profiles(
355
+ os.path.join('pycsw', 'plugins', 'profiles'),
356
+ pprofile.Profile,
357
+ self.config['profiles']
358
+ )
359
+
360
+ for prof in self.profiles['plugins'].keys():
361
+ tmp = self.profiles['plugins'][prof](self.context.model,
362
+ namespaces,
363
+ self.context)
364
+
365
+ key = tmp.outputschema # to ref by outputschema
366
+ self.profiles['loaded'][key] = tmp
367
+ self.profiles['loaded'][key].extend_core(self.context.model,
368
+ namespaces,
369
+ self.config)
370
+
371
+ LOGGER.debug('Profiles loaded: %s' % list(self.profiles['loaded'].keys()))
372
+
373
+ # init repository
374
+ # look for tablename, set 'records' as default
375
+ if 'table' not in self.config['repository']:
376
+ self.config['repository']['table'] = 'records'
377
+
378
+ repo_filter = self.config['repository'].get('filter')
379
+
380
+ if 'source' in self.config['repository']: # load custom repository
381
+ rs = self.config['repository']['source']
382
+ rs_modname, rs_clsname = rs.rsplit('.', 1)
383
+
384
+ rs_mod = __import__(rs_modname, globals(), locals(), [rs_clsname])
385
+ rs_cls = getattr(rs_mod, rs_clsname)
386
+
387
+ try:
388
+ connection_done = False
389
+ max_attempts = 0
390
+ while not connection_done and max_attempts <= self.max_retries:
391
+ try:
392
+ self.repository = rs_cls(self.context, repo_filter)
393
+ LOGGER.debug('Custom repository %s loaded (%s)', rs, self.repository.dbtype)
394
+ connection_done = True
395
+ except Exception as err:
396
+ LOGGER.debug(f'Repository not loaded retry connection {max_attempts}: {err}')
397
+ max_attempts += 1
398
+ except Exception as err:
399
+ msg = 'Could not load custom repository %s: %s' % (rs, err)
400
+ LOGGER.exception(msg)
401
+ error = 1
402
+ code = 'NoApplicableCode'
403
+ locator = 'service'
404
+ text = 'Could not initialize repository. Check server logs'
405
+
406
+ else: # load default repository
407
+ self.orm = 'sqlalchemy'
408
+ from pycsw.core import repository
409
+ try:
410
+ LOGGER.info('Loading default repository')
411
+ connection_done = False
412
+ max_attempts = 0
413
+ while not connection_done and max_attempts <= self.max_retries:
414
+ try:
415
+ self.repository = repository.Repository(
416
+ self.config['repository']['database'],
417
+ self.context,
418
+ self.environ.get('local.app_root', None),
419
+ self.config['repository'].get('table'),
420
+ repo_filter
421
+ )
422
+ LOGGER.debug(
423
+ 'Repository loaded (local): %s.' % self.repository.dbtype)
424
+ connection_done = True
425
+ except Exception:
426
+ LOGGER.debug(f'Repository not loaded retry connection {max_attempts}: {err}')
427
+ max_attempts += 1
428
+ except Exception as err:
429
+ msg = 'Could not load repository (local): %s' % err
430
+ LOGGER.exception(msg)
431
+ error = 1
432
+ code = 'NoApplicableCode'
433
+ locator = 'service'
434
+ text = 'Could not initialize repository. Check server logs'
435
+
436
+ if self.requesttype == 'POST':
437
+ LOGGER.debug('HTTP POST request')
438
+ LOGGER.debug('CSW version: %s', self.iface.version)
439
+ self.kvp = self.iface.parse_postdata(self.request)
440
+
441
+ if isinstance(self.kvp, str): # it's an exception
442
+ error = 1
443
+ locator = 'service'
444
+ text = self.kvp
445
+ if (self.kvp.find('the document is not valid') != -1 or
446
+ self.kvp.find('document not well-formed') != -1):
447
+ code = 'NoApplicableCode'
448
+ else:
449
+ code = 'InvalidParameterValue'
450
+
451
+ LOGGER.debug('HTTP Headers:\n%s.', self.environ)
452
+ LOGGER.debug('Parsed request parameters: %s', self.kvp)
453
+
454
+ if (not isinstance(self.kvp, str) and 'mode' in self.kvp and
455
+ self.kvp['mode'] == 'opensearch'):
456
+ self.mode = 'opensearch'
457
+ LOGGER.info('OpenSearch mode detected; processing request.')
458
+ self.kvp['outputschema'] = 'http://www.w3.org/2005/Atom'
459
+
460
+ if ((len(self.kvp) == 0 and self.request_version == '3.0.0') or
461
+ (len(self.kvp) == 1 and 'config' in self.kvp)):
462
+ LOGGER.info('Turning on default csw30:Capabilities for base URL')
463
+ self.kvp = {
464
+ 'service': 'CSW',
465
+ 'acceptversions': '3.0.0',
466
+ 'request': 'GetCapabilities'
467
+ }
468
+ http_accept = self.environ.get('HTTP_ACCEPT', '')
469
+ if 'application/opensearchdescription+xml' in http_accept:
470
+ self.mode = 'opensearch'
471
+ self.kvp['outputschema'] = 'http://www.w3.org/2005/Atom'
472
+
473
+ if error == 0:
474
+ # test for the basic keyword values (service, version, request)
475
+ basic_options = ['service', 'request']
476
+ request = self.kvp.get('request', '')
477
+ own_version_integer = util.get_version_integer(
478
+ self.request_version)
479
+ if self.request_version == '2.0.2':
480
+ basic_options.append('version')
481
+ if self.request_version == '3.0.0' and 'version' not in self.kvp and self.requesttype == 'POST':
482
+ if 'service' not in self.kvp:
483
+ self.kvp['service'] = 'CSW'
484
+ basic_options.append('service')
485
+ self.kvp['version'] = self.request_version
486
+ basic_options.append('version')
487
+
488
+ for k in basic_options:
489
+ if k not in self.kvp:
490
+ if (k in ['version', 'acceptversions'] and
491
+ request == 'GetCapabilities'):
492
+ pass
493
+ else:
494
+ error = 1
495
+ locator = k
496
+ code = 'MissingParameterValue'
497
+ text = 'Missing keyword: %s' % k
498
+ break
499
+
500
+ # test each of the basic keyword values
501
+ if error == 0:
502
+ # test service
503
+ if self.kvp['service'] != 'CSW':
504
+ error = 1
505
+ locator = 'service'
506
+ code = 'InvalidParameterValue'
507
+ text = 'Invalid value for service: %s.\
508
+ Value MUST be CSW' % self.kvp['service']
509
+
510
+ # test version
511
+ kvp_version = self.kvp.get('version', '')
512
+ try:
513
+ kvp_version_integer = util.get_version_integer(kvp_version)
514
+ except Exception as err:
515
+ kvp_version_integer = 'invalid_value'
516
+ if (request != 'GetCapabilities' and
517
+ kvp_version_integer != own_version_integer):
518
+ error = 1
519
+ locator = 'version'
520
+ code = 'InvalidParameterValue'
521
+ text = ('Invalid value for version: %s. Value MUST be '
522
+ '2.0.2 or 3.0.0' % kvp_version)
523
+
524
+ # check for GetCapabilities acceptversions
525
+ if 'acceptversions' in self.kvp:
526
+ for vers in self.kvp['acceptversions'].split(','):
527
+ vers_integer = util.get_version_integer(vers)
528
+ if vers_integer == own_version_integer:
529
+ break
530
+ else:
531
+ error = 1
532
+ locator = 'acceptversions'
533
+ code = 'VersionNegotiationFailed'
534
+ text = ('Invalid parameter value in '
535
+ 'acceptversions: %s. Value MUST be '
536
+ '2.0.2 or 3.0.0' %
537
+ self.kvp['acceptversions'])
538
+
539
+ # test request
540
+ if self.kvp['request'] not in \
541
+ self.context.model['operations']:
542
+ error = 1
543
+ locator = 'request'
544
+ if request in ['Transaction', 'Harvest']:
545
+ code = 'OperationNotSupported'
546
+ text = '%s operations are not supported' % request
547
+ else:
548
+ code = 'InvalidParameterValue'
549
+ text = 'Invalid value for request: %s' % request
550
+
551
+ if error == 1: # return an ExceptionReport
552
+ LOGGER.error('basic service options error: %s, %s, %s', code, locator, text)
553
+ self.response = self.iface.exceptionreport(code, locator, text)
554
+
555
+ else: # process per the request value
556
+
557
+ if 'responsehandler' in self.kvp:
558
+ # set flag to process asynchronously
559
+ import threading
560
+ self.asynchronous = True
561
+ request_id = self.kvp.get('requestid', None)
562
+ if request_id is None:
563
+ import uuid
564
+ self.kvp['requestid'] = str(uuid.uuid4())
565
+
566
+ if self.kvp['request'] == 'GetCapabilities':
567
+ self.response = self.iface.getcapabilities()
568
+ elif self.kvp['request'] == 'DescribeRecord':
569
+ self.response = self.iface.describerecord()
570
+ elif self.kvp['request'] == 'GetDomain':
571
+ self.response = self.iface.getdomain()
572
+ elif self.kvp['request'] == 'GetRecords':
573
+ if self.asynchronous: # process asynchronously
574
+ threading.Thread(target=self.iface.getrecords).start()
575
+ self.response = self.iface._write_acknowledgement()
576
+ else:
577
+ self.response = self.iface.getrecords()
578
+ elif self.kvp['request'] == 'GetRecordById':
579
+ self.response = self.iface.getrecordbyid()
580
+ elif self.kvp['request'] == 'GetRepositoryItem':
581
+ self.response = self.iface.getrepositoryitem()
582
+ elif self.kvp['request'] == 'Transaction':
583
+ self.response = self.iface.transaction()
584
+ elif self.kvp['request'] == 'Harvest':
585
+ if self.asynchronous: # process asynchronously
586
+ threading.Thread(target=self.iface.harvest).start()
587
+ self.response = self.iface._write_acknowledgement()
588
+ else:
589
+ self.response = self.iface.harvest()
590
+ else:
591
+ self.response = self.iface.exceptionreport(
592
+ 'InvalidParameterValue', 'request',
593
+ 'Invalid request parameter: %s' % self.kvp['request']
594
+ )
595
+
596
+ LOGGER.info('Request processed')
597
+ if self.mode == 'sru':
598
+ LOGGER.info('SRU mode detected; processing response.')
599
+ self.response = self.sru().response_csw2sru(self.response,
600
+ self.environ)
601
+ elif self.mode == 'opensearch':
602
+ LOGGER.info('OpenSearch mode detected; processing response.')
603
+ self.response = self.opensearch().response_csw2opensearch(
604
+ self.response, self.config)
605
+
606
+ elif self.mode == 'oaipmh':
607
+ LOGGER.info('OAI-PMH mode detected; processing response.')
608
+ self.response = self.oaipmh().response(
609
+ self.response, self.oaiargs, self.repository,
610
+ self.config['server']['url']
611
+ )
612
+
613
+ return self._write_response()
614
+
615
+ def getcapabilities(self):
616
+ """ Handle GetCapabilities request """
617
+ return self.iface.getcapabilities()
618
+
619
+ def describerecord(self):
620
+ """ Handle DescribeRecord request """
621
+ return self.iface.describerecord()
622
+
623
+ def getdomain(self):
624
+ """ Handle GetDomain request """
625
+ return self.iface.getdomain()
626
+
627
+ def getrecords(self):
628
+ """ Handle GetRecords request """
629
+ return self.iface.getrecords()
630
+
631
+ def getrecordbyid(self, raw=False):
632
+ """ Handle GetRecordById request """
633
+ return self.iface.getrecordbyid(raw)
634
+
635
+ def getrepositoryitem(self):
636
+ """ Handle GetRepositoryItem request """
637
+ return self.iface.getrepositoryitem()
638
+
639
+ def transaction(self):
640
+ """ Handle Transaction request """
641
+ return self.iface.transaction()
642
+
643
+ def harvest(self):
644
+ """ Handle Harvest request """
645
+ return self.iface.harvest()
646
+
647
+ def _write_response(self):
648
+ """ Generate response """
649
+ # set HTTP response headers and XML declaration
650
+
651
+ xmldecl = ''
652
+ appinfo = ''
653
+
654
+ LOGGER.info('Writing response.')
655
+
656
+ if hasattr(self, 'soap') and self.soap:
657
+ self._gen_soap_wrapper()
658
+
659
+ if etree.__version__ >= '3.5.0': # remove superfluous namespaces
660
+ etree.cleanup_namespaces(self.response,
661
+ keep_ns_prefixes=self.context.keep_ns_prefixes)
662
+
663
+ response = etree.tostring(self.response,
664
+ pretty_print=self.pretty_print,
665
+ encoding='unicode')
666
+
667
+ if (isinstance(self.kvp, dict) and 'outputformat' in self.kvp and
668
+ self.kvp['outputformat'] == 'application/json'):
669
+ self.contenttype = self.kvp['outputformat']
670
+ from pycsw.core.formats import fmt_json
671
+ response = fmt_json.xml2json(response,
672
+ self.context.namespaces,
673
+ self.pretty_print)
674
+ else: # it's XML
675
+ if 'outputformat' in self.kvp:
676
+ self.contenttype = self.kvp['outputformat']
677
+ else:
678
+ self.contenttype = self.mimetype
679
+
680
+ xmldecl = ('<?xml version="1.0" encoding="%s" standalone="no"?>'
681
+ '\n' % self.encoding)
682
+ appinfo = '<!-- pycsw %s -->\n' % self.context.version
683
+
684
+ if isinstance(self.contenttype, bytes):
685
+ self.contenttype = self.contenttype.decode()
686
+
687
+ s = (u'%s%s%s' % (xmldecl, appinfo, response)).encode(self.encoding)
688
+ LOGGER.debug('Response code: %s',
689
+ self.context.response_codes[self.status])
690
+ LOGGER.debug('Response:\n%s', s)
691
+ return [self.context.response_codes[self.status], s]
692
+
693
+ def _gen_soap_wrapper(self):
694
+ """ Generate SOAP wrapper """
695
+ LOGGER.info('Writing SOAP wrapper.')
696
+ node = etree.Element(
697
+ util.nspath_eval('soapenv:Envelope', self.context.namespaces),
698
+ nsmap=self.context.namespaces
699
+ )
700
+
701
+ schema_location_ns = util.nspath_eval('xsi:schemaLocation',
702
+ self.context.namespaces)
703
+ node.attrib[schema_location_ns] = '%s %s' % (
704
+ self.context.namespaces['soapenv'],
705
+ self.context.namespaces['soapenv']
706
+ )
707
+
708
+ node2 = etree.SubElement(
709
+ node, util.nspath_eval('soapenv:Body', self.context.namespaces))
710
+
711
+ if self.exception:
712
+ node3 = etree.SubElement(
713
+ node2,
714
+ util.nspath_eval('soapenv:Fault', self.context.namespaces)
715
+ )
716
+ node4 = etree.SubElement(
717
+ node3,
718
+ util.nspath_eval('soapenv:Code', self.context.namespaces)
719
+ )
720
+
721
+ etree.SubElement(
722
+ node4,
723
+ util.nspath_eval('soapenv:Value', self.context.namespaces)
724
+ ).text = 'soap:Server'
725
+
726
+ node4 = etree.SubElement(
727
+ node3,
728
+ util.nspath_eval('soapenv:Reason', self.context.namespaces)
729
+ )
730
+
731
+ etree.SubElement(
732
+ node4,
733
+ util.nspath_eval('soapenv:Text', self.context.namespaces)
734
+ ).text = 'A server exception was encountered.'
735
+
736
+ node4 = etree.SubElement(
737
+ node3,
738
+ util.nspath_eval('soapenv:Detail', self.context.namespaces)
739
+ )
740
+ node4.append(self.response)
741
+ else:
742
+ node2.append(self.response)
743
+
744
+ self.response = node
745
+
746
+ def _gen_manager(self):
747
+ """ Update self.context.model with CSW-T advertising """
748
+ if self.config['manager'].get('transactions', False):
749
+
750
+ self.manager = True
751
+
752
+ self.context.model['operations_order'].append('Transaction')
753
+
754
+ self.context.model['operations']['Transaction'] = {
755
+ 'methods': {'get': False, 'post': True},
756
+ 'parameters': {}
757
+ }
758
+
759
+ schema_values = [
760
+ 'http://www.opengis.net/cat/csw/2.0.2',
761
+ 'http://www.opengis.net/cat/csw/3.0',
762
+ 'http://www.opengis.net/wms',
763
+ 'http://www.opengis.net/wmts/1.0',
764
+ 'http://www.opengis.net/wfs',
765
+ 'http://www.opengis.net/wfs/2.0',
766
+ 'http://www.opengis.net/wcs',
767
+ 'http://www.opengis.net/wps/1.0.0',
768
+ 'http://www.opengis.net/sos/1.0',
769
+ 'http://www.opengis.net/sos/2.0',
770
+ 'http://www.isotc211.org/2005/gmi',
771
+ 'urn:geoss:waf',
772
+ ]
773
+
774
+ self.context.model['operations_order'].append('Harvest')
775
+
776
+ self.context.model['operations']['Harvest'] = {
777
+ 'methods': {'get': False, 'post': True},
778
+ 'parameters': {
779
+ 'ResourceType': {'values': schema_values}
780
+ }
781
+ }
782
+
783
+ self.context.model['operations']['Transaction'] = {
784
+ 'methods': {'get': False, 'post': True},
785
+ 'parameters': {
786
+ 'TransactionSchemas': {'values': sorted(schema_values)}
787
+ }
788
+ }
789
+
790
+ self.csw_harvest_pagesize = int(self.config['manager'].get('csw_harvest_pagesize', 10))
791
+
792
+ def _test_manager(self):
793
+ """ Verify that transactions are allowed """
794
+
795
+ if not self.config['manager'].get('transactions', False):
796
+ raise RuntimeError('CSW-T interface is disabled')
797
+
798
+ # get the client first forwarded ip
799
+ if 'HTTP_X_FORWARDED_FOR' in self.environ:
800
+ ipaddress = self.environ['HTTP_X_FORWARDED_FOR'].split(',')[0].strip()
801
+ else:
802
+ ipaddress = self.environ['REMOTE_ADDR']
803
+
804
+ if 'allowed_ips' not in self.config['manager'] or not \
805
+ util.ipaddress_in_whitelist(ipaddress, self.config['manager'].get('allowed_ips', [])):
806
+ raise RuntimeError(
807
+ 'CSW-T operations not allowed for this IP address: %s' % ipaddress)
808
+
809
+ def _cql_update_queryables_mappings(self, cql, mappings):
810
+ """ Transform CQL query's properties to underlying DB columns """
811
+ LOGGER.debug('Raw CQL text = %s', cql)
812
+ LOGGER.debug(str(list(mappings.keys())))
813
+ if cql is not None:
814
+ for key in mappings.keys():
815
+ try:
816
+ cql = cql.replace(key, mappings[key]['dbcol'])
817
+ except KeyError:
818
+ LOGGER.debug('Setting without dbcol key')
819
+ cql = cql.replace(key, mappings[key])
820
+ LOGGER.debug('Interpolated CQL text = %s.', cql)
821
+ return cql
822
+
823
+ def _process_responsehandler(self, xml):
824
+ """ Process response handler """
825
+
826
+ if self.kvp['responsehandler'] is not None:
827
+ LOGGER.info('Processing responsehandler %s' %
828
+ self.kvp['responsehandler'])
829
+
830
+ uprh = urlparse(self.kvp['responsehandler'])
831
+
832
+ if uprh.scheme == 'mailto': # email
833
+ import smtplib
834
+
835
+ LOGGER.debug('Email detected')
836
+
837
+ smtp_host = 'localhost'
838
+ smtp_user = ''
839
+ smtp_pass = ''
840
+ smtp_ssl = False
841
+ if 'smtp_host' in self.config['server']:
842
+ smtp_host = self.config['server']['smtp_host']
843
+
844
+ if 'smtp_user' in self.config['server']:
845
+ smtp_user = self.config['server']['smtp_user']
846
+
847
+ if 'smtp_pass' in self.config['server']:
848
+ smtp_pass = self.config['server']['smtp_pass']
849
+
850
+ if 'smtp_ssl' in self.config['server']:
851
+ smtp_ssl = self.config['server'].get('smtp_ssl', False)
852
+
853
+ body = ('Subject: pycsw %s results\n\n%s' %
854
+ (self.kvp['request'], xml))
855
+
856
+ try:
857
+ LOGGER.info('Sending email')
858
+ if smtp_ssl:
859
+ msg = smtplib.SMTP_SSL(smtp_host, port=smtplib.SMTP_SSL_PORT)
860
+ msg.login(smtp_user, smtp_pass)
861
+ else:
862
+ msg = smtplib.SMTP(smtp_host)
863
+
864
+ msg.sendmail(
865
+ self.config['metadata']['contact']['email'],
866
+ uprh.path, body)
867
+ msg.quit()
868
+ LOGGER.debug('Email sent successfully.')
869
+ except Exception as err:
870
+ LOGGER.exception('Error processing email')
871
+
872
+ elif uprh.scheme in ['ftp', 'ftps']:
873
+ import ftplib
874
+
875
+ LOGGER.debug(f'{uprh.scheme} detected.')
876
+
877
+ try:
878
+ LOGGER.info(f'Sending to {uprh.scheme} server.')
879
+ if uprh.scheme == 'ftps':
880
+ ftp = ftplib.FTP_TLS(uprh.hostname)
881
+ else:
882
+ ftp = ftplib.FTP(uprh.hostname)
883
+ if uprh.username is not None:
884
+ ftp.login(uprh.username, uprh.password)
885
+ if uprh.scheme == 'ftps':
886
+ ftp.prot_p()
887
+ ftp.storbinary('STOR %s' % uprh.path[1:], StringIO(xml))
888
+ ftp.quit()
889
+ LOGGER.debug(f'{uprh.scheme} sent successfully.')
890
+ except Exception as err:
891
+ LOGGER.exception(f'Error processing {uprh.scheme}')
892
+
893
+ def _render_xslt(self, res):
894
+ ''' Validate and render XSLT '''
895
+
896
+ LOGGER.debug('Rendering XSLT')
897
+ try:
898
+ input_os = res.schema
899
+ output_os = self.kvp['outputschema']
900
+
901
+ xslt_id = 'xslt:%s,%s' % (input_os, output_os)
902
+ xslt_dict = next(d for i, d in enumerate(self.xslts) if xslt_id in d)
903
+
904
+ LOGGER.debug('XSLT ID: %s' % xslt_id)
905
+ LOGGER.debug('Found matching XSLT transformation')
906
+
907
+ xslt = xslt_dict[xslt_id]
908
+
909
+ transform = etree.XSLT(etree.parse(xslt))
910
+ doc = etree.fromstring(res.xml, self.context.parser)
911
+ result_tree = transform(doc).getroot()
912
+ return result_tree
913
+ except StopIteration:
914
+ LOGGER.debug('No matching XSLT found')
915
+ pass
916
+ except Exception as err:
917
+ LOGGER.warning('XSLT transformation failed: %s' % str(err))
918
+ raise RuntimeError()
919
+
920
+
921
+ @staticmethod
922
+ def normalize_kvp(kvp):
923
+ """Normalize Key Value Pairs.
924
+
925
+ This method will transform all keys to lowercase and leave values
926
+ unchanged, as specified in the CSW standard (see for example note
927
+ C on Table 62 - KVP Encoding for DescribeRecord operation request
928
+ of the CSW standard version 2.0.2)
929
+
930
+ :arg kvp: a mapping with Key Value Pairs
931
+ :type kvp: dict
932
+ :returns: A new dictionary with normalized parameters
933
+ """
934
+
935
+ result = dict()
936
+ for name, value in kvp.items():
937
+ result[name.lower()] = value
938
+ return result