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
@@ -0,0 +1,941 @@
1
+ # -*- coding: utf-8 -*-
2
+ # =================================================================
3
+ #
4
+ # Authors: Tom Kralidis <tomkralidis@gmail.com>
5
+ # Angelos Tzotsos <tzotsos@gmail.com>
6
+ # Ricardo Garcia Silva <ricardo.garcia.silva@gmail.com>
7
+ #
8
+ # Copyright (c) 2024 Tom Kralidis
9
+ # Copyright (c) 2015 Angelos Tzotsos
10
+ # Copyright (c) 2017 Ricardo Garcia 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 inspect
36
+ import logging
37
+ import os
38
+ from time import sleep
39
+
40
+ from shapely.wkt import loads
41
+ from shapely.errors import ShapelyError
42
+
43
+ from sqlalchemy import create_engine, func, __version__, select
44
+ from sqlalchemy.exc import OperationalError
45
+ from sqlalchemy.sql import text
46
+ from sqlalchemy.ext.declarative import declarative_base
47
+ from sqlalchemy.orm import create_session
48
+
49
+ from pycsw.core import util
50
+ from pycsw.core.etree import etree
51
+ from pycsw.core.etree import PARSER
52
+
53
+ LOGGER = logging.getLogger(__name__)
54
+
55
+
56
+ class Repository(object):
57
+ _engines = {}
58
+
59
+ @classmethod
60
+ def create_engine(clazz, url):
61
+ '''
62
+ SQL Alchemy engines are thread-safe and simple wrappers for connection pools
63
+
64
+ https://groups.google.com/forum/#!topic/sqlalchemy/t8i3RSKZGb0
65
+
66
+ To reduce startup time we can cache the engine as a class variable in the
67
+ repository object and do database initialization once
68
+
69
+ Engines are memoized by url
70
+ '''
71
+ if url not in clazz._engines:
72
+ LOGGER.info('creating new engine: %s', url)
73
+ engine = create_engine('%s' % url, echo=False, pool_pre_ping=True)
74
+
75
+ # load SQLite query bindings
76
+ # This can be directly bound via events
77
+ # for sqlite < 0.7, we need to to this on a per-connection basis
78
+ if engine.name in ['sqlite', 'sqlite3'] and __version__ >= '0.7':
79
+ from sqlalchemy import event
80
+
81
+ @event.listens_for(engine, "connect")
82
+ def connect(dbapi_connection, connection_rec):
83
+ create_custom_sql_functions(dbapi_connection)
84
+
85
+ clazz._engines[url] = engine
86
+
87
+ return clazz._engines[url]
88
+
89
+ ''' Class to interact with underlying repository '''
90
+ def __init__(self, database, context, app_root=None, table='records', repo_filter=None):
91
+ ''' Initialize repository '''
92
+
93
+ self.context = context
94
+ self.filter = repo_filter
95
+ self.fts = False
96
+ self.database = database
97
+ self.table = table
98
+
99
+ # Don't use relative paths, this is hack to get around
100
+ # most wsgi restriction...
101
+ if (app_root and self.database.startswith('sqlite:///') and
102
+ not self.database.startswith('sqlite:////')):
103
+ self.database = self.database.replace('sqlite:///', 'sqlite:///%s%s' % (app_root, os.sep))
104
+
105
+ self.engine = Repository.create_engine('%s' % self.database)
106
+
107
+ base = declarative_base(bind=self.engine)
108
+
109
+ LOGGER.info('binding ORM to existing database')
110
+
111
+ self.postgis_geometry_column = None
112
+
113
+ schema_name, table_name = table.rpartition(".")[::2]
114
+
115
+ default_table_args = {
116
+ "autoload": True,
117
+ "schema": schema_name or None
118
+ }
119
+ column_constraints = context.md_core_model.get("column_constraints")
120
+
121
+ # Note: according to the sqlalchemy docs available here:
122
+ #
123
+ # https://docs.sqlalchemy.org/en/14/orm/declarative_tables.html#declarative-table-configuration
124
+ #
125
+ # the __table_args__ attribute can either be a tuple or a dict
126
+ if column_constraints is not None:
127
+ table_args = tuple((*column_constraints, default_table_args))
128
+ else:
129
+ table_args = default_table_args
130
+
131
+ self.dataset = type(
132
+ 'dataset',
133
+ (base,),
134
+ {
135
+ "__tablename__": table_name,
136
+ "__table_args__": table_args
137
+ }
138
+ )
139
+
140
+ self.dbtype = self.engine.name
141
+
142
+ self.session = create_session(self.engine)
143
+ self.func = func
144
+
145
+ temp_dbtype = None
146
+
147
+ self.query_mappings = {
148
+ 'identifier': self.dataset.identifier,
149
+ 'type': self.dataset.type,
150
+ 'typename': self.dataset.typename,
151
+ 'parentidentifier': self.dataset.parentidentifier,
152
+ 'collections': self.dataset.parentidentifier,
153
+ 'updated': self.dataset.insert_date,
154
+ 'title': self.dataset.title,
155
+ 'description': self.dataset.abstract,
156
+ 'keywords': self.dataset.keywords,
157
+ 'edition': self.dataset.edition,
158
+ 'anytext': self.dataset.anytext,
159
+ 'bbox': self.dataset.wkt_geometry,
160
+ 'date': self.dataset.date,
161
+ 'time_begin': self.dataset.time_begin,
162
+ 'time_end': self.dataset.time_end,
163
+ 'platform': self.dataset.platform,
164
+ 'instrument': self.dataset.instrument,
165
+ 'sensortype': self.dataset.sensortype
166
+ }
167
+
168
+ if self.dbtype == 'postgresql':
169
+ # check if PostgreSQL is enabled with PostGIS 1.x
170
+ try:
171
+ self.session.execute(select([func.postgis_version()]))
172
+ temp_dbtype = 'postgresql+postgis+wkt'
173
+ LOGGER.debug('PostgreSQL+PostGIS1+WKT detected')
174
+ except Exception:
175
+ LOGGER.exception('PostgreSQL+PostGIS1+WKT detection failed')
176
+
177
+ # check if PostgreSQL is enabled with PostGIS 2.x
178
+ try:
179
+ self.session.execute('select(postgis_version())')
180
+ temp_dbtype = 'postgresql+postgis+wkt'
181
+ LOGGER.debug('PostgreSQL+PostGIS2+WKT detected')
182
+ except Exception:
183
+ LOGGER.exception('PostgreSQL+PostGIS2+WKT detection failed')
184
+
185
+ # check if a native PostGIS geometry column exists
186
+ try:
187
+ result = self.session.execute(
188
+ "select f_geometry_column "
189
+ "from geometry_columns "
190
+ "where f_table_name = '%s' "
191
+ "and f_geometry_column != 'wkt_geometry' "
192
+ "limit 1;" % table_name
193
+ )
194
+ row = result.fetchone()
195
+ self.postgis_geometry_column = str(row['f_geometry_column'])
196
+ temp_dbtype = 'postgresql+postgis+native'
197
+ LOGGER.debug('PostgreSQL+PostGIS+Native detected')
198
+ except Exception:
199
+ LOGGER.exception('PostgreSQL+PostGIS+Native not picked up: %s', table_name)
200
+
201
+ # check if a native PostgreSQL FTS GIN index exists
202
+ result = self.session.execute("select relname from pg_class where relname='fts_gin_idx'").scalar()
203
+ self.fts = bool(result)
204
+ LOGGER.debug('PostgreSQL FTS enabled: %r', self.fts)
205
+
206
+ if temp_dbtype is not None:
207
+ LOGGER.debug('%s support detected', temp_dbtype)
208
+ self.dbtype = temp_dbtype
209
+
210
+ if self.dbtype == 'postgresql+postgis+native':
211
+ LOGGER.debug('Adjusting to PostGIS geometry column (wkb_geometry)')
212
+ self.query_mappings['bbox'] = self.dataset.wkb_geometry
213
+
214
+ if self.dbtype in ['sqlite', 'sqlite3']: # load SQLite query bindings
215
+ # <= 0.6 behaviour
216
+ if not __version__ >= '0.7':
217
+ self.connection = self.engine.raw_connection()
218
+ create_custom_sql_functions(self.connection)
219
+
220
+ LOGGER.info('setting repository queryables')
221
+ # generate core queryables db and obj bindings
222
+ self.queryables = {}
223
+ for tname in self.context.model['typenames']:
224
+ for qname in self.context.model['typenames'][tname]['queryables']:
225
+ self.queryables[qname] = {}
226
+
227
+ for qkey, qvalue in \
228
+ self.context.model['typenames'][tname]['queryables'][qname].items():
229
+ self.queryables[qname][qkey] = qvalue
230
+
231
+ # flatten all queryables
232
+ # TODO smarter way of doing this
233
+ self.queryables['_all'] = {}
234
+ for qbl in self.queryables:
235
+ if qbl != '_all':
236
+ self.queryables['_all'].update(self.queryables[qbl])
237
+
238
+ self.queryables['_all'].update(self.context.md_core_model['mappings'])
239
+
240
+ def ping(self, max_tries=10, wait_seconds=10):
241
+ LOGGER.debug(f"Waiting for {self.database}...")
242
+
243
+ if self.database.startswith('sqlite'):
244
+ sql = 'SELECT sqlite_version();'
245
+ else:
246
+ sql = 'SELECT version();'
247
+
248
+ engine = create_engine(self.database)
249
+ current_try = 0
250
+ while current_try < max_tries:
251
+ try:
252
+ engine.execute(sql)
253
+ LOGGER.debug("Database is already up!")
254
+ break
255
+ except OperationalError as err:
256
+ LOGGER.debug(f"Database not responding yet {err}")
257
+ current_try += 1
258
+ sleep(wait_seconds)
259
+ else:
260
+ raise RuntimeError(
261
+ f"Database not responding at {self.database} after {max_tries} tries. ")
262
+
263
+ def rebuild_db_indexes(self):
264
+ """Rebuild database indexes"""
265
+
266
+ LOGGER.info('Rebuilding database %s, table %s', self.database, self.table)
267
+ connection = self.engine.connect()
268
+ connection.autocommit = True
269
+ connection.execute('REINDEX %s' % self.table)
270
+ connection.close()
271
+ LOGGER.info('Done')
272
+
273
+ def optimize_db(self):
274
+ """Optimize database"""
275
+ from sqlalchemy.exc import ArgumentError, OperationalError
276
+
277
+ LOGGER.info('Optimizing database %s', self.database)
278
+ connection = self.engine.connect()
279
+ try:
280
+ # PostgreSQL
281
+ connection.execution_options(isolation_level="AUTOCOMMIT")
282
+ connection.execute('VACUUM ANALYZE')
283
+ except (ArgumentError, OperationalError):
284
+ # SQLite
285
+ connection.autocommit = True
286
+ connection.execute('VACUUM')
287
+ connection.execute('ANALYZE')
288
+ finally:
289
+ connection.close()
290
+ LOGGER.info('Done')
291
+
292
+ def _create_values(self, values):
293
+ value_dict = {}
294
+ for num, value in enumerate(values):
295
+ value_dict['pvalue%d' % num] = value
296
+ return value_dict
297
+
298
+ def describe(self):
299
+ ''' Derive table columns and types '''
300
+
301
+ type_mappings = {
302
+ 'TEXT': 'string',
303
+ 'VARCHAR': 'string'
304
+ }
305
+
306
+ properties = {
307
+ 'geometry': {
308
+ '$ref': 'https://geojson.org/schema/Polygon.json',
309
+ 'x-ogc-role': 'primary-geometry'
310
+ }
311
+ }
312
+
313
+ for i in self.dataset.__table__.columns:
314
+ if i.name in ['anytext', 'metadata', 'metadata_type', 'xml']:
315
+ continue
316
+
317
+ properties[i.name] = {
318
+ 'title': i.name
319
+ }
320
+
321
+ if i.name == 'identifier':
322
+ properties[i.name]['x-ogc-role'] = 'id'
323
+
324
+ try:
325
+ properties[i.name]['type'] = type_mappings[str(i.type)]
326
+ except Exception as err:
327
+ LOGGER.debug(f'Cannot determine type: {err}')
328
+
329
+ return properties
330
+
331
+ def query_ids(self, ids):
332
+ ''' Query by list of identifiers '''
333
+
334
+ column = getattr(self.dataset,
335
+ self.context.md_core_model['mappings']['pycsw:Identifier'])
336
+
337
+ query = self.session.query(self.dataset).filter(column.in_(ids))
338
+ return self._get_repo_filter(query).all()
339
+
340
+ def query_collections(self, filters=None, limit=10):
341
+ ''' Query for parent collections '''
342
+
343
+ column = getattr(self.dataset,
344
+ self.context.md_core_model['mappings']['pycsw:ParentIdentifier'])
345
+
346
+ collections = self.session.query(column).distinct()
347
+
348
+ results = self._get_repo_filter(collections).all()
349
+
350
+ ids = [res[0] for res in results if res[0] is not None]
351
+
352
+ column = getattr(self.dataset,
353
+ self.context.md_core_model['mappings']['pycsw:Identifier'])
354
+
355
+ query = self.session.query(self.dataset).filter(column.in_(ids))
356
+
357
+ if filters is not None:
358
+ LOGGER.debug('Querying repository with additional filters')
359
+ return self._get_repo_filter(query).filter(filters).limit(limit).all()
360
+
361
+ return self._get_repo_filter(query).limit(limit).all()
362
+
363
+ def query_domain(self, domain, typenames, domainquerytype='list',
364
+ count=False):
365
+ ''' Query by property domain values '''
366
+
367
+ domain_value = getattr(self.dataset, domain)
368
+
369
+ if domainquerytype == 'range':
370
+ LOGGER.info('Generating property name range values')
371
+ query = self.session.query(func.min(domain_value),
372
+ func.max(domain_value))
373
+ else:
374
+ if count:
375
+ LOGGER.info('Generating property name frequency counts')
376
+ query = self.session.query(getattr(self.dataset, domain),
377
+ func.count(domain_value)).group_by(domain_value)
378
+ else:
379
+ query = self.session.query(domain_value).distinct()
380
+ return self._get_repo_filter(query).all()
381
+
382
+ def query_insert(self, direction='max'):
383
+ ''' Query to get latest (default) or earliest update to repository '''
384
+ column = getattr(self.dataset,
385
+ self.context.md_core_model['mappings']['pycsw:InsertDate'])
386
+
387
+ if direction == 'min':
388
+ return self._get_repo_filter(self.session.query(func.min(column))).first()[0]
389
+ # else default max
390
+ return self._get_repo_filter(self.session.query(func.max(column))).first()[0]
391
+
392
+ def query_source(self, source):
393
+ ''' Query by source '''
394
+ column = getattr(self.dataset,
395
+ self.context.md_core_model['mappings']['pycsw:Source'])
396
+
397
+ query = self.session.query(self.dataset).filter(column == source)
398
+ return self._get_repo_filter(query).all()
399
+
400
+ def query(self, constraint, sortby=None, typenames=None,
401
+ maxrecords=10, startposition=0):
402
+ ''' Query records from underlying repository '''
403
+
404
+ # run the raw query and get total
405
+ if 'where' in constraint: # GetRecords with constraint
406
+ LOGGER.debug('constraint detected')
407
+ query = self.session.query(self.dataset).filter(
408
+ text(constraint['where'])).params(self._create_values(constraint['values']))
409
+ else: # GetRecords sans constraint
410
+ LOGGER.debug('No constraint detected')
411
+ query = self.session.query(self.dataset)
412
+
413
+ total = self._get_repo_filter(query).count()
414
+
415
+ if util.ranking_pass: # apply spatial ranking
416
+ # TODO: Check here for dbtype so to extract wkt from postgis native to wkt
417
+ LOGGER.debug('spatial ranking detected')
418
+ LOGGER.debug('Target WKT: %s', getattr(self.dataset, self.context.md_core_model['mappings']['pycsw:BoundingBox']))
419
+ LOGGER.debug('Query WKT: %s', util.ranking_query_geometry)
420
+ query = query.order_by(func.get_spatial_overlay_rank(getattr(self.dataset, self.context.md_core_model['mappings']['pycsw:BoundingBox']), util.ranking_query_geometry).desc())
421
+ # trying to make this wsgi safe
422
+ util.ranking_pass = False
423
+ util.ranking_query_geometry = ''
424
+
425
+ if sortby is not None: # apply sorting
426
+ LOGGER.debug('sorting detected')
427
+ # TODO: Check here for dbtype so to extract wkt from postgis native to wkt
428
+ sortby_column = getattr(self.dataset, sortby['propertyname'])
429
+
430
+ if sortby['order'] == 'DESC': # descending sort
431
+ if 'spatial' in sortby and sortby['spatial']: # spatial sort
432
+ query = query.order_by(func.get_geometry_area(sortby_column).desc())
433
+ else: # aspatial sort
434
+ query = query.order_by(sortby_column.desc())
435
+ else: # ascending sort
436
+ if 'spatial' in sortby and sortby['spatial']: # spatial sort
437
+ query = query.order_by(func.get_geometry_area(sortby_column))
438
+ else: # aspatial sort
439
+ query = query.order_by(sortby_column)
440
+
441
+ # always apply limit and offset
442
+ return [str(total), self._get_repo_filter(query).limit(
443
+ maxrecords).offset(startposition).all()]
444
+
445
+ def insert(self, record, source, insert_date):
446
+ ''' Insert a record into the repository '''
447
+
448
+ if isinstance(record.xml, bytes):
449
+ LOGGER.debug('Decoding bytes to unicode')
450
+ record.xml = record.xml.decode()
451
+
452
+ try:
453
+ self.session.begin()
454
+ self.session.add(record)
455
+ self.session.commit()
456
+ except Exception as err:
457
+ LOGGER.exception(err)
458
+ self.session.rollback()
459
+ raise
460
+
461
+ def update(self, record=None, recprops=None, constraint=None):
462
+ ''' Update a record in the repository based on identifier '''
463
+
464
+ if record is not None:
465
+ identifier = getattr(record,
466
+ self.context.md_core_model['mappings']['pycsw:Identifier'])
467
+
468
+ if isinstance(record.xml, bytes):
469
+ LOGGER.debug('Decoding bytes to unicode')
470
+ record.xml = record.xml.decode()
471
+
472
+ if recprops is None and constraint is None: # full update
473
+ LOGGER.debug('full update')
474
+ update_dict = dict([(getattr(self.dataset, key),
475
+ getattr(record, key))
476
+ for key in record.__dict__.keys() if key != '_sa_instance_state'])
477
+
478
+ try:
479
+ self.session.begin()
480
+ self._get_repo_filter(self.session.query(self.dataset)).filter_by(
481
+ identifier=identifier).update(update_dict, synchronize_session='fetch')
482
+ self.session.commit()
483
+ except Exception as err:
484
+ self.session.rollback()
485
+ msg = 'Cannot commit to repository'
486
+ LOGGER.exception(msg)
487
+ raise RuntimeError(msg) from err
488
+ else: # update based on record properties
489
+ LOGGER.debug('property based update')
490
+ try:
491
+ rows = rows2 = 0
492
+ self.session.begin()
493
+ for rpu in recprops:
494
+ # update queryable column and XML document via XPath
495
+ if 'xpath' not in rpu['rp']:
496
+ self.session.rollback()
497
+ raise RuntimeError('XPath not found for property %s' % rpu['rp']['name'])
498
+ if 'dbcol' not in rpu['rp']:
499
+ self.session.rollback()
500
+ raise RuntimeError('property not found for XPath %s' % rpu['rp']['name'])
501
+ rows += self._get_repo_filter(self.session.query(self.dataset)).filter(
502
+ text(constraint['where'])).params(self._create_values(constraint['values'])).update({
503
+ getattr(self.dataset, rpu['rp']['dbcol']): rpu['value'],
504
+ 'xml': func.update_xpath(str(self.context.namespaces),
505
+ getattr(self.dataset, self.context.md_core_model['mappings']['pycsw:XML']), str(rpu)),
506
+ }, synchronize_session='fetch')
507
+ # then update anytext tokens
508
+ rows2 += self._get_repo_filter(self.session.query(self.dataset)).filter(
509
+ text(constraint['where'])).params(self._create_values(constraint['values'])).update({
510
+ 'anytext': func.get_anytext(getattr(
511
+ self.dataset, self.context.md_core_model['mappings']['pycsw:XML']))
512
+ }, synchronize_session='fetch')
513
+ self.session.commit()
514
+ LOGGER.debug('Updated %d records', rows)
515
+ return rows
516
+ except Exception as err:
517
+ self.session.rollback()
518
+ msg = 'Cannot commit to repository'
519
+ LOGGER.exception(msg)
520
+ raise RuntimeError(msg) from err
521
+
522
+ def delete(self, constraint):
523
+ ''' Delete a record from the repository '''
524
+
525
+ LOGGER.debug('Deleting record with constraint: %s', constraint)
526
+ try:
527
+ self.session.begin()
528
+ rows = self._get_repo_filter(self.session.query(self.dataset)).filter(
529
+ text(constraint['where'])).params(self._create_values(constraint['values']))
530
+
531
+ parentids = []
532
+ for row in rows: # get ids
533
+ parentids.append(getattr(row,
534
+ self.context.md_core_model['mappings']['pycsw:Identifier']))
535
+
536
+ rows = rows.delete(synchronize_session='fetch')
537
+
538
+ if rows > 0:
539
+ LOGGER.debug('Deleting all child records')
540
+ # delete any child records which had this record as a parent
541
+ rows += self._get_repo_filter(self.session.query(self.dataset)).filter(
542
+ getattr(self.dataset,
543
+ self.context.md_core_model['mappings']['pycsw:ParentIdentifier']).in_(parentids)).delete(
544
+ synchronize_session='fetch')
545
+
546
+ self.session.commit()
547
+ LOGGER.debug('Deleted %d records', rows)
548
+ except Exception as err:
549
+ self.session.rollback()
550
+ msg = 'Cannot commit to repository'
551
+ LOGGER.exception(msg)
552
+ raise RuntimeError(msg) from err
553
+
554
+ return rows
555
+
556
+ def exists(self):
557
+ if self.database.startswith('sqlite:'):
558
+ db_path = self.database.rpartition(":///")[-1]
559
+ if not os.path.isfile(db_path):
560
+ try:
561
+ os.makedirs(os.path.dirname(db_path))
562
+ except OSError as exc:
563
+ if exc.args[0] == 17: # directory already exists
564
+ pass
565
+
566
+ def _get_repo_filter(self, query):
567
+ ''' Apply repository wide side filter / mask query '''
568
+ if self.filter is not None:
569
+ return query.filter(text(self.filter))
570
+ return query
571
+
572
+
573
+ def create_custom_sql_functions(connection):
574
+ """Register custom functions on the database connection."""
575
+
576
+ inspect_function = inspect.getfullargspec
577
+
578
+ for function_object in [
579
+ query_spatial,
580
+ update_xpath,
581
+ util.get_anytext,
582
+ get_geometry_area,
583
+ get_spatial_overlay_rank
584
+ ]:
585
+ argspec = inspect_function(function_object)
586
+ connection.create_function(
587
+ function_object.__name__,
588
+ len(argspec.args),
589
+ function_object
590
+ )
591
+
592
+
593
+ def query_spatial(bbox_data_wkt, bbox_input_wkt, predicate, distance):
594
+ """Perform spatial query
595
+
596
+ Parameters
597
+ ----------
598
+ bbox_data_wkt: str
599
+ Well-Known Text representation of the data being queried
600
+ bbox_input_wkt: str
601
+ Well-Known Text representation of the input being queried
602
+ predicate: str
603
+ Spatial predicate to use in query
604
+ distance: int or float or str
605
+ Distance parameter for when using either of ``beyond`` or ``dwithin``
606
+ predicates.
607
+
608
+ Returns
609
+ -------
610
+ str
611
+ Either ``true`` or ``false`` depending on the result of the spatial
612
+ query
613
+
614
+ Raises
615
+ ------
616
+ RuntimeError
617
+ If an invalid predicate is used
618
+
619
+ """
620
+
621
+ try:
622
+ bbox1 = loads(bbox_data_wkt.split(';')[-1])
623
+ bbox2 = loads(bbox_input_wkt)
624
+ if predicate == 'bbox':
625
+ result = bbox1.intersects(bbox2)
626
+ elif predicate == 'beyond':
627
+ result = bbox1.distance(bbox2) > float(distance)
628
+ elif predicate == 'contains':
629
+ result = bbox1.contains(bbox2)
630
+ elif predicate == 'crosses':
631
+ result = bbox1.crosses(bbox2)
632
+ elif predicate == 'disjoint':
633
+ result = bbox1.disjoint(bbox2)
634
+ elif predicate == 'dwithin':
635
+ result = bbox1.distance(bbox2) <= float(distance)
636
+ elif predicate == 'equals':
637
+ result = bbox1.equals(bbox2)
638
+ elif predicate == 'intersects':
639
+ result = bbox1.intersects(bbox2)
640
+ elif predicate == 'overlaps':
641
+ result = bbox1.intersects(bbox2) and not bbox1.touches(bbox2)
642
+ elif predicate == 'touches':
643
+ result = bbox1.touches(bbox2)
644
+ elif predicate == 'within':
645
+ result = bbox1.within(bbox2)
646
+ else:
647
+ raise RuntimeError(
648
+ 'Invalid spatial query predicate: %s' % predicate)
649
+ except (AttributeError, ValueError, ShapelyError, TypeError):
650
+ result = False
651
+ return "true" if result else "false"
652
+
653
+
654
+ def update_xpath(nsmap, xml, recprop):
655
+ """Update XML document XPath values"""
656
+
657
+ if isinstance(xml, bytes) or isinstance(xml, str):
658
+ # serialize to lxml
659
+ xml = etree.fromstring(xml, PARSER)
660
+
661
+ recprop = eval(recprop)
662
+ nsmap = eval(nsmap)
663
+ try:
664
+ nodes = xml.xpath(recprop['rp']['xpath'], namespaces=nsmap)
665
+ if len(nodes) > 0: # matches
666
+ for node1 in nodes:
667
+ if node1.text != recprop['value']: # values differ, update
668
+ node1.text = recprop['value']
669
+ except Exception as err:
670
+ LOGGER.warning('update_xpath error', exc_info=True)
671
+ raise RuntimeError('ERROR: %s' % str(err)) from err
672
+
673
+ return etree.tostring(xml)
674
+
675
+
676
+ def get_geometry_area(geometry):
677
+ """Derive area of a given geometry"""
678
+ try:
679
+ if geometry is not None:
680
+ return str(loads(geometry).area)
681
+ return '0'
682
+ except Exception:
683
+ return '0'
684
+
685
+
686
+ def get_spatial_overlay_rank(target_geometry, query_geometry):
687
+ """Derive spatial overlay rank for geospatial search as per Lanfear (2006)
688
+ http://pubs.usgs.gov/of/2006/1279/2006-1279.pdf"""
689
+
690
+ # TODO: Add those parameters to config file
691
+ kt = 1.0
692
+ kq = 1.0
693
+ if target_geometry is not None and query_geometry is not None:
694
+ try:
695
+ q_geom = loads(query_geometry)
696
+ t_geom = loads(target_geometry)
697
+ Q = q_geom.area
698
+ T = t_geom.area
699
+ if any(item == 0.0 for item in [Q, T]):
700
+ LOGGER.warning('Geometry has no area')
701
+ return '0'
702
+ X = t_geom.intersection(q_geom).area
703
+ if kt == 1.0 and kq == 1.0:
704
+ LOGGER.debug('Spatial Rank: %s', str((X/Q)*(X/T)))
705
+ return str((X/Q)*(X/T))
706
+ else:
707
+ LOGGER.debug('Spatial Rank: %s', str(((X/Q)**kq)*((X/T)**kt)))
708
+ return str(((X/Q)**kq)*((X/T)**kt))
709
+ except Exception:
710
+ LOGGER.warning('Cannot derive spatial overlay ranking', exc_info=True)
711
+ return '0'
712
+ return '0'
713
+
714
+
715
+ def setup(database, table, create_sfsql_tables=True, postgis_geometry_column='wkb_geometry',
716
+ extra_columns=[], language='english'):
717
+ """Setup database tables and indexes"""
718
+ from sqlalchemy import Column, create_engine, Integer, MetaData, \
719
+ Table, Text, Unicode
720
+ from sqlalchemy.types import Float
721
+ from sqlalchemy.orm import create_session
722
+
723
+ LOGGER.info('Creating database %s', database)
724
+ if database.startswith('sqlite:///'):
725
+ _, filepath = database.split('sqlite:///')
726
+ dirname = os.path.dirname(filepath)
727
+ if not os.path.exists(dirname):
728
+ LOGGER.debug('SQLite directory %s does not exist' % dirname)
729
+ try:
730
+ db_path = database.rpartition(":///")[-1]
731
+ os.makedirs(os.path.dirname(db_path))
732
+ except OSError as exc:
733
+ if exc.args[0] == 17: # directory already exists
734
+ LOGGER.debug('Directory already exists')
735
+
736
+ dbase = create_engine(database)
737
+ schema_name, table_name = table.rpartition(".")[::2]
738
+
739
+ mdata = MetaData(dbase, schema=schema_name or None)
740
+ create_postgis_geometry = False
741
+
742
+ # If PostGIS 2.x detected, do not create sfsql tables.
743
+ if dbase.name == 'postgresql':
744
+ try:
745
+ dbsession = create_session(dbase)
746
+ for row in dbsession.execute('select(postgis_lib_version())'):
747
+ postgis_lib_version = row[0]
748
+ create_sfsql_tables = False
749
+ create_postgis_geometry = True
750
+ LOGGER.info('PostGIS %s detected: Skipping SFSQL tables creation', postgis_lib_version)
751
+ except Exception:
752
+ pass
753
+
754
+ if create_sfsql_tables:
755
+ LOGGER.info('Creating table spatial_ref_sys')
756
+ srs = Table(
757
+ 'spatial_ref_sys', mdata,
758
+ Column('srid', Integer, nullable=False, primary_key=True),
759
+ Column('auth_name', Text),
760
+ Column('auth_srid', Integer),
761
+ Column('srtext', Text)
762
+ )
763
+ srs.create()
764
+
765
+ i = srs.insert()
766
+ i.execute(srid=4326, auth_name='EPSG', auth_srid=4326, srtext='GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]')
767
+
768
+ LOGGER.info('Creating table geometry_columns')
769
+ geom = Table(
770
+ 'geometry_columns', mdata,
771
+ Column('f_table_catalog', Text, nullable=False),
772
+ Column('f_table_schema', Text, nullable=False),
773
+ Column('f_table_name', Text, nullable=False),
774
+ Column('f_geometry_column', Text, nullable=False),
775
+ Column('geometry_type', Integer),
776
+ Column('coord_dimension', Integer),
777
+ Column('srid', Integer, nullable=False),
778
+ Column('geometry_format', Text, nullable=False),
779
+ )
780
+ geom.create()
781
+
782
+ i = geom.insert()
783
+ i.execute(f_table_catalog='public', f_table_schema='public',
784
+ f_table_name=table_name, f_geometry_column='wkt_geometry',
785
+ geometry_type=3, coord_dimension=2,
786
+ srid=4326, geometry_format='WKT')
787
+
788
+ # abstract metadata information model
789
+
790
+ LOGGER.info('Creating table %s', table_name)
791
+ records = Table(
792
+ table_name, mdata,
793
+ # core; nothing happens without these
794
+ Column('identifier', Text, primary_key=True),
795
+ Column('typename', Text,
796
+ default='csw:Record', nullable=False, index=True),
797
+ Column('schema', Text,
798
+ default='http://www.opengis.net/cat/csw/2.0.2', nullable=False,
799
+ index=True),
800
+ Column('mdsource', Text, default='local', nullable=False,
801
+ index=True),
802
+ Column('insert_date', Text, nullable=False, index=True),
803
+ Column('xml', Unicode, nullable=False),
804
+ Column('anytext', Text, nullable=False),
805
+ Column('metadata', Unicode),
806
+ Column('metadata_type', Text, default='application/xml', nullable=False),
807
+ Column('language', Text, index=True),
808
+
809
+ # identification
810
+ Column('type', Text, index=True),
811
+ Column('title', Text, index=True),
812
+ Column('title_alternate', Text, index=True),
813
+ Column('abstract', Text, index=True),
814
+ Column('edition', Text, index=True),
815
+ Column('keywords', Text, index=True),
816
+ Column('keywordstype', Text, index=True),
817
+ Column('themes', Text, index=True),
818
+ Column('parentidentifier', Text, index=True),
819
+ Column('relation', Text, index=True),
820
+ Column('time_begin', Text, index=True),
821
+ Column('time_end', Text, index=True),
822
+ Column('topicategory', Text, index=True),
823
+ Column('resourcelanguage', Text, index=True),
824
+
825
+ # attribution
826
+ Column('creator', Text, index=True),
827
+ Column('publisher', Text, index=True),
828
+ Column('contributor', Text, index=True),
829
+ Column('organization', Text, index=True),
830
+
831
+ # security
832
+ Column('securityconstraints', Text, index=True),
833
+ Column('accessconstraints', Text, index=True),
834
+ Column('otherconstraints', Text, index=True),
835
+
836
+ # date
837
+ Column('date', Text, index=True),
838
+ Column('date_revision', Text, index=True),
839
+ Column('date_creation', Text, index=True),
840
+ Column('date_publication', Text, index=True),
841
+ Column('date_modified', Text, index=True),
842
+
843
+ Column('format', Text, index=True),
844
+ Column('source', Text, index=True),
845
+
846
+ # geospatial
847
+ Column('crs', Text, index=True),
848
+ Column('geodescode', Text, index=True),
849
+ Column('denominator', Text, index=True),
850
+ Column('distancevalue', Text, index=True),
851
+ Column('distanceuom', Text, index=True),
852
+ Column('wkt_geometry', Text),
853
+ Column('vert_extent_min', Float, index=True),
854
+ Column('vert_extent_max', Float, index=True),
855
+
856
+ # service
857
+ Column('servicetype', Text, index=True),
858
+ Column('servicetypeversion', Text, index=True),
859
+ Column('operation', Text, index=True),
860
+ Column('couplingtype', Text, index=True),
861
+ Column('operateson', Text, index=True),
862
+ Column('operatesonidentifier', Text, index=True),
863
+ Column('operatesoname', Text, index=True),
864
+
865
+ # inspire
866
+ Column('degree', Text, index=True),
867
+ Column('classification', Text, index=True),
868
+ Column('conditionapplyingtoaccessanduse', Text, index=True),
869
+ Column('lineage', Text, index=True),
870
+ Column('responsiblepartyrole', Text, index=True),
871
+ Column('specificationtitle', Text, index=True),
872
+ Column('specificationdate', Text, index=True),
873
+ Column('specificationdatetype', Text, index=True),
874
+
875
+ # eo
876
+ Column('platform', Text, index=True),
877
+ Column('instrument', Text, index=True),
878
+ Column('sensortype', Text, index=True),
879
+ Column('cloudcover', Text, index=True),
880
+ # bands: JSON list of dicts with properties: name, units, min, max
881
+ Column('bands', Text, index=True),
882
+
883
+ # distribution
884
+ # links: JSON list of dicts with properties: name, description, protocol, url
885
+ Column('links', Text, index=True),
886
+ # contacts: JSON list of dicts with owslib contact properties, name, organization, email, role, etc.
887
+ Column('contacts', Text, index=True),
888
+ )
889
+
890
+ # add extra columns that may have been passed via extra_columns
891
+ # extra_columns is a list of sqlalchemy.Column objects
892
+ if extra_columns:
893
+ LOGGER.info('Extra column definitions detected')
894
+ for extra_column in extra_columns:
895
+ LOGGER.info('Adding extra column: %s', extra_column)
896
+ records.append_column(extra_column)
897
+
898
+ records.create()
899
+
900
+ conn = dbase.connect()
901
+
902
+ if dbase.name == 'postgresql':
903
+ LOGGER.info('Creating PostgreSQL Free Text Search (FTS) GIN index')
904
+ tsvector_fts = "alter table %s add column anytext_tsvector tsvector" % table_name
905
+ conn.execute(tsvector_fts)
906
+ index_fts = "create index fts_gin_idx on %s using gin(anytext_tsvector)" % table_name
907
+ conn.execute(index_fts)
908
+ # This needs to run if records exist "UPDATE records SET anytext_tsvector = to_tsvector('english', anytext)"
909
+ trigger_fts = "create trigger ftsupdate before insert or update on %s for each row execute procedure tsvector_update_trigger('anytext_tsvector', 'pg_catalog.%s', 'anytext')" % (table_name, language)
910
+ conn.execute(trigger_fts)
911
+
912
+ if dbase.name == 'postgresql' and create_postgis_geometry:
913
+ # create native geometry column within db
914
+ LOGGER.info('Creating native PostGIS geometry column')
915
+ if postgis_lib_version < '2':
916
+ create_column_sql = "SELECT AddGeometryColumn('%s', '%s', 4326, 'POLYGON', 2)" % (table_name, postgis_geometry_column)
917
+ else:
918
+ create_column_sql = "ALTER TABLE %s ADD COLUMN %s geometry(Geometry,4326);" % (table_name, postgis_geometry_column)
919
+ create_insert_update_trigger_sql = '''
920
+ DROP TRIGGER IF EXISTS %(table)s_update_geometry ON %(table)s;
921
+ DROP FUNCTION IF EXISTS %(table)s_update_geometry();
922
+ CREATE FUNCTION %(table)s_update_geometry() RETURNS trigger AS $%(table)s_update_geometry$
923
+ BEGIN
924
+ IF NEW.wkt_geometry IS NULL THEN
925
+ RETURN NEW;
926
+ END IF;
927
+ NEW.%(geometry)s := ST_GeomFromText(NEW.wkt_geometry,4326);
928
+ RETURN NEW;
929
+ END;
930
+ $%(table)s_update_geometry$ LANGUAGE plpgsql;
931
+
932
+ CREATE TRIGGER %(table)s_update_geometry BEFORE INSERT OR UPDATE ON %(table)s
933
+ FOR EACH ROW EXECUTE PROCEDURE %(table)s_update_geometry();
934
+ ''' % {'table': table_name, 'geometry': postgis_geometry_column}
935
+
936
+ create_spatial_index_sql = 'CREATE INDEX %(geometry)s_idx ON %(table)s USING GIST (%(geometry)s);' \
937
+ % {'table': table_name, 'geometry': postgis_geometry_column}
938
+
939
+ conn.execute(create_column_sql)
940
+ conn.execute(create_insert_update_trigger_sql)
941
+ conn.execute(create_spatial_index_sql)