• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geopython / OWSLib / 11628884181

01 Nov 2024 11:49AM UTC coverage: 58.814% (-1.3%) from 60.156%
11628884181

Pull #548

github

web-flow
Merge 4b9b7cf1f into ae98c2039
Pull Request #548: Strip out redundant __new__ to enable object pickling

8364 of 14221 relevant lines covered (58.81%)

1.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

15.44
/owslib/catalogue/csw3.py
1
# -*- coding: ISO-8859-15 -*-
2
# =============================================================================
3
# Copyright (c) 2021 Tom Kralidis
4
#
5
# Authors : Tom Kralidis <tomkralidis@gmail.com>
6
#
7
# Contact email: tomkralidis@gmail.com
8
# =============================================================================
9

10
""" CSW 3.0.0 request and response processor """
11

12
import inspect
2✔
13
import warnings
2✔
14
from io import BytesIO
2✔
15
import random
2✔
16
from urllib.parse import urlencode
2✔
17

18
from owslib.etree import etree
2✔
19
from owslib import fes2
2✔
20
from owslib import util
2✔
21
from owslib import ows
2✔
22
from owslib.iso import MD_Metadata, FC_FeatureCatalogue
2✔
23
from owslib.iso3 import MD_Metadata as MD_Metadata3  # ISO 19115 Part 3 XML
2✔
24
from owslib.iso3 import FC_FeatureCatalogue as FC_FeatureCatalogue3  # ISO 19115 Part 3 XML
2✔
25
from owslib.fgdc import Metadata
2✔
26
from owslib.dif import DIF
2✔
27
from owslib.gm03 import GM03
2✔
28
from owslib.namespaces import Namespaces
2✔
29
from owslib.util import cleanup_namespaces, bind_url, add_namespaces, OrderedDict, Authentication, openURL, http_post
2✔
30

31
# default variables
32
outputformat = 'application/xml'
2✔
33

34

35
def get_namespaces():
2✔
36
    n = Namespaces()
2✔
37
    return n.get_namespaces()
2✔
38

39

40
namespaces = get_namespaces()
2✔
41
schema = 'http://schemas.opengis.net/cat/csw/3.0/cswAll.xsd'
2✔
42
schema_location = '%s %s' % (namespaces['csw30'], schema)
2✔
43

44

45
class CatalogueServiceWeb(object):
2✔
46
    """ csw request class """
47
    def __init__(self, url, lang='en-US', version='3.0.0', timeout=10, skip_caps=False,
2✔
48
                 username=None, password=None, auth=None, headers=None):
49
        """
50

51
        Construct and process a GetCapabilities request
52

53
        Parameters
54
        ----------
55

56
        - url: the URL of the CSW
57
        - lang: the language (default is 'en-US')
58
        - version: version (default is '3.0.0')
59
        - timeout: timeout in seconds
60
        - skip_caps: whether to skip GetCapabilities processing on init (default is False)
61
        - username: username for HTTP basic authentication
62
        - password: password for HTTP basic authentication
63
        - auth: instance of owslib.util.Authentication
64
        - headers: HTTP headers to send with requests
65

66
        """
67
        if auth:
2✔
68
            if username:
2✔
69
                auth.username = username
×
70
            if password:
2✔
71
                auth.password = password
×
72
        self.url = util.clean_ows_url(url)
2✔
73
        self.lang = lang
2✔
74
        self.version = version
2✔
75
        self.timeout = timeout
2✔
76
        self.auth = auth or Authentication(username, password)
2✔
77
        self.headers = headers
2✔
78
        self.service = 'CSW'
2✔
79
        self.exceptionreport = None
2✔
80
        self.owscommon = ows.OwsCommon('2.0.0')
2✔
81

82
        if not skip_caps:  # process GetCapabilities
2✔
83
            # construct request
84

85
            data = {'service': self.service, 'version': self.version, 'request': 'GetCapabilities'}
2✔
86

87
            self.request = urlencode(data)
2✔
88

89
            self._invoke()
2✔
90

91
            if self.exceptionreport is None:
×
92
                self.updateSequence = self._exml.getroot().attrib.get('updateSequence')
×
93

94
                # ServiceIdentification
95
                val = self._exml.find(util.nspath_eval('ows200:ServiceIdentification', namespaces))
×
96
                if val is not None:
×
97
                    self.identification = ows.ServiceIdentification(val, self.owscommon.namespace)
×
98
                else:
99
                    self.identification = None
×
100
                # ServiceProvider
101
                val = self._exml.find(util.nspath_eval('ows200:ServiceProvider', namespaces))
×
102
                if val is not None:
×
103
                    self.provider = ows.ServiceProvider(val, self.owscommon.namespace)
×
104
                else:
105
                    self.provider = None
×
106
                # ServiceOperations metadata
107
                self.operations = []
×
108
                for elem in self._exml.findall(util.nspath_eval('ows200:OperationsMetadata/ows200:Operation', namespaces)):  # noqa
×
109
                    self.operations.append(ows.OperationsMetadata(elem, self.owscommon.namespace))
×
110
                self.constraints = {}
×
111
                for elem in self._exml.findall(util.nspath_eval('ows200:OperationsMetadata/ows200:Constraint', namespaces)):  # noqa
×
112
                    self.constraints[elem.attrib['name']] = ows.Constraint(elem, self.owscommon.namespace)
×
113
                self.parameters = {}
×
114
                for elem in self._exml.findall(util.nspath_eval('ows200:OperationsMetadata/ows200:Parameter', namespaces)):  # noqa
×
115
                    self.parameters[elem.attrib['name']] = ows.Parameter(elem, self.owscommon.namespace)
×
116

117
                # FilterCapabilities
118
                val = self._exml.find(util.nspath_eval('fes:Filter_Capabilities', namespaces))
×
119
                self.filters = fes2.FilterCapabilities(val)
×
120

121
    def getdomain(self, dname, dtype='parameter'):
2✔
122
        """
123

124
        Construct and process a GetDomain request
125

126
        Parameters
127
        ----------
128

129
        - dname: the value of the Parameter or Property to query
130
        - dtype: whether to query a parameter (parameter) or property (property)
131

132
        """
133

134
        # construct request
135
        dtypename = 'ParameterName'
×
136
        node0 = self._setrootelement('csw30:GetDomain')
×
137
        node0.set('service', self.service)
×
138
        node0.set('version', self.version)
×
139
        node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
140
        if dtype == 'property':
×
141
            dtypename = 'ValueReference'
×
142
        else:
143
            dtypename = 'ParameterName'
×
144

145
        etree.SubElement(node0, util.nspath_eval('csw30:%s' % dtypename, namespaces)).text = dname
×
146

147
        self.request = node0
×
148
        try:  # getdomain operation is optional on CSW3, return empty if failing
×
149
            self._invoke()
×
150

151
            if self.exceptionreport is None:
×
152
                self.results = {}
×
153

154
                val = self._exml.find(util.nspath_eval('csw30:DomainValues', namespaces)).attrib.get('type')
×
155
                self.results['type'] = util.testXMLValue(val, True)
×
156

157
                val = self._exml.find(util.nspath_eval('csw30:DomainValues/csw30:%s' % dtypename, namespaces))
×
158
                self.results[dtype] = util.testXMLValue(val)
×
159

160
                # get the list of values associated with the Domain
161
                self.results['values'] = []
×
162

163
                for f in self._exml.findall(util.nspath_eval('csw30:DomainValues/csw30:ListOfValues/csw30:Value', namespaces)):  # noqa
×
164
                    self.results['values'].append(util.testXMLValue(f))
×
165
        except Exception:
×
166
            self.results = {'values': []}
×
167

168
    def getrecordbyid(self, id=[], esn='full', outputschema=namespaces['csw30'], format=outputformat):
2✔
169
        """
170

171
        Construct and process a GetRecordById request
172

173
        Parameters
174
        ----------
175

176
        - id: the list of Ids
177
        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full')
178
        - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/3.0.0')
179
        - format: the outputFormat (default is 'application/xml')
180

181
        """
182

183
        # construct request
184
        data = {
×
185
            'service': self.service,
186
            'version': self.version,
187
            'request': 'GetRecordById',
188
            'outputFormat': format,
189
            'outputSchema': outputschema,
190
            'elementsetname': esn,
191
            'id': ','.join(id),
192
        }
193

194
        self.request = urlencode(data)
×
195

196
        self._invoke()
×
197

198
        if self.exceptionreport is None:
×
199
            self.results = {}
×
200
            self.records = OrderedDict()
×
201
            self._parserecords(outputschema, esn)
×
202

203
    def getrecords(self, constraints=[], sortby=None, typenames='csw30:Record', esn='summary',
2✔
204
                   outputschema=namespaces['csw30'], format=outputformat, startposition=0,
205
                   maxrecords=10, cql=None, xml=None, distributedsearch=False, hopcount=2,
206
                   federatedcatalogues=[]):
207
        """
208

209
        Construct and process a  GetRecords request
210

211
        Parameters
212
        ----------
213

214
        - constraints: the list of constraints (OgcExpression from owslib.fes2 module)
215
        - sortby: an OGC SortBy object (SortBy from owslib.fes2 module)
216
        - typenames: the typeNames to query against (default is csw30:Record)
217
        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary')
218
        - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/3.0.0')
219
        - format: the outputFormat (default is 'application/xml')
220
        - startposition: requests a slice of the result set, starting at this position (default is 0)
221
        - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10)
222
        - cql: common query language text.  Note this overrides bbox, qtype, keywords
223
        - xml: raw XML request.  Note this overrides all other options
224
        - distributedsearch: `bool` of whether to trigger distributed search
225
        - hopcount: number of message hops before search is terminated (default is 1)
226
        - federatedcatalogues: list of CSW 3 URLs
227

228
        """
229

230
        if xml is not None:
×
231
            if isinstance(xml, bytes):
×
232
                startswith_xml = xml.startswith(b'<')
×
233
            else:  # str
234
                startswith_xml = xml.startswith('<')
×
235

236
            if startswith_xml:
×
237
                self.request = etree.fromstring(xml)
×
238
                val = self.request.find(util.nspath_eval('csw30:Query/csw30:ElementSetName', namespaces))
×
239
                if val is not None:
×
240
                    esn = util.testXMLValue(val)
×
241
                val = self.request.attrib.get('outputSchema')
×
242
                if val is not None:
×
243
                    outputschema = util.testXMLValue(val, True)
×
244
            else:
245
                self.request = xml
×
246
        else:
247
            # construct request
248
            node0 = self._setrootelement('csw30:GetRecords')
×
249
            node0.set('outputSchema', outputschema)
×
250
            node0.set('outputFormat', format)
×
251
            node0.set('version', self.version)
×
252
            node0.set('service', self.service)
×
253
            if startposition > 0:
×
254
                node0.set('startPosition', str(startposition))
×
255
            node0.set('maxRecords', str(maxrecords))
×
256
            node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
257

258
            if distributedsearch:
×
259
                node00 = etree.SubElement(node0, util.nspath_eval('csw30:DistributedSearch', namespaces),
×
260
                                          hopCount=str(hopcount), clientId='owslib',
261
                                          distributedSearchId='owslib-request')
262

263
                if federatedcatalogues:
×
264
                    for fc in federatedcatalogues:
×
265
                        etree.SubElement(node00, util.nspath_eval('csw30:federatedCatalogues', namespaces),
×
266
                                         catalogueURL=fc)
267

268
            node1 = etree.SubElement(node0, util.nspath_eval('csw30:Query', namespaces))
×
269
            node1.set('typeNames', typenames)
×
270

271
            etree.SubElement(node1, util.nspath_eval('csw30:ElementSetName', namespaces)).text = esn
×
272

273
            if any([len(constraints) > 0, cql is not None]):
×
274
                node2 = etree.SubElement(node1, util.nspath_eval('csw30:Constraint', namespaces))
×
275
                node2.set('version', '1.1.0')
×
276
                flt = fes2.FilterRequest()
×
277
                if len(constraints) > 0:
×
278
                    node2.append(flt.setConstraintList(constraints))
×
279
                # Now add a CQL filter if passed in
280
                elif cql is not None:
×
281
                    etree.SubElement(node2, util.nspath_eval('csw30:CqlText', namespaces)).text = cql
×
282

283
            if sortby is not None and isinstance(sortby, fes2.SortBy):
×
284
                node1.append(sortby.toXML())
×
285

286
            self.request = node0
×
287

288
        self._invoke()
×
289

290
        if self.exceptionreport is None:
×
291
            self.results = {}
×
292

293
            # process search results attributes
294
            val = self._exml.find(
×
295
                util.nspath_eval('csw30:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched')
296
            self.results['matches'] = int(util.testXMLValue(val, True))
×
297
            val = self._exml.find(
×
298
                util.nspath_eval('csw30:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned')
299
            self.results['returned'] = int(util.testXMLValue(val, True))
×
300
            val = self._exml.find(util.nspath_eval('csw30:SearchResults', namespaces)).attrib.get('nextRecord')
×
301
            if val is not None:
×
302
                self.results['nextrecord'] = int(util.testXMLValue(val, True))
×
303
            else:
304
                warnings.warn("""CSW Server did not supply a nextRecord value (it is optional), so the client
×
305
                should page through the results in another way.""")
306
                # For more info, see:
307
                # https://github.com/geopython/OWSLib/issues/100
308
                self.results['nextrecord'] = None
×
309

310
            # process list of matching records
311
            self.records = OrderedDict()
×
312

313
            self._parserecords(outputschema, esn)
×
314

315
    def transaction(self, ttype=None, typename='csw30:Record', record=None, propertyname=None, propertyvalue=None,
2✔
316
                    bbox=None, keywords=[], cql=None, identifier=None):
317
        """
318

319
        Construct and process a Transaction request
320

321
        Parameters
322
        ----------
323

324
        - ttype: the type of transaction 'insert, 'update', 'delete'
325
        - typename: the typename to describe (default is 'csw30:Record')
326
        - record: the XML record to insert
327
        - propertyname: the RecordProperty/PropertyName to Filter against
328
        - propertyvalue: the RecordProperty Value to Filter against (for updates)
329
        - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy]
330
        - keywords: list of keywords
331
        - cql: common query language text.  Note this overrides bbox, qtype, keywords
332
        - identifier: record identifier.  Note this overrides bbox, qtype, keywords, cql
333

334
        """
335

336
        # construct request
337
        node0 = self._setrootelement('csw30:Transaction')
×
338
        node0.set('version', self.version)
×
339
        node0.set('service', self.service)
×
340
        node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
341

342
        validtransactions = ['insert', 'update', 'delete']
×
343

344
        if ttype not in validtransactions:  # invalid transaction
×
345
            raise RuntimeError('Invalid transaction \'%s\'.' % ttype)
×
346

347
        node1 = etree.SubElement(node0, util.nspath_eval('csw30:%s' % ttype.capitalize(), namespaces))
×
348

349
        if ttype != 'update':
×
350
            node1.set('typeName', typename)
×
351

352
        if ttype == 'insert':
×
353
            if record is None:
×
354
                raise RuntimeError('Nothing to insert.')
×
355
            node1.append(etree.fromstring(record))
×
356

357
        if ttype == 'update':
×
358
            if record is not None:
×
359
                node1.append(etree.fromstring(record))
×
360
            else:
361
                if propertyname is not None and propertyvalue is not None:
×
362
                    node2 = etree.SubElement(node1, util.nspath_eval('csw30:RecordProperty', namespaces))
×
363
                    etree.SubElement(node2, util.nspath_eval('csw30:Name', namespaces)).text = propertyname
×
364
                    etree.SubElement(node2, util.nspath_eval('csw30:Value', namespaces)).text = propertyvalue
×
365
                    self._setconstraint(node1, None, propertyname, keywords, bbox, cql, identifier)
×
366

367
        if ttype == 'delete':
×
368
            self._setconstraint(node1, None, propertyname, keywords, bbox, cql, identifier)
×
369

370
        self.request = node0
×
371

372
        self._invoke()
×
373
        self.results = {}
×
374

375
        if self.exceptionreport is None:
×
376
            self._parsetransactionsummary()
×
377
            self._parseinsertresult()
×
378

379
    def harvest(self, source, resourcetype, resourceformat=None, harvestinterval=None, responsehandler=None):
2✔
380
        """
381

382
        Construct and process a Harvest request
383

384
        Parameters
385
        ----------
386

387
        - source: a URI to harvest
388
        - resourcetype: namespace identifying the type of resource
389
        - resourceformat: MIME type of the resource
390
        - harvestinterval: frequency of harvesting, in ISO8601
391
        - responsehandler: endpoint that CSW should responsd to with response
392

393
        """
394

395
        # construct request
396
        node0 = self._setrootelement('csw30:Harvest')
×
397
        node0.set('version', self.version)
×
398
        node0.set('service', self.service)
×
399
        node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
400
        etree.SubElement(node0, util.nspath_eval('csw30:Source', namespaces)).text = source
×
401
        etree.SubElement(node0, util.nspath_eval('csw30:ResourceType', namespaces)).text = resourcetype
×
402
        if resourceformat is not None:
×
403
            etree.SubElement(node0, util.nspath_eval('csw30:ResourceFormat', namespaces)).text = resourceformat
×
404
        if harvestinterval is not None:
×
405
            etree.SubElement(node0, util.nspath_eval('csw30:HarvestInterval', namespaces)).text = harvestinterval
×
406
        if responsehandler is not None:
×
407
            etree.SubElement(node0, util.nspath_eval('csw30:ResponseHandler', namespaces)).text = responsehandler
×
408

409
        self.request = node0
×
410

411
        self._invoke()
×
412
        self.results = {}
×
413

414
        if self.exceptionreport is None:
×
415
            val = self._exml.find(util.nspath_eval('csw30:Acknowledgement', namespaces))
×
416
            if util.testXMLValue(val) is not None:
×
417
                ts = val.attrib.get('timeStamp')
×
418
                self.timestamp = util.testXMLValue(ts, True)
×
419
                id = val.find(util.nspath_eval('csw30:RequestId', namespaces))
×
420
                self.id = util.testXMLValue(id)
×
421
            else:
422
                self._parsetransactionsummary()
×
423
                self._parseinsertresult()
×
424

425
    def get_operation_by_name(self, name):
2✔
426
        """Return a named operation"""
427
        for item in self.operations:
×
428
            if item.name.lower() == name.lower():
×
429
                return item
×
430
        raise KeyError("No operation named %s" % name)
×
431

432
    def getService_urls(self, service_string=None):
2✔
433
        """
434

435
        Return easily identifiable URLs for all service types
436

437
        Parameters
438
        ----------
439

440
        - service_string: a URI to lookup
441

442
        """
443

444
        urls = []
×
445
        for key, rec in list(self.records.items()):
×
446
            # create a generator object, and iterate through it until the match is found
447
            # if not found, gets the default value (here "none")
448
            url = next((d['url'] for d in rec.references if d['scheme'] == service_string), None)
×
449
            if url is not None:
×
450
                urls.append(url)
×
451
        return urls
×
452

453
    def _parseinsertresult(self):
2✔
454
        self.results['insertresults'] = []
×
455
        for i in self._exml.findall('.//' + util.nspath_eval('csw30:InsertResult', namespaces)):
×
456
            for j in i.findall(util.nspath_eval('csw30:BriefRecord/dc:identifier', namespaces)):
×
457
                self.results['insertresults'].append(util.testXMLValue(j))
×
458

459
    def _parserecords(self, outputschema, esn):
2✔
460
        if outputschema == namespaces['gmd']:  # iso 19139
×
461
            for i in self._exml.findall('.//' + util.nspath_eval('gmd:MD_Metadata', namespaces)) or \
×
462
                    self._exml.findall('.//' + util.nspath_eval('gmi:MI_Metadata', namespaces)):
463
                val = i.find(util.nspath_eval('gmd:fileIdentifier/gco:CharacterString', namespaces))
×
464
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
465
                self.records[identifier] = MD_Metadata(i)
×
466
            for i in self._exml.findall('.//' + util.nspath_eval('gfc:FC_FeatureCatalogue', namespaces)):
×
467
                identifier = self._setidentifierkey(util.testXMLValue(i.attrib['uuid'], attrib=True))
×
468
                self.records[identifier] = FC_FeatureCatalogue(i)
×
469
        elif outputschema == namespaces['fgdc']:  # fgdc csdgm
×
470
            for i in self._exml.findall('.//metadata'):
×
471
                val = i.find('idinfo/datasetid')
×
472
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
473
                self.records[identifier] = Metadata(i)
×
474
        elif outputschema == namespaces['dif']:  # nasa dif
×
475
            for i in self._exml.findall('.//' + util.nspath_eval('dif:DIF', namespaces)):
×
476
                val = i.find(util.nspath_eval('dif:Entry_ID', namespaces))
×
477
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
478
                self.records[identifier] = DIF(i)
×
479
        elif outputschema == namespaces['gm03']:  # GM03
×
480
            for i in self._exml.findall('.//' + util.nspath_eval('gm03:TRANSFER', namespaces)):
×
481
                val = i.find(util.nspath_eval('gm03:fileIdentifier', namespaces))
×
482
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
483
                self.records[identifier] = GM03(i)
×
484
        elif MD_Metadata3.handles(outputschema):  # ISO 19115 Part 3 XML
×
485
            for elem, id in MD_Metadata3.find_ids(self._exml):
×
486
                self.records[self._setidentifierkey(id)] = MD_Metadata3(elem)
×
487
            for i in self._exml.findall('.//' + util.nspath_eval('gfc:FC_FeatureCatalogue', namespaces)):
×
488
                identifier = self._setidentifierkey(util.testXMLValue(i.attrib['uuid'], attrib=True))
×
489
                self.records[identifier] = FC_FeatureCatalogue3(i)
×
490
        else:  # process default
491
            for i in self._exml.findall('.//' + util.nspath_eval('csw30:%s' % self._setesnel(esn), namespaces)):
×
492
                val = i.find(util.nspath_eval('dc:identifier', namespaces))
×
493
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
494
                self.records[identifier] = Csw30Record(i)
×
495

496
    def _parsetransactionsummary(self):
2✔
497
        val = self._exml.find(util.nspath_eval('csw30:TransactionResponse/csw30:TransactionSummary', namespaces))
×
498
        if val is not None:
×
499
            rid = val.attrib.get('requestId')
×
500
            self.results['requestid'] = util.testXMLValue(rid, True)
×
501
            ts = val.find(util.nspath_eval('csw30:totalInserted', namespaces))
×
502
            self.results['inserted'] = int(util.testXMLValue(ts))
×
503
            ts = val.find(util.nspath_eval('csw30:totalUpdated', namespaces))
×
504
            self.results['updated'] = int(util.testXMLValue(ts))
×
505
            ts = val.find(util.nspath_eval('csw30:totalDeleted', namespaces))
×
506
            self.results['deleted'] = int(util.testXMLValue(ts))
×
507

508
    def _setesnel(self, esn):
2✔
509
        """ Set the element name to parse depending on the ElementSetName requested """
510
        el = 'Record'
×
511
        if esn == 'brief':
×
512
            el = 'BriefRecord'
×
513
        if esn == 'summary':
×
514
            el = 'SummaryRecord'
×
515
        return el
×
516

517
    def _setidentifierkey(self, el):
2✔
518
        if el is None:
×
519
            return 'owslib_random_%i' % random.randint(1, 65536)
×
520
        else:
521
            return el
×
522

523
    def _setrootelement(self, el):
2✔
524
        return etree.Element(util.nspath_eval(el, namespaces), nsmap=namespaces)
×
525

526
    def _setconstraint(self, parent, qtype=None, propertyname='csw30:AnyText', keywords=[], bbox=None, cql=None,
2✔
527
                       identifier=None):
528
        if keywords or bbox is not None or qtype is not None or cql is not None or identifier is not None:
×
529
            node0 = etree.SubElement(parent, util.nspath_eval('csw30:Constraint', namespaces))
×
530
            node0.set('version', '1.1.0')
×
531

532
            if identifier is not None:  # set identifier filter, overrides all other parameters
×
533
                flt = fes2.FilterRequest()
×
534
                node0.append(flt.set(identifier=identifier))
×
535
            elif cql is not None:  # send raw CQL query
×
536
                # CQL passed, overrides all other parameters
537
                node1 = etree.SubElement(node0, util.nspath_eval('csw30:CqlText', namespaces))
×
538
                node1.text = cql
×
539
            else:  # construct a Filter request
540
                flt = fes2.FilterRequest()
×
541
                node0.append(flt.set(qtype=qtype, keywords=keywords, propertyname=propertyname, bbox=bbox))
×
542

543
    def _invoke(self):
2✔
544
        # do HTTP request
545

546
        request_url = self.url
2✔
547

548
        # Get correct URL based on Operation list.
549

550
        # If skip_caps=True, then self.operations has not been set, so use
551
        # default URL.
552
        if hasattr(self, 'operations'):
2✔
553
            try:
×
554
                op = self.get_operation_by_name('getrecords')
×
555
                if isinstance(self.request, str):  # GET KVP
×
556
                    get_verbs = [x for x in op.methods if x.get('type').lower() == 'get']
×
557
                    request_url = get_verbs[0].get('url')
×
558
                else:
559
                    post_verbs = [x for x in op.methods if x.get('type').lower() == 'post']
×
560
                    if len(post_verbs) > 1:
×
561
                        # Filter by constraints.  We must match a PostEncoding of "XML"
562
                        found_xml = False
×
563
                        for pv in post_verbs:
×
564
                            for const in pv.get('constraints'):
×
565
                                if const.name.lower() == 'postencoding':
×
566
                                    values = [v.lower() for v in const.values]
×
567
                                    if 'xml' in values:
×
568
                                        request_url = pv.get('url')
×
569
                                        found_xml = True
×
570
                                        break
×
571
                        if not found_xml:  # Well, just use the first one.
×
572
                            request_url = post_verbs[0].get('url')
×
573
                    elif len(post_verbs) == 1:
×
574
                        request_url = post_verbs[0].get('url')
×
575
            except Exception:  # no such luck, just go with request_url
×
576
                pass
×
577

578
        if isinstance(self.request, str):  # GET KVP
2✔
579
            self.request = '%s%s' % (bind_url(request_url), self.request)
2✔
580
            headers_ = {'Accept': outputformat}
2✔
581
            if self.headers:
2✔
582
                headers_.update(self.headers)
2✔
583
            self.response = openURL(
2✔
584
                self.request, None, 'Get', timeout=self.timeout, auth=self.auth, headers=headers_
585
            ).read()
586
        else:
587
            self.request = cleanup_namespaces(self.request)
×
588
            # Add any namespaces used in the "typeNames" attribute of the
589
            # csw30:Query element to the query's xml namespaces.
590
            for query in self.request.findall(util.nspath_eval('csw30:Query', namespaces)):
×
591
                ns = query.get("typeNames", None)
×
592
                if ns is not None:
×
593
                    # Pull out "gmd" from something like "gmd:MD_Metadata" from the list
594
                    # of typenames
595
                    ns_keys = [x.split(':')[0] for x in ns.split(' ')]
×
596
                    self.request = add_namespaces(self.request, ns_keys)
×
597
            self.request = add_namespaces(self.request, 'fes')
×
598

599
            self.request = util.element_to_string(self.request, encoding='utf-8')
×
600

601
            self.response = http_post(request_url, self.request, self.lang, self.timeout,
×
602
                                      auth=self.auth, headers=self.headers).content
603

604
        # parse result see if it's XML
605
        self._exml = etree.parse(BytesIO(self.response))
×
606

607
        # it's XML.  Attempt to decipher whether the XML response is CSW-ish """
608
        valid_xpaths = [
×
609
            util.nspath_eval('ows200:ExceptionReport', namespaces),
610
            util.nspath_eval('csw30:Capabilities', namespaces),
611
            util.nspath_eval('csw30:DescribeRecordResponse', namespaces),
612
            util.nspath_eval('csw30:GetDomainResponse', namespaces),
613
            util.nspath_eval('csw30:GetRecordsResponse', namespaces),
614
            util.nspath_eval('csw30:GetRecordByIdResponse', namespaces),
615
            util.nspath_eval('csw30:HarvestResponse', namespaces),
616
            util.nspath_eval('csw30:TransactionResponse', namespaces),
617
            util.nspath_eval('csw30:Record', namespaces)
618
        ]
619

620
        if self._exml.getroot().tag not in valid_xpaths:
×
621
            raise RuntimeError('Document is XML, but not CSW-ish')
×
622

623
        # check if it's an OGC Exception
624
        val = self._exml.find(util.nspath_eval('ows200:Exception', namespaces))
×
625
        if val is not None:
×
626
            raise ows.ExceptionReport(self._exml, self.owscommon.namespace)
×
627
        else:
628
            self.exceptionreport = None
×
629

630

631
class Csw30Record(object):
2✔
632
    """ Process csw30:Record, csw30:BriefRecord, csw30:SummaryRecord """
633
    def __init__(self, record):
2✔
634

635
        if hasattr(record, 'getroot'):  # standalone document
×
636
            self.xml = etree.tostring(record.getroot())
×
637
        else:  # part of a larger document
638
            self.xml = etree.tostring(record)
×
639

640
        # check to see if Dublin Core record comes from
641
        # rdf:RDF/rdf:Description container
642
        # (child content model is identical)
643
        self.rdf = False
×
644
        rdf = record.find(util.nspath_eval('rdf:Description', namespaces))
×
645
        if rdf is not None:
×
646
            self.rdf = True
×
647
            record = rdf
×
648

649
        # some CSWs return records with multiple identifiers based on
650
        # different schemes.  Use the first dc:identifier value to set
651
        # self.identifier, and set self.identifiers as a list of dicts
652
        val = record.find(util.nspath_eval('dc:identifier', namespaces))
×
653
        self.identifier = util.testXMLValue(val)
×
654

655
        self.identifiers = []
×
656
        for i in record.findall(util.nspath_eval('dc:identifier', namespaces)):
×
657
            d = {}
×
658
            d['scheme'] = i.attrib.get('scheme')
×
659
            d['identifier'] = i.text
×
660
            self.identifiers.append(d)
×
661

662
        val = record.find(util.nspath_eval('dc:type', namespaces))
×
663
        self.type = util.testXMLValue(val)
×
664

665
        val = record.find(util.nspath_eval('dc:title', namespaces))
×
666
        self.title = util.testXMLValue(val)
×
667

668
        val = record.find(util.nspath_eval('dct:alternative', namespaces))
×
669
        self.alternative = util.testXMLValue(val)
×
670

671
        val = record.find(util.nspath_eval('dct:isPartOf', namespaces))
×
672
        self.ispartof = util.testXMLValue(val)
×
673

674
        val = record.find(util.nspath_eval('dct:abstract', namespaces))
×
675
        self.abstract = util.testXMLValue(val)
×
676

677
        val = record.find(util.nspath_eval('dc:date', namespaces))
×
678
        self.date = util.testXMLValue(val)
×
679

680
        val = record.find(util.nspath_eval('dct:created', namespaces))
×
681
        self.created = util.testXMLValue(val)
×
682

683
        val = record.find(util.nspath_eval('dct:issued', namespaces))
×
684
        self.issued = util.testXMLValue(val)
×
685

686
        val = record.find(util.nspath_eval('dc:relation', namespaces))
×
687
        self.relation = util.testXMLValue(val)
×
688

689
        val = record.find(util.nspath_eval('dct:temporal', namespaces))
×
690
        self.temporal = util.testXMLValue(val)
×
691

692
        self.uris = []  # list of dicts
×
693
        for i in record.findall(util.nspath_eval('dc:URI', namespaces)):
×
694
            uri = {}
×
695
            uri['protocol'] = util.testXMLValue(i.attrib.get('protocol'), True)
×
696
            uri['name'] = util.testXMLValue(i.attrib.get('name'), True)
×
697
            uri['description'] = util.testXMLValue(i.attrib.get('description'), True)
×
698
            uri['url'] = util.testXMLValue(i)
×
699

700
            self.uris.append(uri)
×
701

702
        self.references = []  # list of dicts
×
703
        for i in record.findall(util.nspath_eval('dct:references', namespaces)):
×
704
            ref = {}
×
705
            ref['scheme'] = util.testXMLValue(i.attrib.get('scheme'), True)
×
706
            ref['url'] = util.testXMLValue(i)
×
707

708
            self.references.append(ref)
×
709

710
        val = record.find(util.nspath_eval('dct:modified', namespaces))
×
711
        self.modified = util.testXMLValue(val)
×
712

713
        val = record.find(util.nspath_eval('dc:creator', namespaces))
×
714
        self.creator = util.testXMLValue(val)
×
715

716
        val = record.find(util.nspath_eval('dc:publisher', namespaces))
×
717
        self.publisher = util.testXMLValue(val)
×
718

719
        val = record.find(util.nspath_eval('dc:coverage', namespaces))
×
720
        self.coverage = util.testXMLValue(val)
×
721

722
        val = record.find(util.nspath_eval('dc:contributor', namespaces))
×
723
        self.contributor = util.testXMLValue(val)
×
724

725
        val = record.find(util.nspath_eval('dc:language', namespaces))
×
726
        self.language = util.testXMLValue(val)
×
727

728
        val = record.find(util.nspath_eval('dc:source', namespaces))
×
729
        self.source = util.testXMLValue(val)
×
730

731
        val = record.find(util.nspath_eval('dct:rightsHolder', namespaces))
×
732
        self.rightsholder = util.testXMLValue(val)
×
733

734
        val = record.find(util.nspath_eval('dct:accessRights', namespaces))
×
735
        self.accessrights = util.testXMLValue(val)
×
736

737
        val = record.find(util.nspath_eval('dct:license', namespaces))
×
738
        self.license = util.testXMLValue(val)
×
739

740
        val = record.find(util.nspath_eval('dc:format', namespaces))
×
741
        self.format = util.testXMLValue(val)
×
742

743
        self.subjects = []
×
744
        for i in record.findall(util.nspath_eval('dc:subject', namespaces)):
×
745
            self.subjects.append(util.testXMLValue(i))
×
746

747
        self.rights = []
×
748
        for i in record.findall(util.nspath_eval('dc:rights', namespaces)):
×
749
            self.rights.append(util.testXMLValue(i))
×
750

751
        val = record.find(util.nspath_eval('dct:spatial', namespaces))
×
752
        self.spatial = util.testXMLValue(val)
×
753

754
        val = record.find(util.nspath_eval('ows200:BoundingBox', namespaces))
×
755
        if val is not None:
×
756
            self.bbox = ows.BoundingBox(val, namespaces['ows'])
×
757
        else:
758
            self.bbox = None
×
759

760
        val = record.find(util.nspath_eval('ows200:WGS84BoundingBox', namespaces))
×
761
        if val is not None:
×
762
            self.bbox_wgs84 = ows.WGS84BoundingBox(val, namespaces['ows'])
×
763
        else:
764
            self.bbox_wgs84 = None
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc