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.
- geonode_pycsw-3.0.0b2.dist-info/METADATA +73 -0
- geonode_pycsw-3.0.0b2.dist-info/RECORD +382 -0
- geonode_pycsw-3.0.0b2.dist-info/WHEEL +5 -0
- geonode_pycsw-3.0.0b2.dist-info/entry_points.txt +2 -0
- geonode_pycsw-3.0.0b2.dist-info/licenses/LICENSE.txt +26 -0
- geonode_pycsw-3.0.0b2.dist-info/top_level.txt +1 -0
- pycsw/__init__.py +33 -0
- pycsw/core/__init__.py +29 -0
- pycsw/core/admin.py +703 -0
- pycsw/core/config.py +621 -0
- pycsw/core/etree.py +43 -0
- pycsw/core/formats/__init__.py +29 -0
- pycsw/core/formats/fmt_json.py +69 -0
- pycsw/core/log.py +71 -0
- pycsw/core/metadata.py +1996 -0
- pycsw/core/pygeofilter_evaluate.py +83 -0
- pycsw/core/repository.py +941 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/_wrapper.xsd +14 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswAll.xsd +33 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswCommon.xsd +71 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetCapabilities.xsd +80 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetDomain.xsd +146 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecordById.xsd +58 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecords.xsd +391 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswHarvest.xsd +95 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswTransaction.xsd +187 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswUnHarvest.xsd +77 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcmes.xsd +245 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcterms.xsd +101 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/record.xsd +170 -0
- pycsw/core/schemas/ogc/csw/2.0.2/CSW-discovery.xsd +494 -0
- pycsw/core/schemas/ogc/csw/2.0.2/CSW-publication.xsd +242 -0
- pycsw/core/schemas/ogc/csw/2.0.2/rec-dcmes.xsd +199 -0
- pycsw/core/schemas/ogc/csw/2.0.2/rec-dcterms.xsd +94 -0
- pycsw/core/schemas/ogc/csw/2.0.2/record.xsd +138 -0
- pycsw/core/schemas/ogc/filter/1.1.0/expr.xsd +67 -0
- pycsw/core/schemas/ogc/filter/1.1.0/filter.xsd +265 -0
- pycsw/core/schemas/ogc/filter/1.1.0/filterCapabilities.xsd +171 -0
- pycsw/core/schemas/ogc/filter/1.1.0/sort.xsd +46 -0
- pycsw/core/schemas/ogc/filter/2.0/_wrapper.xsd +5 -0
- pycsw/core/schemas/ogc/filter/2.0/expr.xsd +44 -0
- pycsw/core/schemas/ogc/filter/2.0/filter.xsd +396 -0
- pycsw/core/schemas/ogc/filter/2.0/filterAll.xsd +23 -0
- pycsw/core/schemas/ogc/filter/2.0/filterCapabilities.xsd +286 -0
- pycsw/core/schemas/ogc/filter/2.0/query.xsd +70 -0
- pycsw/core/schemas/ogc/filter/2.0/sort.xsd +49 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/basicTypes.xsd +278 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateOperations.xsd +789 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateReferenceSystems.xsd +429 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateSystems.xsd +408 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coverage.xsd +451 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dataQuality.xsd +129 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/datums.xsd +484 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/defaultStyle.xsd +454 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dictionary.xsd +137 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/direction.xsd +72 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dynamicFeature.xsd +115 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/feature.xsd +199 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryAggregates.xsd +430 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic0d1d.xsd +602 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic2d.xsd +213 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryComplexes.xsd +141 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryPrimitives.xsd +1609 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/gml.xsd +22 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/gmlBase.xsd +294 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/grids.xsd +76 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/measures.xsd +200 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/observation.xsd +96 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/referenceSystems.xsd +211 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporal.xsd +332 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporalReferenceSystems.xsd +251 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporalTopology.xsd +186 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/topology.xsd +459 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/units.xsd +170 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/valueObjects.xsd +361 -0
- pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20-language.xsd +117 -0
- pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20.xsd +234 -0
- pycsw/core/schemas/ogc/gml/3.2.1/basicTypes.xsd +268 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateOperations.xsd +525 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateReferenceSystems.xsd +373 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateSystems.xsd +297 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coverage.xsd +292 -0
- pycsw/core/schemas/ogc/gml/3.2.1/datums.xsd +287 -0
- pycsw/core/schemas/ogc/gml/3.2.1/defaultStyle.xsd +453 -0
- pycsw/core/schemas/ogc/gml/3.2.1/deprecatedTypes.xsd +1133 -0
- pycsw/core/schemas/ogc/gml/3.2.1/dictionary.xsd +90 -0
- pycsw/core/schemas/ogc/gml/3.2.1/direction.xsd +84 -0
- pycsw/core/schemas/ogc/gml/3.2.1/dynamicFeature.xsd +109 -0
- pycsw/core/schemas/ogc/gml/3.2.1/feature.xsd +94 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryAggregates.xsd +197 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic0d1d.xsd +277 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic2d.xsd +124 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryComplexes.xsd +95 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryPrimitives.xsd +846 -0
- pycsw/core/schemas/ogc/gml/3.2.1/gml.xsd +20 -0
- pycsw/core/schemas/ogc/gml/3.2.1/gmlBase.xsd +185 -0
- pycsw/core/schemas/ogc/gml/3.2.1/grids.xsd +64 -0
- pycsw/core/schemas/ogc/gml/3.2.1/measures.xsd +68 -0
- pycsw/core/schemas/ogc/gml/3.2.1/observation.xsd +95 -0
- pycsw/core/schemas/ogc/gml/3.2.1/referenceSystems.xsd +70 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporal.xsd +269 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporalReferenceSystems.xsd +189 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporalTopology.xsd +119 -0
- pycsw/core/schemas/ogc/gml/3.2.1/topology.xsd +386 -0
- pycsw/core/schemas/ogc/gml/3.2.1/units.xsd +162 -0
- pycsw/core/schemas/ogc/gml/3.2.1/valueObjects.xsd +205 -0
- pycsw/core/schemas/ogc/ogcapi/records/part1/1.0/ogcapi-records-1.yaml +932 -0
- pycsw/core/schemas/ogc/ows/1.0.0/ows19115subset.xsd +222 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsAll.xsd +20 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsCommon.xsd +155 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsDataIdentification.xsd +112 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsExceptionReport.xsd +67 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsGetCapabilities.xsd +108 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsOperationsMetadata.xsd +161 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsServiceIdentification.xsd +55 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsServiceProvider.xsd +46 -0
- pycsw/core/schemas/ogc/ows/1.1.0/ows19115subset.xsd +236 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsAll.xsd +23 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsCommon.xsd +158 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsContents.xsd +87 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsDataIdentification.xsd +128 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsDomainType.xsd +280 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsExceptionReport.xsd +77 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsGetCapabilities.xsd +113 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsGetResourceByID.xsd +52 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsInputOutputData.xsd +60 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsManifest.xsd +125 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsOperationsMetadata.xsd +141 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsServiceIdentification.xsd +61 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsServiceProvider.xsd +48 -0
- pycsw/core/schemas/ogc/ows/2.0/ows19115subset.xsd +364 -0
- pycsw/core/schemas/ogc/ows/2.0/owsAdditionalParameters.xsd +114 -0
- pycsw/core/schemas/ogc/ows/2.0/owsAll.xsd +29 -0
- pycsw/core/schemas/ogc/ows/2.0/owsCommon.xsd +275 -0
- pycsw/core/schemas/ogc/ows/2.0/owsContents.xsd +163 -0
- pycsw/core/schemas/ogc/ows/2.0/owsDataIdentification.xsd +202 -0
- pycsw/core/schemas/ogc/ows/2.0/owsDomainType.xsd +388 -0
- pycsw/core/schemas/ogc/ows/2.0/owsExceptionReport.xsd +126 -0
- pycsw/core/schemas/ogc/ows/2.0/owsGetCapabilities.xsd +220 -0
- pycsw/core/schemas/ogc/ows/2.0/owsGetResourceByID.xsd +83 -0
- pycsw/core/schemas/ogc/ows/2.0/owsInputOutputData.xsd +98 -0
- pycsw/core/schemas/ogc/ows/2.0/owsManifest.xsd +181 -0
- pycsw/core/schemas/ogc/ows/2.0/owsOperationsMetadata.xsd +234 -0
- pycsw/core/schemas/ogc/ows/2.0/owsServiceIdentification.xsd +98 -0
- pycsw/core/schemas/ogc/ows/2.0/owsServiceProvider.xsd +64 -0
- pycsw/core/schemas/w3c/1999/xlink.xsd +271 -0
- pycsw/core/schemas/w3c/2001/xml.xsd +287 -0
- pycsw/core/util.py +552 -0
- pycsw/oaipmh.py +311 -0
- pycsw/ogc/__init__.py +29 -0
- pycsw/ogc/api/__init__.py +28 -0
- pycsw/ogc/api/oapi.py +558 -0
- pycsw/ogc/api/records.py +1414 -0
- pycsw/ogc/api/templates/_base.html +76 -0
- pycsw/ogc/api/templates/collection.html +44 -0
- pycsw/ogc/api/templates/collections.html +45 -0
- pycsw/ogc/api/templates/conformance.html +21 -0
- pycsw/ogc/api/templates/exception.html +8 -0
- pycsw/ogc/api/templates/item.html +228 -0
- pycsw/ogc/api/templates/items.html +337 -0
- pycsw/ogc/api/templates/landing_page.html +27 -0
- pycsw/ogc/api/templates/openapi.html +58 -0
- pycsw/ogc/api/templates/queryables.html +50 -0
- pycsw/ogc/api/templates/stac_items.html +173 -0
- pycsw/ogc/api/templates/static/favicon.ico +0 -0
- pycsw/ogc/api/templates/static/logo-horizontal.png +0 -0
- pycsw/ogc/api/templates/static/logo-vertical-darkbg.png +0 -0
- pycsw/ogc/api/util.py +252 -0
- pycsw/ogc/csw/__init__.py +29 -0
- pycsw/ogc/csw/cql.py +133 -0
- pycsw/ogc/csw/csw2.py +2042 -0
- pycsw/ogc/csw/csw3.py +2193 -0
- pycsw/ogc/fes/__init__.py +29 -0
- pycsw/ogc/fes/fes1.py +433 -0
- pycsw/ogc/fes/fes2.py +451 -0
- pycsw/ogc/gml/__init__.py +29 -0
- pycsw/ogc/gml/gml3.py +240 -0
- pycsw/ogc/gml/gml32.py +243 -0
- pycsw/opensearch.py +857 -0
- pycsw/plugins/__init__.py +29 -0
- pycsw/plugins/outputschemas/__init__.py +31 -0
- pycsw/plugins/outputschemas/atom.py +143 -0
- pycsw/plugins/outputschemas/datacite.py +375 -0
- pycsw/plugins/outputschemas/dif.py +213 -0
- pycsw/plugins/outputschemas/fgdc.py +180 -0
- pycsw/plugins/outputschemas/gm03.py +240 -0
- pycsw/plugins/profiles/__init__.py +29 -0
- pycsw/plugins/profiles/apiso/__init__.py +29 -0
- pycsw/plugins/profiles/apiso/apiso.py +757 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/csw/2.0.2/profiles/apiso/1.0.0/apiso.xsd +13 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/basicTypes.xsd +429 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gco.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gcoBase.xsd +61 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/applicationSchema.xsd +42 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/citation.xsd +275 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/constraints.xsd +106 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/content.xsd +188 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/dataQuality.xsd +554 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/distribution.xsd +202 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/extent.xsd +205 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/freeText.xsd +122 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/gmd.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/identification.xsd +348 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/maintenance.xsd +86 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataApplication.xsd +175 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataEntity.xsd +70 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataExtension.xsd +99 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/portrayalCatalogue.xsd +36 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/referenceSystem.xsd +100 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/spatialRepresentation.xsd +237 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/basicTypes.xsd +267 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateOperations.xsd +639 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateReferenceSystems.xsd +526 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateSystems.xsd +401 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coverage.xsd +462 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/datums.xsd +342 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dictionary.xsd +129 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/direction.xsd +80 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dynamicFeature.xsd +142 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/feature.xsd +215 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryAggregates.xsd +216 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic0d1d.xsd +304 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic2d.xsd +123 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryComplexes.xsd +89 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryPrimitives.xsd +892 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gml.xsd +14 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gmlBase.xsd +305 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/grids.xsd +58 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/measures.xsd +167 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/observation.xsd +90 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/referenceSystems.xsd +69 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporal.xsd +263 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalReferenceSystems.xsd +183 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalTopology.xsd +124 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/topology.xsd +372 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/units.xsd +156 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/valueObjects.xsd +212 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/catalogues.xsd +112 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/codelistItem.xsd +168 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/crsItem.xsd +1030 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/extendedTypes.xsd +75 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmx.xsd +2 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmxUsage.xsd +127 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/uomItem.xsd +162 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/gsr.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/spatialReferencing.xsd +24 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/geometry.xsd +35 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/gss.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/gts.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/temporalObjects.xsd +34 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceMetadata.xsd +197 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceModel.xsd +220 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/srv.xsd +13 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/basicTypes.xsd +431 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gco.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gcoBase.xsd +63 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/applicationSchema.xsd +43 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/citation.xsd +276 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/constraints.xsd +107 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/content.xsd +190 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/dataQuality.xsd +556 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/distribution.xsd +203 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/extent.xsd +206 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/freeText.xsd +123 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/gmd.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/identification.xsd +349 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/maintenance.xsd +87 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataApplication.xsd +176 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataEntity.xsd +71 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataExtension.xsd +100 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/portrayalCatalogue.xsd +37 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/referenceSystem.xsd +101 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/spatialRepresentation.xsd +238 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/catalogues.xsd +113 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/codelistItem.xsd +169 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/crsItem.xsd +1031 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/extendedTypes.xsd +76 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmx.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmxUsage.xsd +128 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/uomItem.xsd +163 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/gsr.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/spatialReferencing.xsd +25 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/geometry.xsd +36 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/gss.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/gts.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/temporalObjects.xsd +35 -0
- pycsw/plugins/profiles/ebrim/__init__.py +29 -0
- pycsw/plugins/profiles/ebrim/ebrim.py +174 -0
- pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim-iri.xsd +146 -0
- pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim.xsd +104 -0
- pycsw/plugins/profiles/iso19115p3/__init__.py +29 -0
- pycsw/plugins/profiles/iso19115p3/iso19115p3.py +854 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/cat.xsd +13 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/catalogues.xsd +53 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/codelistItem.xsd +54 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/crsItem.xsd +181 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/uomItem.xsd +38 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/cit.xsd +7 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/citation.xsd +514 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/cit.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/citation.xsd +523 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/baseTypes2014.xsd +530 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/gco.xsd +16 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes.xsd +92 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes_autoFromShapeChange.xsd +60 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/gcx.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/extent.xsd +241 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/gex.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmlWrapperTypes2014.xsd +159 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmw.xsd +14 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/lan.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/language.xsd +140 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/acquisitionInformationImagery.xsd +622 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/mac.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/acquisitionInformationImagery.xsd +590 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/event.xsd +108 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/mac.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/applicationSchema.xsd +61 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/mas.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/AbstractCommonClasses.xsd +358 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/commonClasses.xsd +220 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/mcc.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/constraints.xsd +188 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/mco.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/md1.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/metadataWExtendedType.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/md2.xsd +15 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/metadataWithExtensions.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/mda.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/metadataApplication.xsd +200 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/mdb.xsd +14 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/metadataBase.xsd +98 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/mdb.xsd +20 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/metadataBase.xsd +102 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/mds.xsd +22 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/metadataDataServices.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/mdt.xsd +13 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/metadataTransfer.xsd +111 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/metadataExtension.xsd +156 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/mex.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/maintenance.xsd +76 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/mmi.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/mpc.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/portrayalCatalogue.xsd +31 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/content.xsd +416 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/contentInformationImagery.xsd +185 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/mrc.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/content.xsd +419 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/contentInformationImagery.xsd +171 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/mrc.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/distribution.xsd +267 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/mrd.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/identification.xsd +517 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/mri.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineage.xsd +147 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineageImagery.xsd +236 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/mrl.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineage.xsd +147 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineageImagery.xsd +312 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/mrl.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/mrs.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/referenceSystem.xsd +47 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/msr.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentation.xsd +375 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentationImagery.xsd +119 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/msr.xsd +15 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentation.xsd +381 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentationImagery.xsd +120 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/serviceInformation.xsd +272 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/srv.xsd +6 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/serviceInformation.xsd +278 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/srv.xsd +6 -0
- pycsw/plugins/profiles/profile.py +141 -0
- pycsw/plugins/repository/__init__.py +29 -0
- pycsw/plugins/repository/odc/__init__.py +29 -0
- pycsw/plugins/repository/odc/odc.py +153 -0
- pycsw/server.py +938 -0
- pycsw/sru.py +217 -0
- pycsw/stac/__init__.py +29 -0
- pycsw/stac/api.py +571 -0
- pycsw/wsgi.py +233 -0
- 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
|