def getrecordbyid(self, id=[], esn='full', outputschema=namespaces['csw'], format=outputformat): """ Construct and process a GetRecordById request Parameters ---------- - id: the list of Ids - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') """ # construct request data = { 'service': self.service, 'version': self.version, 'request': 'GetRecordById', 'outputFormat': format, 'outputSchema': outputschema, 'elementsetname': esn, 'id': ','.join(id), } self.request = '%s%s' % (bind_url(self.url), urlencode(data)) self._invoke() if self.exceptionreport is None: self.results = {} self.records = OrderedDict() self._parserecords(outputschema, esn)
def get_records(self, maxrecords=50, **kwargs): """ Send a GetRecords request. The results are stored in self.records property. :param kwargs: see OWSLib's getrecords2 (https://github.com/geopython/OWSLib/blob/master/owslib/csw.py). A hint: if "xml" argument is passed (raw WML request), other arguments are ignored. Also, if maxrecords exceeds 50, getrecords2 is called multiple times to get maxrecords records (or less if less are found) """ # has xml argument been passed? try: payload = kwargs['xml'].strip() except KeyError: payload = None # all 'csw:Record' dict from 'GetRecords' request pages all_records = OrderedDict() while True: # set kwargs' maxrecords according to how many records we want (doesn't matter if it exceeds 50) kwargs['maxrecords'] = min(maxrecords, maxrecords - len(all_records)) # get next page by using OWSLib's getrecords2 if payload is None: self.getrecords2(**kwargs) else: self.getrecords2(xml=payload) # store found records in all_records all_records.update(self.records) # stop if records reached limit if len(all_records) >= maxrecords: break next_record = self.results['nextrecord'] # if next_record is "0", we got all records if next_record == 0: break # else, update start position if payload is None: kwargs['startposition'] = next_record else: payload_xml = etree.fromstring(payload) payload_xml.set('startPosition', str(next_record)) payload = etree.tostring(payload_xml, pretty_print=True, encoding='unicode') self.records = all_records
def getrecordbyid(self, id=[], esn="full", outputschema=namespaces["csw"], format=outputformat): """ Construct and process a GetRecordById request Parameters ---------- - id: the list of Ids - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') """ # construct request data = { "service": self.service, "version": self.version, "request": "GetRecordById", "outputFormat": format, "outputSchema": outputschema, "elementsetname": esn, "id": ",".join(id), } self.request = "%s%s" % (bind_url(self.url), urlencode(data)) self._invoke() if self.exceptionreport is None: self.results = {} self.records = OrderedDict() self._parserecords(outputschema, esn)
def _buildMetadata(self, parse_remote_metadata=False): '''set up capabilities metadata objects:''' self.updateSequence = self._capabilities.attrib.get('updateSequence') # serviceIdentification metadata serviceelem = self._capabilities.find( nspath('Service', ns=WMS_NAMESPACE)) self.identification = ServiceIdentification(serviceelem, self.version) # serviceProvider metadata self.provider = ServiceProvider(serviceelem) # serviceOperations metadata self.operations = [] for elem in self._capabilities.find( nspath('Capability/Request', ns=WMS_NAMESPACE))[:]: self.operations.append(OperationMetadata(elem)) # serviceContents metadata: our assumption is that services use a top-level # layer as a metadata organizer, nothing more. self.contents = OrderedDict() caps = self._capabilities.find(nspath('Capability', WMS_NAMESPACE)) # recursively gather content metadata for all layer elements. # To the WebMapService.contents store only metadata of named layers. def gather_layers(parent_elem, parent_metadata): layers = [] for index, elem in enumerate( parent_elem.findall(nspath('Layer', WMS_NAMESPACE))): cm = ContentMetadata( elem, parent=parent_metadata, index=index + 1, parse_remote_metadata=parse_remote_metadata) if cm.id: if cm.id in self.contents: warnings.warn( 'Content metadata for layer "%s" already exists. Using child layer' % cm.id) layers.append(cm) self.contents[cm.id] = cm cm.children = gather_layers(elem, cm) return layers gather_layers(caps, None) # exceptions self.exceptions = [ f.text for f in self._capabilities.findall( nspath('Capability/Exception/Format', WMS_NAMESPACE)) ]
def mundigetrecords2(self, xml): # removing unrelevant information from given payload payload = xml.strip() # all 'csw:Record' dictionnary from 'GetRecords' request pages all_records = OrderedDict() # getting first page (i.e. 'page0') self.getrecords2(xml=payload) all_records.update(self.records) page0 = lxml.etree.fromstring(self.response) sr_node = page0.find('csw:SearchResults', namespaces=mundi_nsmap) nb_total = int(sr_node.get("numberOfRecordsMatched")) nb_set = int(sr_node.get("numberOfRecordsReturned")) next_record = int(sr_node.get("nextRecord")) # calculation of page number if (nb_total == 0): nbPages = 1 else: nbPages = ceil(nb_total / nb_set) # getting other/next pages (i.e. 'pageN') i = 1 while (i < nbPages): # modifying payload with new start position node_p = lxml.etree.fromstring(payload) node_p.set('startPosition', str(next_record)) payload = lxml.etree.tostring(node_p, pretty_print=True) self.getrecords2(xml=payload) pageN = lxml.etree.fromstring(self.response) sr_node = pageN.find('csw:SearchResults', namespaces=mundi_nsmap) next_record = int(sr_node.get("nextRecord")) all_records.update(self.records) # go next page i += 1 self.records = all_records
def getrecords2(self, constraints=[], sortby=None, typenames='csw:Record', esn='summary', outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype='results'): """ Construct and process a GetRecords request Parameters ---------- - constraints: the list of constraints (OgcExpression from owslib.fes module) - sortby: an OGC SortBy object (SortBy from owslib.fes module) - typenames: the typeNames to query against (default is csw:Record) - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results') """ if xml is not None: self.request = etree.fromstring(xml) val = self.request.find( util.nspath_eval('csw:Query/csw:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) val = self.request.attrib.get('outputSchema') if val is not None: outputschema = util.testXMLValue(val, True) else: # construct request node0 = self._setrootelement('csw:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows', namespaces['ows']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('service', self.service) node0.set('resultType', resulttype) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement( node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn if any([len(constraints) > 0, cql is not None]): node2 = etree.SubElement( node1, util.nspath_eval('csw:Constraint', namespaces)) node2.set('version', '1.1.0') flt = fes.FilterRequest() if len(constraints) > 0: node2.append(flt.setConstraintList(constraints)) # Now add a CQL filter if passed in elif cql is not None: etree.SubElement( node2, util.nspath_eval('csw:CqlText', namespaces)).text = cql if sortby is not None and isinstance(sortby, fes.SortBy): node1.append(sortby.toXML()) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find( util.nspath_eval( 'csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find( util.nspath_eval( 'csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find( util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord') if val is not None: self.results['nextrecord'] = int(util.testXMLValue(val, True)) else: warnings.warn( """CSW Server did not supply a nextRecord value (it is optional), so the client should page through the results in another way.""") # For more info, see: # https://github.com/geopython/OWSLib/issues/100 self.results['nextrecord'] = None # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn)
def getrecords(self, qtype=None, keywords=[], typenames='csw:Record', propertyname='csw:AnyText', bbox=None, esn='summary', sortby=None, outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype='results'): """ Construct and process a GetRecords request Parameters ---------- - qtype: type of resource to query (i.e. service, dataset) - keywords: list of keywords - typenames: the typeNames to query against (default is csw:Record) - propertyname: the PropertyName to Filter against - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy] - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - sortby: property to sort results on - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results') """ warnings.warn( """Please use the updated 'getrecords2' method instead of 'getrecords'. The 'getrecords' method will be upgraded to use the 'getrecords2' parameters in a future version of OWSLib.""") if xml is not None: self.request = etree.fromstring(xml) val = self.request.find( util.nspath_eval('csw:Query/csw:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) else: # construct request node0 = self._setrootelement('csw:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows', namespaces['ows']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('resultType', resulttype) node0.set('service', self.service) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement( node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn self._setconstraint(node1, qtype, propertyname, keywords, bbox, cql, None) if sortby is not None: fes.setsortby(node1, sortby) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find( util.nspath_eval( 'csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find( util.nspath_eval( 'csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find( util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord') self.results['nextrecord'] = int(util.testXMLValue(val, True)) # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn)
class CatalogueServiceWeb(object): """ csw request class """ def __init__(self, url, lang='en-US', version='3.0.0', timeout=10, skip_caps=False, username=None, password=None, auth=None): """ Construct and process a GetCapabilities request Parameters ---------- - url: the URL of the CSW - lang: the language (default is 'en-US') - version: version (default is '3.0.0') - timeout: timeout in seconds - skip_caps: whether to skip GetCapabilities processing on init (default is False) - username: username for HTTP basic authentication - password: password for HTTP basic authentication - auth: instance of owslib.util.Authentication """ if auth: if username: auth.username = username if password: auth.password = password self.url = util.clean_ows_url(url) self.lang = lang self.version = version self.timeout = timeout self.auth = auth or Authentication(username, password) self.service = 'CSW' self.exceptionreport = None self.owscommon = ows.OwsCommon('2.0.0') if not skip_caps: # process GetCapabilities # construct request data = { 'service': self.service, 'version': self.version, 'request': 'GetCapabilities' } self.request = urlencode(data) self._invoke() if self.exceptionreport is None: self.updateSequence = self._exml.getroot().attrib.get( 'updateSequence') # ServiceIdentification val = self._exml.find( util.nspath_eval('ows200:ServiceIdentification', namespaces)) if val is not None: self.identification = ows.ServiceIdentification( val, self.owscommon.namespace) else: self.identification = None # ServiceProvider val = self._exml.find( util.nspath_eval('ows200:ServiceProvider', namespaces)) if val is not None: self.provider = ows.ServiceProvider( val, self.owscommon.namespace) else: self.provider = None # ServiceOperations metadata self.operations = [] for elem in self._exml.findall( util.nspath_eval( 'ows200:OperationsMetadata/ows200:Operation', namespaces)): # noqa self.operations.append( ows.OperationsMetadata(elem, self.owscommon.namespace)) self.constraints = {} for elem in self._exml.findall( util.nspath_eval( 'ows200:OperationsMetadata/ows200:Constraint', namespaces)): # noqa self.constraints[elem.attrib['name']] = ows.Constraint( elem, self.owscommon.namespace) self.parameters = {} for elem in self._exml.findall( util.nspath_eval( 'ows200:OperationsMetadata/ows200:Parameter', namespaces)): # noqa self.parameters[elem.attrib['name']] = ows.Parameter( elem, self.owscommon.namespace) # FilterCapabilities val = self._exml.find( util.nspath_eval('fes:Filter_Capabilities', namespaces)) self.filters = fes2.FilterCapabilities(val) def getdomain(self, dname, dtype='parameter'): """ Construct and process a GetDomain request Parameters ---------- - dname: the value of the Parameter or Property to query - dtype: whether to query a parameter (parameter) or property (property) """ # construct request dtypename = 'ParameterName' node0 = self._setrootelement('csw30:GetDomain') node0.set('service', self.service) node0.set('version', self.version) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) if dtype == 'property': dtypename = 'ValueReference' else: dtypename = 'ParameterName' etree.SubElement(node0, util.nspath_eval('csw30:%s' % dtypename, namespaces)).text = dname self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} val = self._exml.find( util.nspath_eval('csw30:DomainValues', namespaces)).attrib.get('type') self.results['type'] = util.testXMLValue(val, True) val = self._exml.find( util.nspath_eval('csw30:DomainValues/csw30:%s' % dtypename, namespaces)) self.results[dtype] = util.testXMLValue(val) # get the list of values associated with the Domain self.results['values'] = [] for f in self._exml.findall( util.nspath_eval( 'csw30:DomainValues/csw30:ListOfValues/csw30:Value', namespaces)): # noqa self.results['values'].append(util.testXMLValue(f)) def getrecordbyid(self, id=[], esn='full', outputschema=namespaces['csw30'], format=outputformat): """ Construct and process a GetRecordById request Parameters ---------- - id: the list of Ids - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/3.0.0') - format: the outputFormat (default is 'application/xml') """ # construct request data = { 'service': self.service, 'version': self.version, 'request': 'GetRecordById', 'outputFormat': format, 'outputSchema': outputschema, 'elementsetname': esn, 'id': ','.join(id), } self.request = urlencode(data) self._invoke() if self.exceptionreport is None: self.results = {} self.records = OrderedDict() self._parserecords(outputschema, esn) def getrecords(self, constraints=[], sortby=None, typenames='csw30:Record', esn='summary', outputschema=namespaces['csw30'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None): """ Construct and process a GetRecords request Parameters ---------- - constraints: the list of constraints (OgcExpression from owslib.fes2 module) - sortby: an OGC SortBy object (SortBy from owslib.fes2 module) - typenames: the typeNames to query against (default is csw30:Record) - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/3.0.0') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options """ if xml is not None: if xml.startswith('<'): self.request = etree.fromstring(xml) val = self.request.find( util.nspath_eval('csw30:Query/csw30:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) val = self.request.attrib.get('outputSchema') if val is not None: outputschema = util.testXMLValue(val, True) else: self.request = xml else: # construct request node0 = self._setrootelement('csw30:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows110', namespaces['ows110']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('service', self.service) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement( node0, util.nspath_eval('csw30:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement( node1, util.nspath_eval('csw30:ElementSetName', namespaces)).text = esn if any([len(constraints) > 0, cql is not None]): node2 = etree.SubElement( node1, util.nspath_eval('csw30:Constraint', namespaces)) node2.set('version', '1.1.0') flt = fes2.FilterRequest() if len(constraints) > 0: node2.append(flt.setConstraintList(constraints)) # Now add a CQL filter if passed in elif cql is not None: etree.SubElement( node2, util.nspath_eval('csw30:CqlText', namespaces)).text = cql if sortby is not None and isinstance(sortby, fes2.SortBy): node1.append(sortby.toXML()) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find( util.nspath_eval( 'csw30:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find( util.nspath_eval( 'csw30:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find( util.nspath_eval('csw30:SearchResults', namespaces)).attrib.get('nextRecord') if val is not None: self.results['nextrecord'] = int(util.testXMLValue(val, True)) else: warnings.warn( """CSW Server did not supply a nextRecord value (it is optional), so the client should page through the results in another way.""") # For more info, see: # https://github.com/geopython/OWSLib/issues/100 self.results['nextrecord'] = None # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn) def transaction(self, ttype=None, typename='csw30:Record', record=None, propertyname=None, propertyvalue=None, bbox=None, keywords=[], cql=None, identifier=None): """ Construct and process a Transaction request Parameters ---------- - ttype: the type of transaction 'insert, 'update', 'delete' - typename: the typename to describe (default is 'csw30:Record') - record: the XML record to insert - propertyname: the RecordProperty/PropertyName to Filter against - propertyvalue: the RecordProperty Value to Filter against (for updates) - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy] - keywords: list of keywords - cql: common query language text. Note this overrides bbox, qtype, keywords - identifier: record identifier. Note this overrides bbox, qtype, keywords, cql """ # construct request node0 = self._setrootelement('csw30:Transaction') node0.set('version', self.version) node0.set('service', self.service) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) validtransactions = ['insert', 'update', 'delete'] if ttype not in validtransactions: # invalid transaction raise RuntimeError('Invalid transaction \'%s\'.' % ttype) node1 = etree.SubElement( node0, util.nspath_eval('csw30:%s' % ttype.capitalize(), namespaces)) if ttype != 'update': node1.set('typeName', typename) if ttype == 'insert': if record is None: raise RuntimeError('Nothing to insert.') node1.append(etree.fromstring(record)) if ttype == 'update': if record is not None: node1.append(etree.fromstring(record)) else: if propertyname is not None and propertyvalue is not None: node2 = etree.SubElement( node1, util.nspath_eval('csw30:RecordProperty', namespaces)) etree.SubElement( node2, util.nspath_eval('csw30:Name', namespaces)).text = propertyname etree.SubElement( node2, util.nspath_eval('csw30:Value', namespaces)).text = propertyvalue self._setconstraint(node1, None, propertyname, keywords, bbox, cql, identifier) if ttype == 'delete': self._setconstraint(node1, None, propertyname, keywords, bbox, cql, identifier) self.request = node0 self._invoke() self.results = {} if self.exceptionreport is None: self._parsetransactionsummary() self._parseinsertresult() def harvest(self, source, resourcetype, resourceformat=None, harvestinterval=None, responsehandler=None): """ Construct and process a Harvest request Parameters ---------- - source: a URI to harvest - resourcetype: namespace identifying the type of resource - resourceformat: MIME type of the resource - harvestinterval: frequency of harvesting, in ISO8601 - responsehandler: endpoint that CSW should responsd to with response """ # construct request node0 = self._setrootelement('csw30:Harvest') node0.set('version', self.version) node0.set('service', self.service) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) etree.SubElement(node0, util.nspath_eval('csw30:Source', namespaces)).text = source etree.SubElement(node0, util.nspath_eval('csw30:ResourceType', namespaces)).text = resourcetype if resourceformat is not None: etree.SubElement( node0, util.nspath_eval('csw30:ResourceFormat', namespaces)).text = resourceformat if harvestinterval is not None: etree.SubElement( node0, util.nspath_eval('csw30:HarvestInterval', namespaces)).text = harvestinterval if responsehandler is not None: etree.SubElement( node0, util.nspath_eval('csw30:ResponseHandler', namespaces)).text = responsehandler self.request = node0 self._invoke() self.results = {} if self.exceptionreport is None: val = self._exml.find( util.nspath_eval('csw30:Acknowledgement', namespaces)) if util.testXMLValue(val) is not None: ts = val.attrib.get('timeStamp') self.timestamp = util.testXMLValue(ts, True) id = val.find(util.nspath_eval('csw30:RequestId', namespaces)) self.id = util.testXMLValue(id) else: self._parsetransactionsummary() self._parseinsertresult() def get_operation_by_name(self, name): """Return a named operation""" for item in self.operations: if item.name.lower() == name.lower(): return item raise KeyError("No operation named %s" % name) def getService_urls(self, service_string=None): """ Return easily identifiable URLs for all service types Parameters ---------- - service_string: a URI to lookup """ urls = [] for key, rec in list(self.records.items()): # create a generator object, and iterate through it until the match is found # if not found, gets the default value (here "none") url = next( (d['url'] for d in rec.references if d['scheme'] == service_string), None) if url is not None: urls.append(url) return urls def _parseinsertresult(self): self.results['insertresults'] = [] for i in self._exml.findall( './/' + util.nspath_eval('csw30:InsertResult', namespaces)): for j in i.findall( util.nspath_eval('csw30:BriefRecord/dc:identifier', namespaces)): self.results['insertresults'].append(util.testXMLValue(j)) def _parserecords(self, outputschema, esn): if outputschema == namespaces['gmd']: # iso 19139 for i in self._exml.findall('.//' + util.nspath_eval('gmd:MD_Metadata', namespaces)) or \ self._exml.findall('.//' + util.nspath_eval('gmi:MI_Metadata', namespaces)): val = i.find( util.nspath_eval('gmd:fileIdentifier/gco:CharacterString', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = MD_Metadata(i) for i in self._exml.findall( './/' + util.nspath_eval('gfc:FC_FeatureCatalogue', namespaces)): identifier = self._setidentifierkey( util.testXMLValue(i.attrib['uuid'], attrib=True)) self.records[identifier] = FC_FeatureCatalogue(i) elif outputschema == namespaces['fgdc']: # fgdc csdgm for i in self._exml.findall('.//metadata'): val = i.find('idinfo/datasetid') identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = Metadata(i) elif outputschema == namespaces['dif']: # nasa dif for i in self._exml.findall( './/' + util.nspath_eval('dif:DIF', namespaces)): val = i.find(util.nspath_eval('dif:Entry_ID', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = DIF(i) elif outputschema == namespaces['gm03']: # GM03 for i in self._exml.findall( './/' + util.nspath_eval('gm03:TRANSFER', namespaces)): val = i.find( util.nspath_eval('gm03:fileIdentifier', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = GM03(i) else: # process default for i in self._exml.findall('.//' + util.nspath_eval( 'csw30:%s' % self._setesnel(esn), namespaces)): val = i.find(util.nspath_eval('dc:identifier', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = Csw30Record(i) def _parsetransactionsummary(self): val = self._exml.find( util.nspath_eval( 'csw30:TransactionResponse/csw30:TransactionSummary', namespaces)) if val is not None: rid = val.attrib.get('requestId') self.results['requestid'] = util.testXMLValue(rid, True) ts = val.find(util.nspath_eval('csw30:totalInserted', namespaces)) self.results['inserted'] = int(util.testXMLValue(ts)) ts = val.find(util.nspath_eval('csw30:totalUpdated', namespaces)) self.results['updated'] = int(util.testXMLValue(ts)) ts = val.find(util.nspath_eval('csw30:totalDeleted', namespaces)) self.results['deleted'] = int(util.testXMLValue(ts)) def _setesnel(self, esn): """ Set the element name to parse depending on the ElementSetName requested """ el = 'Record' if esn == 'brief': el = 'BriefRecord' if esn == 'summary': el = 'SummaryRecord' return el def _setidentifierkey(self, el): if el is None: return 'owslib_random_%i' % random.randint(1, 65536) else: return el def _setrootelement(self, el): if etree.__name__ == 'lxml.etree': # apply nsmap return etree.Element(util.nspath_eval(el, namespaces), nsmap=namespaces) else: return etree.Element(util.nspath_eval(el, namespaces)) def _setconstraint(self, parent, qtype=None, propertyname='csw30:AnyText', keywords=[], bbox=None, cql=None, identifier=None): if keywords or bbox is not None or qtype is not None or cql is not None or identifier is not None: node0 = etree.SubElement( parent, util.nspath_eval('csw30:Constraint', namespaces)) node0.set('version', '1.1.0') if identifier is not None: # set identifier filter, overrides all other parameters flt = fes2.FilterRequest() node0.append(flt.set(identifier=identifier)) elif cql is not None: # send raw CQL query # CQL passed, overrides all other parameters node1 = etree.SubElement( node0, util.nspath_eval('csw30:CqlText', namespaces)) node1.text = cql else: # construct a Filter request flt = fes2.FilterRequest() node0.append( flt.set(qtype=qtype, keywords=keywords, propertyname=propertyname, bbox=bbox)) def _invoke(self): # do HTTP request request_url = self.url # Get correct URL based on Operation list. # If skip_caps=True, then self.operations has not been set, so use # default URL. if hasattr(self, 'operations'): try: op = self.get_operation_by_name('getrecords') if isinstance(self.request, str): # GET KVP get_verbs = [ x for x in op.methods if x.get('type').lower() == 'get' ] request_url = get_verbs[0].get('url') else: post_verbs = [ x for x in op.methods if x.get('type').lower() == 'post' ] if len(post_verbs) > 1: # Filter by constraints. We must match a PostEncoding of "XML" for pv in post_verbs: for const in pv.get('constraints'): if const.name.lower() == 'postencoding': values = [v.lower() for v in const.values] if 'xml' in values: request_url = pv.get('url') break else: # Well, just use the first one. request_url = post_verbs[0].get('url') elif len(post_verbs) == 1: request_url = post_verbs[0].get('url') except Exception: # no such luck, just go with request_url pass if isinstance(self.request, str): # GET KVP self.request = '%s%s' % (bind_url(request_url), self.request) self.response = openURL(self.request, None, 'Get', timeout=self.timeout, auth=self.auth, headers={ 'Accept': outputformat }).read() else: self.request = cleanup_namespaces(self.request) # Add any namespaces used in the "typeNames" attribute of the # csw30:Query element to the query's xml namespaces. for query in self.request.findall( util.nspath_eval('csw30:Query', namespaces)): ns = query.get("typeNames", None) if ns is not None: # Pull out "gmd" from something like "gmd:MD_Metadata" from the list # of typenames ns_keys = [x.split(':')[0] for x in ns.split(' ')] self.request = add_namespaces(self.request, ns_keys) self.request = add_namespaces(self.request, 'fes') self.request = util.element_to_string(self.request, encoding='utf-8') self.response = http_post(request_url, self.request, self.lang, self.timeout, auth=self.auth) # parse result see if it's XML self._exml = etree.parse(BytesIO(self.response)) # it's XML. Attempt to decipher whether the XML response is CSW-ish """ valid_xpaths = [ util.nspath_eval('ows200:ExceptionReport', namespaces), util.nspath_eval('csw30:Capabilities', namespaces), util.nspath_eval('csw30:DescribeRecordResponse', namespaces), util.nspath_eval('csw30:GetDomainResponse', namespaces), util.nspath_eval('csw30:GetRecordsResponse', namespaces), util.nspath_eval('csw30:GetRecordByIdResponse', namespaces), util.nspath_eval('csw30:HarvestResponse', namespaces), util.nspath_eval('csw30:TransactionResponse', namespaces), util.nspath_eval('csw30:Record', namespaces) ] if self._exml.getroot().tag not in valid_xpaths: raise RuntimeError('Document is XML, but not CSW-ish') # check if it's an OGC Exception val = self._exml.find(util.nspath_eval('ows200:Exception', namespaces)) if val is not None: raise ows.ExceptionReport(self._exml, self.owscommon.namespace) else: self.exceptionreport = None
class CatalogueServiceWeb: """ csw request class """ def __init__(self, url, lang='en-US', version='2.0.2', timeout=10, skip_caps=False): """ Construct and process a GetCapabilities request Parameters ---------- - url: the URL of the CSW - lang: the language (default is 'en-US') - version: version (default is '2.0.2') - timeout: timeout in seconds - skip_caps: whether to skip GetCapabilities processing on init (default is False) """ self.url = url self.lang = lang self.version = version self.timeout = timeout self.service = 'CSW' self.exceptionreport = None self.owscommon = ows.OwsCommon('1.0.0') if not skip_caps: # process GetCapabilities # construct request data = {'service': self.service, 'version': self.version, 'request': 'GetCapabilities'} self.request = '%s%s' % (bind_url(self.url), urlencode(data)) self._invoke() if self.exceptionreport is None: # ServiceIdentification val = self._exml.find(util.nspath_eval('ows:ServiceIdentification', namespaces)) self.identification=ows.ServiceIdentification(val,self.owscommon.namespace) # ServiceProvider val = self._exml.find(util.nspath_eval('ows:ServiceProvider', namespaces)) self.provider=ows.ServiceProvider(val,self.owscommon.namespace) # ServiceOperations metadata self.operations=[] for elem in self._exml.findall(util.nspath_eval('ows:OperationsMetadata/ows:Operation', namespaces)): self.operations.append(ows.OperationsMetadata(elem, self.owscommon.namespace)) # FilterCapabilities val = self._exml.find(util.nspath_eval('ogc:Filter_Capabilities', namespaces)) self.filters=fes.FilterCapabilities(val) def describerecord(self, typename='csw:Record', format=outputformat): """ Construct and process DescribeRecord request Parameters ---------- - typename: the typename to describe (default is 'csw:Record') - format: the outputFormat (default is 'application/xml') """ # construct request node0 = self._setrootelement('csw:DescribeRecord') node0.set('service', self.service) node0.set('version', self.version) node0.set('outputFormat', format) node0.set('schemaLanguage', namespaces['xs2']) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) etree.SubElement(node0, util.nspath_eval('csw:TypeName', namespaces)).text = typename self.request = node0 self._invoke() # parse result # TODO: process the XML Schema (you're on your own for now with self.response) def getdomain(self, dname, dtype='parameter'): """ Construct and process a GetDomain request Parameters ---------- - dname: the value of the Parameter or Property to query - dtype: whether to query a parameter (parameter) or property (property) """ # construct request dtypename = 'ParameterName' node0 = self._setrootelement('csw:GetDomain') node0.set('service', self.service) node0.set('version', self.version) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) if dtype == 'property': dtypename = 'PropertyName' etree.SubElement(node0, util.nspath_eval('csw:%s' % dtypename, namespaces)).text = dname self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} val = self._exml.find(util.nspath_eval('csw:DomainValues', namespaces)).attrib.get('type') self.results['type'] = util.testXMLValue(val, True) val = self._exml.find(util.nspath_eval('csw:DomainValues/csw:%s' % dtypename, namespaces)) self.results[dtype] = util.testXMLValue(val) # get the list of values associated with the Domain self.results['values'] = [] for f in self._exml.findall(util.nspath_eval('csw:DomainValues/csw:ListOfValues/csw:Value', namespaces)): self.results['values'].append(util.testXMLValue(f)) def getrecords(self, qtype=None, keywords=[], typenames='csw:Record', propertyname='csw:AnyText', bbox=None, esn='summary', sortby=None, outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype='results'): """ Construct and process a GetRecords request Parameters ---------- - qtype: type of resource to query (i.e. service, dataset) - keywords: list of keywords - typenames: the typeNames to query against (default is csw:Record) - propertyname: the PropertyName to Filter against - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy] - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - sortby: property to sort results on - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results') """ warnings.warn("""Please use the updated 'getrecords2' method instead of 'getrecords'. The 'getrecords' method will be upgraded to use the 'getrecords2' parameters in a future version of OWSLib.""") if xml is not None: self.request = etree.fromstring(xml) val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) else: # construct request node0 = self._setrootelement('csw:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows', namespaces['ows']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('resultType', resulttype) node0.set('service', self.service) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement(node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn self._setconstraint(node1, qtype, propertyname, keywords, bbox, cql, None) if sortby is not None: fes.setsortby(node1, sortby) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord') self.results['nextrecord'] = int(util.testXMLValue(val, True)) # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn) def getrecordbyid(self, id=[], esn='full', outputschema=namespaces['csw'], format=outputformat): """ Construct and process a GetRecordById request Parameters ---------- - id: the list of Ids - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') """ # construct request data = { 'service': self.service, 'version': self.version, 'request': 'GetRecordById', 'outputFormat': format, 'outputSchema': outputschema, 'elementsetname': esn, 'id': ','.join(id), } self.request = '%s%s' % (bind_url(self.url), urlencode(data)) self._invoke() if self.exceptionreport is None: self.results = {} self.records = OrderedDict() self._parserecords(outputschema, esn) def getrecords2(self, constraints=[], sortby=None, typenames='csw:Record', esn='summary', outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype='results'): """ Construct and process a GetRecords request Parameters ---------- - constraints: the list of constraints (OgcExpression from owslib.fes module) - sortby: an OGC SortBy object (SortBy from owslib.fes module) - typenames: the typeNames to query against (default is csw:Record) - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results') """ if xml is not None: self.request = etree.fromstring(xml) val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) else: # construct request node0 = self._setrootelement('csw:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows', namespaces['ows']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('service', self.service) node0.set('resultType', resulttype) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement(node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn if any([len(constraints) > 0, cql is not None]): node2 = etree.SubElement(node1, util.nspath_eval('csw:Constraint', namespaces)) node2.set('version', '1.1.0') flt = fes.FilterRequest() if len(constraints) > 0: node2.append(flt.setConstraintList(constraints)) # Now add a CQL filter if passed in elif cql is not None: etree.SubElement(node2, util.nspath_eval('csw:CqlText', namespaces)).text = cql if sortby is not None and isinstance(sortby, fes.SortBy): node1.append(sortby.toXML()) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord') if val is not None: self.results['nextrecord'] = int(util.testXMLValue(val, True)) else: warnings.warn("""CSW Server did not supply a nextRecord value (it is optional), so the client should page through the results in another way.""") # For more info, see: # https://github.com/geopython/OWSLib/issues/100 self.results['nextrecord'] = None # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn) def transaction(self, ttype=None, typename='csw:Record', record=None, propertyname=None, propertyvalue=None, bbox=None, keywords=[], cql=None, identifier=None): """ Construct and process a Transaction request Parameters ---------- - ttype: the type of transaction 'insert, 'update', 'delete' - typename: the typename to describe (default is 'csw:Record') - record: the XML record to insert - propertyname: the RecordProperty/PropertyName to Filter against - propertyvalue: the RecordProperty Value to Filter against (for updates) - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy] - keywords: list of keywords - cql: common query language text. Note this overrides bbox, qtype, keywords - identifier: record identifier. Note this overrides bbox, qtype, keywords, cql """ # construct request node0 = self._setrootelement('csw:Transaction') node0.set('version', self.version) node0.set('service', self.service) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) validtransactions = ['insert', 'update', 'delete'] if ttype not in validtransactions: # invalid transaction raise RuntimeError, 'Invalid transaction \'%s\'.' % ttype node1 = etree.SubElement(node0, util.nspath_eval('csw:%s' % ttype.capitalize(), namespaces)) if ttype != 'update': node1.set('typeName', typename) if ttype == 'insert': if record is None: raise RuntimeError, 'Nothing to insert.' node1.append(etree.fromstring(record)) if ttype == 'update': if record is not None: node1.append(etree.fromstring(record)) else: if propertyname is not None and propertyvalue is not None: node2 = etree.SubElement(node1, util.nspath_eval('csw:RecordProperty', namespaces)) etree.SubElement(node2, util.nspath_eval('csw:Name', namespaces)).text = propertyname etree.SubElement(node2, util.nspath_eval('csw:Value', namespaces)).text = propertyvalue self._setconstraint(node1, qtype, propertyname, keywords, bbox, cql, identifier) if ttype == 'delete': self._setconstraint(node1, None, propertyname, keywords, bbox, cql, identifier) self.request = node0 self._invoke() self.results = {} if self.exceptionreport is None: self._parsetransactionsummary() self._parseinsertresult() def harvest(self, source, resourcetype, resourceformat=None, harvestinterval=None, responsehandler=None): """ Construct and process a Harvest request Parameters ---------- - source: a URI to harvest - resourcetype: namespace identifying the type of resource - resourceformat: MIME type of the resource - harvestinterval: frequency of harvesting, in ISO8601 - responsehandler: endpoint that CSW should responsd to with response """ # construct request node0 = self._setrootelement('csw:Harvest') node0.set('version', self.version) node0.set('service', self.service) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) etree.SubElement(node0, util.nspath_eval('csw:Source', namespaces)).text = source etree.SubElement(node0, util.nspath_eval('csw:ResourceType', namespaces)).text = resourcetype if resourceformat is not None: etree.SubElement(node0, util.nspath_eval('csw:ResourceFormat', namespaces)).text = resourceformat if harvestinterval is not None: etree.SubElement(node0, util.nspath_eval('csw:HarvestInterval', namespaces)).text = harvestinterval if responsehandler is not None: etree.SubElement(node0, util.nspath_eval('csw:ResponseHandler', namespaces)).text = responsehandler self.request = node0 self._invoke() self.results = {} if self.exceptionreport is None: val = self._exml.find(util.nspath_eval('csw:Acknowledgement', namespaces)) if util.testXMLValue(val) is not None: ts = val.attrib.get('timeStamp') self.timestamp = util.testXMLValue(ts, True) id = val.find(util.nspath_eval('csw:RequestId', namespaces)) self.id = util.testXMLValue(id) else: self._parsetransactionsummary() self._parseinsertresult() def get_operation_by_name(self, name): """Return a named operation""" for item in self.operations: if item.name.lower() == name.lower(): return item raise KeyError, "No operation named %s" % name def getService_urls(self, service_string=None): """ Return easily identifiable URLs for all service types Parameters ---------- - service_string: a URI to lookup """ urls=[] for key,rec in self.records.iteritems(): #create a generator object, and iterate through it until the match is found #if not found, gets the default value (here "none") url = next((d['url'] for d in rec.references if d['scheme'] == service_string), None) if url is not None: urls.append(url) return urls def _parseinsertresult(self): self.results['insertresults'] = [] for i in self._exml.findall(util.nspath_eval('csw:InsertResult', namespaces)): for j in i.findall(util.nspath_eval('csw:BriefRecord/dc:identifier', namespaces)): self.results['insertresults'].append(util.testXMLValue(j)) def _parserecords(self, outputschema, esn): if outputschema == namespaces['gmd']: # iso 19139 for i in self._exml.findall('.//'+util.nspath_eval('gmd:MD_Metadata', namespaces)): val = i.find(util.nspath_eval('gmd:fileIdentifier/gco:CharacterString', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = MD_Metadata(i) elif outputschema == namespaces['fgdc']: # fgdc csdgm for i in self._exml.findall('.//metadata'): val = i.find('idinfo/datasetid') identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = Metadata(i) elif outputschema == namespaces['dif']: # nasa dif for i in self._exml.findall('.//'+util.nspath_eval('dif:DIF', namespaces)): val = i.find(util.nspath_eval('dif:Entry_ID', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = DIF(i) else: # process default for i in self._exml.findall('.//'+util.nspath_eval('csw:%s' % self._setesnel(esn), namespaces)): val = i.find(util.nspath_eval('dc:identifier', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = CswRecord(i) def _parsetransactionsummary(self): val = self._exml.find(util.nspath_eval('csw:TransactionSummary', namespaces)) if val is not None: rid = val.attrib.get('requestId') self.results['requestid'] = util.testXMLValue(rid, True) ts = val.find(util.nspath_eval('csw:totalInserted', namespaces)) self.results['inserted'] = int(util.testXMLValue(ts)) ts = val.find(util.nspath_eval('csw:totalUpdated', namespaces)) self.results['updated'] = int(util.testXMLValue(ts)) ts = val.find(util.nspath_eval('csw:totalDeleted', namespaces)) self.results['deleted'] = int(util.testXMLValue(ts)) def _setesnel(self, esn): """ Set the element name to parse depending on the ElementSetName requested """ el = 'Record' if esn == 'brief': el = 'BriefRecord' if esn == 'summary': el = 'SummaryRecord' return el def _setidentifierkey(self, el): if el is None: return 'owslib_random_%i' % random.randint(1,65536) else: return el def _setrootelement(self, el): if etree.__name__ == 'lxml.etree': # apply nsmap return etree.Element(util.nspath_eval(el, namespaces), nsmap=namespaces) else: return etree.Element(util.nspath_eval(el, namespaces)) def _setconstraint(self, parent, qtype=None, propertyname='csw:AnyText', keywords=[], bbox=None, cql=None, identifier=None): if keywords or bbox is not None or qtype is not None or cql is not None or identifier is not None: node0 = etree.SubElement(parent, util.nspath_eval('csw:Constraint', namespaces)) node0.set('version', '1.1.0') if identifier is not None: # set identifier filter, overrides all other parameters flt = fes.FilterRequest() node0.append(flt.set(identifier=identifier)) elif cql is not None: # send raw CQL query # CQL passed, overrides all other parameters node1 = etree.SubElement(node0, util.nspath_eval('csw:CqlText', namespaces)) node1.text = cql else: # construct a Filter request flt = fes.FilterRequest() node0.append(flt.set(qtype=qtype, keywords=keywords, propertyname=propertyname,bbox=bbox)) def _invoke(self): # do HTTP request if isinstance(self.request, basestring): # GET KVP self.response = urlopen(self.request, timeout=self.timeout).read() else: xml_post_url = self.url # Get correct POST URL based on Operation list. # If skip_caps=True, then self.operations has not been set, so use # default URL. if hasattr(self, 'operations'): for op in self.operations: post_verbs = filter(lambda x: x.get('type').lower() == 'post', op.methods) if len(post_verbs) > 1: # Filter by constraints. We must match a PostEncoding of "XML" try: xml_post_url = next(x for x in filter(list, ([pv.get('url') for const in pv.get('constraints') if const.name.lower() == "postencoding" and 'xml' in map(lambda x: x.lower(), const.values)] for pv in post_verbs)))[0] except StopIteration: # Well, just use the first one. xml_post_url = post_verbs[0].get('url') elif len(post_verbs) == 1: xml_post_url = post_verbs[0].get('url') self.request = cleanup_namespaces(self.request) # Add any namespaces used in the "typeNames" attribute of the # csw:Query element to the query's xml namespaces. for query in self.request.findall(util.nspath_eval('csw:Query', namespaces)): ns = query.get("typeNames", None) if ns is not None: # Pull out "gmd" from something like "gmd:MD_Metadata" from the list # of typenames ns_keys = [x.split(':')[0] for x in ns.split(' ')] self.request = add_namespaces(self.request, ns_keys) self.request = util.element_to_string(self.request, encoding='utf-8') self.response = util.http_post(xml_post_url, self.request, self.lang, self.timeout) # parse result see if it's XML self._exml = etree.parse(StringIO.StringIO(self.response)) # it's XML. Attempt to decipher whether the XML response is CSW-ish """ valid_xpaths = [ util.nspath_eval('ows:ExceptionReport', namespaces), util.nspath_eval('csw:Capabilities', namespaces), util.nspath_eval('csw:DescribeRecordResponse', namespaces), util.nspath_eval('csw:GetDomainResponse', namespaces), util.nspath_eval('csw:GetRecordsResponse', namespaces), util.nspath_eval('csw:GetRecordByIdResponse', namespaces), util.nspath_eval('csw:HarvestResponse', namespaces), util.nspath_eval('csw:TransactionResponse', namespaces) ] if self._exml.getroot().tag not in valid_xpaths: raise RuntimeError, 'Document is XML, but not CSW-ish' # check if it's an OGC Exception val = self._exml.find(util.nspath_eval('ows:Exception', namespaces)) if val is not None: raise ows.ExceptionReport(self._exml, self.owscommon.namespace) else: self.exceptionreport = None
def getrecords2(self, constraints=[], sortby=None, typenames='csw:Record', esn='summary', outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype='results'): """ Construct and process a GetRecords request Parameters ---------- - constraints: the list of constraints (OgcExpression from owslib.fes module) - sortby: an OGC SortBy object (SortBy from owslib.fes module) - typenames: the typeNames to query against (default is csw:Record) - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results') """ if xml is not None: self.request = etree.fromstring(xml) val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) else: # construct request node0 = self._setrootelement('csw:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows', namespaces['ows']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('service', self.service) node0.set('resultType', resulttype) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement(node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn if any([len(constraints) > 0, cql is not None]): node2 = etree.SubElement(node1, util.nspath_eval('csw:Constraint', namespaces)) node2.set('version', '1.1.0') flt = fes.FilterRequest() if len(constraints) > 0: node2.append(flt.setConstraintList(constraints)) # Now add a CQL filter if passed in elif cql is not None: etree.SubElement(node2, util.nspath_eval('csw:CqlText', namespaces)).text = cql if sortby is not None and isinstance(sortby, fes.SortBy): node1.append(sortby.toXML()) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord') if val is not None: self.results['nextrecord'] = int(util.testXMLValue(val, True)) else: warnings.warn("""CSW Server did not supply a nextRecord value (it is optional), so the client should page through the results in another way.""") # For more info, see: # https://github.com/geopython/OWSLib/issues/100 self.results['nextrecord'] = None # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn)
def getrecords(self, qtype=None, keywords=[], typenames='csw:Record', propertyname='csw:AnyText', bbox=None, esn='summary', sortby=None, outputschema=namespaces['csw'], format=outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype='results'): """ Construct and process a GetRecords request Parameters ---------- - qtype: type of resource to query (i.e. service, dataset) - keywords: list of keywords - typenames: the typeNames to query against (default is csw:Record) - propertyname: the PropertyName to Filter against - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy] - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary') - sortby: property to sort results on - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2') - format: the outputFormat (default is 'application/xml') - startposition: requests a slice of the result set, starting at this position (default is 0) - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10) - cql: common query language text. Note this overrides bbox, qtype, keywords - xml: raw XML request. Note this overrides all other options - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results') """ warnings.warn("""Please use the updated 'getrecords2' method instead of 'getrecords'. The 'getrecords' method will be upgraded to use the 'getrecords2' parameters in a future version of OWSLib.""") if xml is not None: self.request = etree.fromstring(xml) val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces)) if val is not None: esn = util.testXMLValue(val) else: # construct request node0 = self._setrootelement('csw:GetRecords') if etree.__name__ != 'lxml.etree': # apply nsmap manually node0.set('xmlns:ows', namespaces['ows']) node0.set('xmlns:gmd', namespaces['gmd']) node0.set('xmlns:dif', namespaces['dif']) node0.set('xmlns:fgdc', namespaces['fgdc']) node0.set('outputSchema', outputschema) node0.set('outputFormat', format) node0.set('version', self.version) node0.set('resultType', resulttype) node0.set('service', self.service) if startposition > 0: node0.set('startPosition', str(startposition)) node0.set('maxRecords', str(maxrecords)) node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location) node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces)) node1.set('typeNames', typenames) etree.SubElement(node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn self._setconstraint(node1, qtype, propertyname, keywords, bbox, cql, None) if sortby is not None: fes.setsortby(node1, sortby) self.request = node0 self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched') self.results['matches'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned') self.results['returned'] = int(util.testXMLValue(val, True)) val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord') self.results['nextrecord'] = int(util.testXMLValue(val, True)) # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn)
def getrecords2( self, constraints=[], sortby=None, typenames="csw:Record", esn="summary", outputschema=csw_namespaces["csw"], format=csw_outputformat, startposition=0, maxrecords=10, cql=None, xml=None, resulttype="results", ): if xml is not None: self.request = etree.fromstring(xml) val = self.request.find( util.nspath_eval("csw:Query/csw:ElementSetName", csw_namespaces)) if val is not None: esn = util.testXMLValue(val) val = self.request.attrib.get("outputSchema") if val is not None: outputschema = util.testXMLValue(val, True) else: # construct request node0 = self._setrootelement("csw:GetRecords") if etree.__name__ != "lxml.etree": # apply nsmap manually node0.set("xmlns:ows", csw_namespaces["ows"]) node0.set("xmlns:gmd", csw_namespaces["gmd"]) node0.set("xmlns:dif", csw_namespaces["dif"]) node0.set("xmlns:fgdc", csw_namespaces["fgdc"]) node0.set("outputSchema", outputschema) node0.set("outputFormat", format) node0.set("version", self.version) node0.set("service", self.service) node0.set("resultType", resulttype) if startposition > 0: node0.set("startPosition", str(startposition)) node0.set("maxRecords", str(maxrecords)) node0.set( util.nspath_eval("xsi:schemaLocation", csw_namespaces), csw_schema_location, ) node1 = etree.SubElement( node0, util.nspath_eval("csw:Query", csw_namespaces)) node1.set("typeNames", typenames) etree.SubElement( node1, util.nspath_eval("csw:ElementSetName", csw_namespaces)).text = esn if any([len(constraints) > 0, cql is not None]): node2 = etree.SubElement( node1, util.nspath_eval("csw:Constraint", csw_namespaces)) node2.set("version", "1.1.0") flt = fes.FilterRequest() if len(constraints) > 0: node2.append(flt.setConstraintList(constraints)) # Now add a CQL filter if passed in elif cql is not None: etree.SubElement( node2, util.nspath_eval("csw:CqlText", csw_namespaces)).text = cql if sortby is not None and isinstance(sortby, fes.SortBy): node1.append(sortby.toXML()) self.request = node0 # print("Delta") self._invoke() if self.exceptionreport is None: self.results = {} # process search results attributes val = self._exml.find( util.nspath_eval( "csw:SearchResults", csw_namespaces)).attrib.get("numberOfRecordsMatched") self.results["matches"] = int(util.testXMLValue(val, True)) val = self._exml.find( util.nspath_eval( "csw:SearchResults", csw_namespaces)).attrib.get("numberOfRecordsReturned") self.results["returned"] = int(util.testXMLValue(val, True)) val = self._exml.find( util.nspath_eval("csw:SearchResults", csw_namespaces)).attrib.get("nextRecord") if val is not None: self.results["nextrecord"] = int(util.testXMLValue(val, True)) else: warnings.warn( """CSW Server did not supply a nextRecord value (it is optional), so the client should page through the results in another way.""") # For more info, see: # https://github.com/geopython/OWSLib/issues/100 self.results["nextrecord"] = None # process list of matching records self.records = OrderedDict() self._parserecords(outputschema, esn)