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

geopython / OWSLib / 3663701074

pending completion
3663701074

Pull #851

github

GitHub
Merge 6cd54d613 into 13b1443f7
Pull Request #851: Adding Python 3.10 in CI

7461 of 12701 relevant lines covered (58.74%)

0.59 hits per line

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

49.68
/owslib/catalogue/csw2.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 2.0.2 request and response processor """
1✔
11

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

18
from owslib.etree import etree
1✔
19
from owslib import fes
1✔
20
from owslib import util
1✔
21
from owslib import ows
1✔
22
from owslib.iso import MD_Metadata, FC_FeatureCatalogue
1✔
23
from owslib.fgdc import Metadata
1✔
24
from owslib.dif import DIF
1✔
25
from owslib.gm03 import GM03
1✔
26
from owslib.namespaces import Namespaces
1✔
27
from owslib.util import cleanup_namespaces, bind_url, add_namespaces, OrderedDict, Authentication, openURL, http_post
1✔
28

29
# default variables
30
outputformat = 'application/xml'
1✔
31

32

33
def get_namespaces():
1✔
34
    n = Namespaces()
1✔
35
    return n.get_namespaces()
1✔
36

37

38
namespaces = get_namespaces()
1✔
39
schema = 'http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd'
1✔
40
schema_location = '%s %s' % (namespaces['csw'], schema)
1✔
41

42

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

49
        Construct and process a GetCapabilities request
50

51
        Parameters
52
        ----------
53

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

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

80
        if not skip_caps:  # process GetCapabilities
1✔
81
            # construct request
82

83
            data = {'service': self.service, 'version': self.version, 'request': 'GetCapabilities'}
1✔
84

85
            self.request = urlencode(data)
1✔
86

87
            self._invoke()
1✔
88

89
            if self.exceptionreport is None:
1✔
90
                self.updateSequence = self._exml.getroot().attrib.get('updateSequence')
1✔
91

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

115
                # FilterCapabilities
116
                val = self._exml.find(util.nspath_eval('ogc:Filter_Capabilities', namespaces))
1✔
117
                self.filters = fes.FilterCapabilities(val)
1✔
118

119
    def describerecord(self, typename='csw:Record', format=outputformat):
1✔
120
        """
121

122
        Construct and process DescribeRecord request
123

124
        Parameters
125
        ----------
126

127
        - typename: the typename to describe (default is 'csw:Record')
128
        - format: the outputFormat (default is 'application/xml')
129

130
        """
131

132
        # construct request
133
        node0 = self._setrootelement('csw:DescribeRecord')
×
134
        node0.set('service', self.service)
×
135
        node0.set('version', self.version)
×
136
        node0.set('outputFormat', format)
×
137
        node0.set('schemaLanguage', namespaces['xs2'])
×
138
        node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
139
        etree.SubElement(node0, util.nspath_eval('csw:TypeName', namespaces)).text = typename
×
140

141
        self.request = node0
×
142

143
        self._invoke()
×
144

145
        # parse result
146
        # TODO: process the XML Schema (you're on your own for now with self.response)
147

148
    def getdomain(self, dname, dtype='parameter'):
1✔
149
        """
150

151
        Construct and process a GetDomain request
152

153
        Parameters
154
        ----------
155

156
        - dname: the value of the Parameter or Property to query
157
        - dtype: whether to query a parameter (parameter) or property (property)
158

159
        """
160

161
        # construct request
162
        dtypename = 'ParameterName'
×
163
        node0 = self._setrootelement('csw:GetDomain')
×
164
        node0.set('service', self.service)
×
165
        node0.set('version', self.version)
×
166
        node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
167
        if dtype == 'property':
×
168
            dtypename = 'PropertyName'
×
169
        etree.SubElement(node0, util.nspath_eval('csw:%s' % dtypename, namespaces)).text = dname
×
170

171
        self.request = node0
×
172

173
        self._invoke()
×
174

175
        if self.exceptionreport is None:
×
176
            self.results = {}
×
177

178
            val = self._exml.find(util.nspath_eval('csw:DomainValues', namespaces)).attrib.get('type')
×
179
            self.results['type'] = util.testXMLValue(val, True)
×
180

181
            val = self._exml.find(util.nspath_eval('csw:DomainValues/csw:%s' % dtypename, namespaces))
×
182
            self.results[dtype] = util.testXMLValue(val)
×
183

184
            # get the list of values associated with the Domain
185
            self.results['values'] = []
×
186

187
            for f in self._exml.findall(util.nspath_eval('csw:DomainValues/csw:ListOfValues/csw:Value', namespaces)):
×
188
                self.results['values'].append(util.testXMLValue(f))
×
189

190
    def getrecords(self, qtype=None, keywords=[], typenames='csw:Record', propertyname='csw:AnyText', bbox=None,
1✔
191
                   esn='summary', sortby=None, outputschema=namespaces['csw'], format=outputformat, startposition=0,
192
                   maxrecords=10, cql=None, xml=None, resulttype='results'):
193
        """
194

195
        Construct and process a  GetRecords request
196

197
        Parameters
198
        ----------
199

200
        - qtype: type of resource to query (i.e. service, dataset)
201
        - keywords: list of keywords
202
        - typenames: the typeNames to query against (default is csw:Record)
203
        - propertyname: the PropertyName to Filter against
204
        - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy]
205
        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary')
206
        - sortby: property to sort results on
207
        - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2')
208
        - format: the outputFormat (default is 'application/xml')
209
        - startposition: requests a slice of the result set, starting at this position (default is 0)
210
        - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10)
211
        - cql: common query language text.  Note this overrides bbox, qtype, keywords
212
        - xml: raw XML request.  Note this overrides all other options
213
        - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results')
214

215
        """
216

217
        warnings.warn("""Please use the updated 'getrecords2' method instead of 'getrecords'.
×
218
        The 'getrecords' method will be upgraded to use the 'getrecords2' parameters
219
        in a future version of OWSLib.""")
220

221
        if xml is not None:
×
222
            self.request = etree.fromstring(xml)
×
223
            val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces))
×
224
            if val is not None:
×
225
                esn = util.testXMLValue(val)
×
226
        else:
227
            # construct request
228
            node0 = self._setrootelement('csw:GetRecords')
×
229
            if etree.__name__ != 'lxml.etree':  # apply nsmap manually
×
230
                node0.set('xmlns:ows', namespaces['ows'])
×
231
                node0.set('xmlns:gmd', namespaces['gmd'])
×
232
                node0.set('xmlns:dif', namespaces['dif'])
×
233
                node0.set('xmlns:fgdc', namespaces['fgdc'])
×
234
            node0.set('outputSchema', outputschema)
×
235
            node0.set('outputFormat', format)
×
236
            node0.set('version', self.version)
×
237
            node0.set('resultType', resulttype)
×
238
            node0.set('service', self.service)
×
239
            if startposition > 0:
×
240
                node0.set('startPosition', str(startposition))
×
241
            node0.set('maxRecords', str(maxrecords))
×
242
            node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
243

244
            node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces))
×
245
            node1.set('typeNames', typenames)
×
246

247
            etree.SubElement(node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn
×
248

249
            self._setconstraint(node1, qtype, propertyname, keywords, bbox, cql, None)
×
250

251
            if sortby is not None:
×
252
                fes.setsortby(node1, sortby)
×
253

254
            self.request = node0
×
255

256
        self._invoke()
×
257

258
        if self.exceptionreport is None:
×
259
            self.results = {}
×
260

261
            # process search results attributes
262
            val = self._exml.find(
×
263
                util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched')
264
            self.results['matches'] = int(util.testXMLValue(val, True))
×
265
            val = self._exml.find(
×
266
                util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned')
267
            self.results['returned'] = int(util.testXMLValue(val, True))
×
268
            val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord')
×
269
            self.results['nextrecord'] = int(util.testXMLValue(val, True))
×
270

271
            # process list of matching records
272
            self.records = OrderedDict()
×
273

274
            self._parserecords(outputschema, esn)
×
275

276
    def getrecordbyid(self, id=[], esn='full', outputschema=namespaces['csw'], format=outputformat):
1✔
277
        """
278

279
        Construct and process a GetRecordById request
280

281
        Parameters
282
        ----------
283

284
        - id: the list of Ids
285
        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'full')
286
        - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2')
287
        - format: the outputFormat (default is 'application/xml')
288

289
        """
290

291
        # construct request
292
        data = {
×
293
            'service': self.service,
294
            'version': self.version,
295
            'request': 'GetRecordById',
296
            'outputFormat': format,
297
            'outputSchema': outputschema,
298
            'elementsetname': esn,
299
            'id': ','.join(id),
300
        }
301

302
        self.request = urlencode(data)
×
303

304
        self._invoke()
×
305

306
        if self.exceptionreport is None:
×
307
            self.results = {}
×
308
            self.records = OrderedDict()
×
309
            self._parserecords(outputschema, esn)
×
310

311
    def getrecords2(self, constraints=[], sortby=None, typenames='csw:Record', esn='summary',
1✔
312
                    outputschema=namespaces['csw'], format=outputformat, startposition=0,
313
                    maxrecords=10, cql=None, xml=None, resulttype='results',
314
                    distributedsearch=False, hopcount=1):
315
        """
316

317
        Construct and process a  GetRecords request
318

319
        Parameters
320
        ----------
321

322
        - constraints: the list of constraints (OgcExpression from owslib.fes module)
323
        - sortby: an OGC SortBy object (SortBy from owslib.fes module)
324
        - typenames: the typeNames to query against (default is csw:Record)
325
        - esn: the ElementSetName 'full', 'brief' or 'summary' (default is 'summary')
326
        - outputschema: the outputSchema (default is 'http://www.opengis.net/cat/csw/2.0.2')
327
        - format: the outputFormat (default is 'application/xml')
328
        - startposition: requests a slice of the result set, starting at this position (default is 0)
329
        - maxrecords: the maximum number of records to return. No records are returned if 0 (default is 10)
330
        - cql: common query language text.  Note this overrides bbox, qtype, keywords
331
        - xml: raw XML request.  Note this overrides all other options
332
        - resulttype: the resultType 'hits', 'results', 'validate' (default is 'results')
333
        - distributedsearch: `bool` of whether to trigger distributed search
334
        - hopcount: number of message hops before search is terminated (default is 1)
335

336
        """
337

338
        if xml is not None:
1✔
339
            if isinstance(xml, bytes):
×
340
                startswith_xml = xml.startswith(b'<')
×
341
            else:  # str
342
                startswith_xml = xml.startswith('<')
×
343

344
            if startswith_xml:
×
345
                self.request = etree.fromstring(xml)
×
346
                val = self.request.find(util.nspath_eval('csw:Query/csw:ElementSetName', namespaces))
×
347
                if val is not None:
×
348
                    esn = util.testXMLValue(val)
×
349
                val = self.request.attrib.get('outputSchema')
×
350
                if val is not None:
×
351
                    outputschema = util.testXMLValue(val, True)
×
352
            else:
353
                self.request = xml
×
354
        else:
355
            # construct request
356
            node0 = self._setrootelement('csw:GetRecords')
1✔
357
            if etree.__name__ != 'lxml.etree':  # apply nsmap manually
1✔
358
                node0.set('xmlns:ows', namespaces['ows'])
1✔
359
                node0.set('xmlns:gmd', namespaces['gmd'])
1✔
360
                node0.set('xmlns:dif', namespaces['dif'])
1✔
361
                node0.set('xmlns:fgdc', namespaces['fgdc'])
1✔
362
            node0.set('outputSchema', outputschema)
1✔
363
            node0.set('outputFormat', format)
1✔
364
            node0.set('version', self.version)
1✔
365
            node0.set('service', self.service)
1✔
366
            node0.set('resultType', resulttype)
1✔
367
            if startposition > 0:
1✔
368
                node0.set('startPosition', str(startposition))
×
369
            node0.set('maxRecords', str(maxrecords))
1✔
370
            node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
1✔
371

372
            if distributedsearch:
1✔
373
                etree.SubElement(node0, util.nspath_eval('csw:DistributedSearch', namespaces), hopCount=str(hopcount))
×
374

375
            node1 = etree.SubElement(node0, util.nspath_eval('csw:Query', namespaces))
1✔
376
            node1.set('typeNames', typenames)
1✔
377

378
            etree.SubElement(node1, util.nspath_eval('csw:ElementSetName', namespaces)).text = esn
1✔
379

380
            if any([len(constraints) > 0, cql is not None]):
1✔
381
                node2 = etree.SubElement(node1, util.nspath_eval('csw:Constraint', namespaces))
×
382
                node2.set('version', '1.1.0')
×
383
                flt = fes.FilterRequest()
×
384
                if len(constraints) > 0:
×
385
                    node2.append(flt.setConstraintList(constraints))
×
386
                # Now add a CQL filter if passed in
387
                elif cql is not None:
×
388
                    etree.SubElement(node2, util.nspath_eval('csw:CqlText', namespaces)).text = cql
×
389

390
            if sortby is not None and isinstance(sortby, fes.SortBy):
1✔
391
                node1.append(sortby.toXML())
×
392

393
            self.request = node0
1✔
394

395
        self._invoke()
1✔
396

397
        if self.exceptionreport is None:
1✔
398
            self.results = {}
1✔
399

400
            # process search results attributes
401
            val = self._exml.find(
1✔
402
                util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsMatched')
403
            self.results['matches'] = int(util.testXMLValue(val, True))
1✔
404
            val = self._exml.find(
1✔
405
                util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('numberOfRecordsReturned')
406
            self.results['returned'] = int(util.testXMLValue(val, True))
1✔
407
            val = self._exml.find(util.nspath_eval('csw:SearchResults', namespaces)).attrib.get('nextRecord')
1✔
408
            if val is not None:
1✔
409
                self.results['nextrecord'] = int(util.testXMLValue(val, True))
1✔
410
            else:
411
                warnings.warn("""CSW Server did not supply a nextRecord value (it is optional), so the client
×
412
                should page through the results in another way.""")
413
                # For more info, see:
414
                # https://github.com/geopython/OWSLib/issues/100
415
                self.results['nextrecord'] = None
×
416

417
            # process list of matching records
418
            self.records = OrderedDict()
1✔
419

420
            self._parserecords(outputschema, esn)
1✔
421

422
    def transaction(self, ttype=None, typename='csw:Record', record=None, propertyname=None, propertyvalue=None,
1✔
423
                    bbox=None, keywords=[], cql=None, identifier=None):
424
        """
425

426
        Construct and process a Transaction request
427

428
        Parameters
429
        ----------
430

431
        - ttype: the type of transaction 'insert, 'update', 'delete'
432
        - typename: the typename to describe (default is 'csw:Record')
433
        - record: the XML record to insert
434
        - propertyname: the RecordProperty/PropertyName to Filter against
435
        - propertyvalue: the RecordProperty Value to Filter against (for updates)
436
        - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy]
437
        - keywords: list of keywords
438
        - cql: common query language text.  Note this overrides bbox, qtype, keywords
439
        - identifier: record identifier.  Note this overrides bbox, qtype, keywords, cql
440

441
        """
442

443
        # construct request
444
        node0 = self._setrootelement('csw:Transaction')
×
445
        node0.set('version', self.version)
×
446
        node0.set('service', self.service)
×
447
        node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
448

449
        validtransactions = ['insert', 'update', 'delete']
×
450

451
        if ttype not in validtransactions:  # invalid transaction
×
452
            raise RuntimeError('Invalid transaction \'%s\'.' % ttype)
×
453

454
        node1 = etree.SubElement(node0, util.nspath_eval('csw:%s' % ttype.capitalize(), namespaces))
×
455

456
        if ttype != 'update':
×
457
            node1.set('typeName', typename)
×
458

459
        if ttype == 'insert':
×
460
            if record is None:
×
461
                raise RuntimeError('Nothing to insert.')
×
462
            node1.append(etree.fromstring(record))
×
463

464
        if ttype == 'update':
×
465
            if record is not None:
×
466
                node1.append(etree.fromstring(record))
×
467
            else:
468
                if propertyname is not None and propertyvalue is not None:
×
469
                    node2 = etree.SubElement(node1, util.nspath_eval('csw:RecordProperty', namespaces))
×
470
                    etree.SubElement(node2, util.nspath_eval('csw:Name', namespaces)).text = propertyname
×
471
                    etree.SubElement(node2, util.nspath_eval('csw:Value', namespaces)).text = propertyvalue
×
472
                    self._setconstraint(node1, None, propertyname, keywords, bbox, cql, identifier)
×
473

474
        if ttype == 'delete':
×
475
            self._setconstraint(node1, None, propertyname, keywords, bbox, cql, identifier)
×
476

477
        self.request = node0
×
478

479
        self._invoke()
×
480
        self.results = {}
×
481

482
        if self.exceptionreport is None:
×
483
            self._parsetransactionsummary()
×
484
            self._parseinsertresult()
×
485

486
    def harvest(self, source, resourcetype, resourceformat=None, harvestinterval=None, responsehandler=None):
1✔
487
        """
488

489
        Construct and process a Harvest request
490

491
        Parameters
492
        ----------
493

494
        - source: a URI to harvest
495
        - resourcetype: namespace identifying the type of resource
496
        - resourceformat: MIME type of the resource
497
        - harvestinterval: frequency of harvesting, in ISO8601
498
        - responsehandler: endpoint that CSW should responsd to with response
499

500
        """
501

502
        # construct request
503
        node0 = self._setrootelement('csw:Harvest')
×
504
        node0.set('version', self.version)
×
505
        node0.set('service', self.service)
×
506
        node0.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
507
        etree.SubElement(node0, util.nspath_eval('csw:Source', namespaces)).text = source
×
508
        etree.SubElement(node0, util.nspath_eval('csw:ResourceType', namespaces)).text = resourcetype
×
509
        if resourceformat is not None:
×
510
            etree.SubElement(node0, util.nspath_eval('csw:ResourceFormat', namespaces)).text = resourceformat
×
511
        if harvestinterval is not None:
×
512
            etree.SubElement(node0, util.nspath_eval('csw:HarvestInterval', namespaces)).text = harvestinterval
×
513
        if responsehandler is not None:
×
514
            etree.SubElement(node0, util.nspath_eval('csw:ResponseHandler', namespaces)).text = responsehandler
×
515

516
        self.request = node0
×
517

518
        self._invoke()
×
519
        self.results = {}
×
520

521
        if self.exceptionreport is None:
×
522
            val = self._exml.find(util.nspath_eval('csw:Acknowledgement', namespaces))
×
523
            if util.testXMLValue(val) is not None:
×
524
                ts = val.attrib.get('timeStamp')
×
525
                self.timestamp = util.testXMLValue(ts, True)
×
526
                id = val.find(util.nspath_eval('csw:RequestId', namespaces))
×
527
                self.id = util.testXMLValue(id)
×
528
            else:
529
                self._parsetransactionsummary()
×
530
                self._parseinsertresult()
×
531

532
    def get_operation_by_name(self, name):
1✔
533
        """Return a named operation"""
534
        for item in self.operations:
1✔
535
            if item.name.lower() == name.lower():
1✔
536
                return item
1✔
537
        raise KeyError("No operation named %s" % name)
×
538

539
    def getService_urls(self, service_string=None):
1✔
540
        """
541

542
        Return easily identifiable URLs for all service types
543

544
        Parameters
545
        ----------
546

547
        - service_string: a URI to lookup
548

549
        """
550

551
        urls = []
×
552
        for key, rec in list(self.records.items()):
×
553
            # create a generator object, and iterate through it until the match is found
554
            # if not found, gets the default value (here "none")
555
            url = next((d['url'] for d in rec.references if d['scheme'] == service_string), None)
×
556
            if url is not None:
×
557
                urls.append(url)
×
558
        return urls
×
559

560
    def _parseinsertresult(self):
1✔
561
        self.results['insertresults'] = []
×
562
        for i in self._exml.findall('.//' + util.nspath_eval('csw:InsertResult', namespaces)):
×
563
            for j in i.findall(util.nspath_eval('csw:BriefRecord/dc:identifier', namespaces)):
×
564
                self.results['insertresults'].append(util.testXMLValue(j))
×
565

566
    def _parserecords(self, outputschema, esn):
1✔
567
        if outputschema == namespaces['gmd']:  # iso 19139
1✔
568
            for i in self._exml.findall('.//' + util.nspath_eval('gmd:MD_Metadata', namespaces)) or \
×
569
                    self._exml.findall('.//' + util.nspath_eval('gmi:MI_Metadata', namespaces)):
570
                val = i.find(util.nspath_eval('gmd:fileIdentifier/gco:CharacterString', namespaces))
×
571
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
572
                self.records[identifier] = MD_Metadata(i)
×
573
            for i in self._exml.findall('.//' + util.nspath_eval('gfc:FC_FeatureCatalogue', namespaces)):
×
574
                identifier = self._setidentifierkey(util.testXMLValue(i.attrib['uuid'], attrib=True))
×
575
                self.records[identifier] = FC_FeatureCatalogue(i)
×
576
        elif outputschema == namespaces['fgdc']:  # fgdc csdgm
1✔
577
            for i in self._exml.findall('.//metadata'):
×
578
                val = i.find('idinfo/datasetid')
×
579
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
580
                self.records[identifier] = Metadata(i)
×
581
        elif outputschema == namespaces['dif']:  # nasa dif
1✔
582
            for i in self._exml.findall('.//' + util.nspath_eval('dif:DIF', namespaces)):
×
583
                val = i.find(util.nspath_eval('dif:Entry_ID', namespaces))
×
584
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
585
                self.records[identifier] = DIF(i)
×
586
        elif outputschema == namespaces['gm03']:  # GM03
1✔
587
            for i in self._exml.findall('.//' + util.nspath_eval('gm03:TRANSFER', namespaces)):
×
588
                val = i.find(util.nspath_eval('gm03:fileIdentifier', namespaces))
×
589
                identifier = self._setidentifierkey(util.testXMLValue(val))
×
590
                self.records[identifier] = GM03(i)
×
591
        else:  # process default
592
            for i in self._exml.findall('.//' + util.nspath_eval('csw:%s' % self._setesnel(esn), namespaces)):
1✔
593
                val = i.find(util.nspath_eval('dc:identifier', namespaces))
1✔
594
                identifier = self._setidentifierkey(util.testXMLValue(val))
1✔
595
                self.records[identifier] = CswRecord(i)
1✔
596

597
    def _parsetransactionsummary(self):
1✔
598
        val = self._exml.find(util.nspath_eval('csw:TransactionResponse/csw:TransactionSummary', namespaces))
×
599
        if val is not None:
×
600
            rid = val.attrib.get('requestId')
×
601
            self.results['requestid'] = util.testXMLValue(rid, True)
×
602
            ts = val.find(util.nspath_eval('csw:totalInserted', namespaces))
×
603
            self.results['inserted'] = int(util.testXMLValue(ts))
×
604
            ts = val.find(util.nspath_eval('csw:totalUpdated', namespaces))
×
605
            self.results['updated'] = int(util.testXMLValue(ts))
×
606
            ts = val.find(util.nspath_eval('csw:totalDeleted', namespaces))
×
607
            self.results['deleted'] = int(util.testXMLValue(ts))
×
608

609
    def _setesnel(self, esn):
1✔
610
        """ Set the element name to parse depending on the ElementSetName requested """
611
        el = 'Record'
1✔
612
        if esn == 'brief':
1✔
613
            el = 'BriefRecord'
×
614
        if esn == 'summary':
1✔
615
            el = 'SummaryRecord'
1✔
616
        return el
1✔
617

618
    def _setidentifierkey(self, el):
1✔
619
        if el is None:
1✔
620
            return 'owslib_random_%i' % random.randint(1, 65536)
×
621
        else:
622
            return el
1✔
623

624
    def _setrootelement(self, el):
1✔
625
        if etree.__name__ == 'lxml.etree':  # apply nsmap
1✔
626
            return etree.Element(util.nspath_eval(el, namespaces), nsmap=namespaces)
×
627
        else:
628
            return etree.Element(util.nspath_eval(el, namespaces))
1✔
629

630
    def _setconstraint(self, parent, qtype=None, propertyname='csw:AnyText', keywords=[], bbox=None, cql=None,
1✔
631
                       identifier=None):
632
        if keywords or bbox is not None or qtype is not None or cql is not None or identifier is not None:
×
633
            node0 = etree.SubElement(parent, util.nspath_eval('csw:Constraint', namespaces))
×
634
            node0.set('version', '1.1.0')
×
635

636
            if identifier is not None:  # set identifier filter, overrides all other parameters
×
637
                flt = fes.FilterRequest()
×
638
                node0.append(flt.set(identifier=identifier))
×
639
            elif cql is not None:  # send raw CQL query
×
640
                # CQL passed, overrides all other parameters
641
                node1 = etree.SubElement(node0, util.nspath_eval('csw:CqlText', namespaces))
×
642
                node1.text = cql
×
643
            else:  # construct a Filter request
644
                flt = fes.FilterRequest()
×
645
                node0.append(flt.set(qtype=qtype, keywords=keywords, propertyname=propertyname, bbox=bbox))
×
646

647
    def _invoke(self):
1✔
648
        # do HTTP request
649

650
        request_url = self.url
1✔
651

652
        # Get correct URL based on Operation list.
653

654
        # If skip_caps=True, then self.operations has not been set, so use
655
        # default URL.
656
        if hasattr(self, 'operations'):
1✔
657
            caller = inspect.stack()[1][3]
1✔
658
            if caller == 'getrecords2':
1✔
659
                caller = 'getrecords'
1✔
660
            try:
1✔
661
                op = self.get_operation_by_name(caller)
1✔
662
                if isinstance(self.request, str):  # GET KVP
1✔
663
                    get_verbs = [x for x in op.methods if x.get('type').lower() == 'get']
×
664
                    request_url = get_verbs[0].get('url')
×
665
                else:
666
                    post_verbs = [x for x in op.methods if x.get('type').lower() == 'post']
1✔
667
                    if len(post_verbs) > 1:
1✔
668
                        # Filter by constraints.  We must match a PostEncoding of "XML"
669
                        for pv in post_verbs:
×
670
                            for const in pv.get('constraints'):
×
671
                                if const.name.lower() == 'postencoding':
×
672
                                    values = [v.lower() for v in const.values]
×
673
                                    if 'xml' in values:
×
674
                                        request_url = pv.get('url')
×
675
                                        break
×
676
                        else:
677
                            # Well, just use the first one.
678
                            request_url = post_verbs[0].get('url')
×
679
                    elif len(post_verbs) == 1:
1✔
680
                        request_url = post_verbs[0].get('url')
1✔
681
            except Exception:  # no such luck, just go with request_url
×
682
                pass
×
683

684
        if isinstance(self.request, str):  # GET KVP
1✔
685
            self.request = '%s%s' % (bind_url(request_url), self.request)
1✔
686
            self.response = openURL(
1✔
687
                self.request, None, 'Get', timeout=self.timeout, auth=self.auth,
688
                headers=self.headers).read()
689
        else:
690
            self.request = cleanup_namespaces(self.request)
1✔
691
            # Add any namespaces used in the "typeNames" attribute of the
692
            # csw:Query element to the query's xml namespaces.
693
            for query in self.request.findall(util.nspath_eval('csw:Query', namespaces)):
1✔
694
                ns = query.get("typeNames", None)
1✔
695
                if ns is not None:
1✔
696
                    # Pull out "gmd" from something like "gmd:MD_Metadata" from the list
697
                    # of typenames
698
                    ns_keys = [x.split(':')[0] for x in ns.split(' ')]
1✔
699
                    self.request = add_namespaces(self.request, ns_keys)
1✔
700
            self.request = add_namespaces(self.request, 'ows')
1✔
701

702
            self.request = util.element_to_string(self.request, encoding='utf-8')
1✔
703

704
            self.response = http_post(request_url, self.request, self.lang, self.timeout,
1✔
705
                                      auth=self.auth, headers=self.headers).content
706

707
        # parse result see if it's XML
708
        self._exml = etree.parse(BytesIO(self.response))
1✔
709

710
        # it's XML.  Attempt to decipher whether the XML response is CSW-ish """
711
        valid_xpaths = [
1✔
712
            util.nspath_eval('ows:ExceptionReport', namespaces),
713
            util.nspath_eval('csw:Capabilities', namespaces),
714
            util.nspath_eval('csw:DescribeRecordResponse', namespaces),
715
            util.nspath_eval('csw:GetDomainResponse', namespaces),
716
            util.nspath_eval('csw:GetRecordsResponse', namespaces),
717
            util.nspath_eval('csw:GetRecordByIdResponse', namespaces),
718
            util.nspath_eval('csw:HarvestResponse', namespaces),
719
            util.nspath_eval('csw:TransactionResponse', namespaces)
720
        ]
721

722
        if self._exml.getroot().tag not in valid_xpaths:
1✔
723
            raise RuntimeError('Document is XML, but not CSW-ish')
×
724

725
        # check if it's an OGC Exception
726
        val = self._exml.find(util.nspath_eval('ows:Exception', namespaces))
1✔
727
        if val is not None:
1✔
728
            raise ows.ExceptionReport(self._exml, self.owscommon.namespace)
×
729
        else:
730
            self.exceptionreport = None
1✔
731

732

733
class CswRecord(object):
1✔
734
    """ Process csw:Record, csw:BriefRecord, csw:SummaryRecord """
735
    def __init__(self, record):
1✔
736

737
        if hasattr(record, 'getroot'):  # standalone document
1✔
738
            self.xml = etree.tostring(record.getroot())
×
739
        else:  # part of a larger document
740
            self.xml = etree.tostring(record)
1✔
741

742
        # check to see if Dublin Core record comes from
743
        # rdf:RDF/rdf:Description container
744
        # (child content model is identical)
745
        self.rdf = False
1✔
746
        rdf = record.find(util.nspath_eval('rdf:Description', namespaces))
1✔
747
        if rdf is not None:
1✔
748
            self.rdf = True
×
749
            record = rdf
×
750

751
        # some CSWs return records with multiple identifiers based on
752
        # different schemes.  Use the first dc:identifier value to set
753
        # self.identifier, and set self.identifiers as a list of dicts
754
        val = record.find(util.nspath_eval('dc:identifier', namespaces))
1✔
755
        self.identifier = util.testXMLValue(val)
1✔
756

757
        self.identifiers = []
1✔
758
        for i in record.findall(util.nspath_eval('dc:identifier', namespaces)):
1✔
759
            d = {}
1✔
760
            d['scheme'] = i.attrib.get('scheme')
1✔
761
            d['identifier'] = i.text
1✔
762
            self.identifiers.append(d)
1✔
763

764
        val = record.find(util.nspath_eval('dc:type', namespaces))
1✔
765
        self.type = util.testXMLValue(val)
1✔
766

767
        val = record.find(util.nspath_eval('dc:title', namespaces))
1✔
768
        self.title = util.testXMLValue(val)
1✔
769

770
        val = record.find(util.nspath_eval('dct:alternative', namespaces))
1✔
771
        self.alternative = util.testXMLValue(val)
1✔
772

773
        val = record.find(util.nspath_eval('dct:isPartOf', namespaces))
1✔
774
        self.ispartof = util.testXMLValue(val)
1✔
775

776
        val = record.find(util.nspath_eval('dct:abstract', namespaces))
1✔
777
        self.abstract = util.testXMLValue(val)
1✔
778

779
        val = record.find(util.nspath_eval('dc:date', namespaces))
1✔
780
        self.date = util.testXMLValue(val)
1✔
781

782
        val = record.find(util.nspath_eval('dct:created', namespaces))
1✔
783
        self.created = util.testXMLValue(val)
1✔
784

785
        val = record.find(util.nspath_eval('dct:issued', namespaces))
1✔
786
        self.issued = util.testXMLValue(val)
1✔
787

788
        val = record.find(util.nspath_eval('dc:relation', namespaces))
1✔
789
        self.relation = util.testXMLValue(val)
1✔
790

791
        val = record.find(util.nspath_eval('dct:temporal', namespaces))
1✔
792
        self.temporal = util.testXMLValue(val)
1✔
793

794
        self.uris = []  # list of dicts
1✔
795
        for i in record.findall(util.nspath_eval('dc:URI', namespaces)):
1✔
796
            uri = {}
×
797
            uri['protocol'] = util.testXMLValue(i.attrib.get('protocol'), True)
×
798
            uri['name'] = util.testXMLValue(i.attrib.get('name'), True)
×
799
            uri['description'] = util.testXMLValue(i.attrib.get('description'), True)
×
800
            uri['url'] = util.testXMLValue(i)
×
801

802
            self.uris.append(uri)
×
803

804
        self.references = []  # list of dicts
1✔
805
        for i in record.findall(util.nspath_eval('dct:references', namespaces)):
1✔
806
            ref = {}
×
807
            ref['scheme'] = util.testXMLValue(i.attrib.get('scheme'), True)
×
808
            ref['url'] = util.testXMLValue(i)
×
809

810
            self.references.append(ref)
×
811

812
        val = record.find(util.nspath_eval('dct:modified', namespaces))
1✔
813
        self.modified = util.testXMLValue(val)
1✔
814

815
        val = record.find(util.nspath_eval('dc:creator', namespaces))
1✔
816
        self.creator = util.testXMLValue(val)
1✔
817

818
        val = record.find(util.nspath_eval('dc:publisher', namespaces))
1✔
819
        self.publisher = util.testXMLValue(val)
1✔
820

821
        val = record.find(util.nspath_eval('dc:coverage', namespaces))
1✔
822
        self.coverage = util.testXMLValue(val)
1✔
823

824
        val = record.find(util.nspath_eval('dc:contributor', namespaces))
1✔
825
        self.contributor = util.testXMLValue(val)
1✔
826

827
        val = record.find(util.nspath_eval('dc:language', namespaces))
1✔
828
        self.language = util.testXMLValue(val)
1✔
829

830
        val = record.find(util.nspath_eval('dc:source', namespaces))
1✔
831
        self.source = util.testXMLValue(val)
1✔
832

833
        val = record.find(util.nspath_eval('dct:rightsHolder', namespaces))
1✔
834
        self.rightsholder = util.testXMLValue(val)
1✔
835

836
        val = record.find(util.nspath_eval('dct:accessRights', namespaces))
1✔
837
        self.accessrights = util.testXMLValue(val)
1✔
838

839
        val = record.find(util.nspath_eval('dct:license', namespaces))
1✔
840
        self.license = util.testXMLValue(val)
1✔
841

842
        val = record.find(util.nspath_eval('dc:format', namespaces))
1✔
843
        self.format = util.testXMLValue(val)
1✔
844

845
        self.subjects = []
1✔
846
        for i in record.findall(util.nspath_eval('dc:subject', namespaces)):
1✔
847
            self.subjects.append(util.testXMLValue(i))
1✔
848

849
        self.rights = []
1✔
850
        for i in record.findall(util.nspath_eval('dc:rights', namespaces)):
1✔
851
            self.rights.append(util.testXMLValue(i))
×
852

853
        val = record.find(util.nspath_eval('dct:spatial', namespaces))
1✔
854
        self.spatial = util.testXMLValue(val)
1✔
855

856
        val = record.find(util.nspath_eval('ows:BoundingBox', namespaces))
1✔
857
        if val is not None:
1✔
858
            self.bbox = ows.BoundingBox(val, namespaces['ows'])
1✔
859
        else:
860
            self.bbox = None
1✔
861

862
        val = record.find(util.nspath_eval('ows:WGS84BoundingBox', namespaces))
1✔
863
        if val is not None:
1✔
864
            self.bbox_wgs84 = ows.WGS84BoundingBox(val, namespaces['ows'])
×
865
        else:
866
            self.bbox_wgs84 = None
1✔
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