/usr/lib/python3/dist-packages/pydap/parsers/das.py is in python3-pydap 3.2.2+ds1-1ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | """A parser for the Dataset Attribute Structure (DAS) response.
This module implements a DAS parser. The ``parse_das`` function will convert a
DAS response into a dictionary of attributes, which can be applied to an
existing dataset using the ``add_attributes`` function.
"""
import re
import ast
import operator
from six.moves import reduce
from . import SimpleParser
from ..lib import walk
class DASParser(SimpleParser):
"""A parser for the Dataset Attribute Structure response."""
def __init__(self, das):
super(DASParser, self).__init__(
das, re.IGNORECASE | re.VERBOSE | re.DOTALL)
def consume(self, regexp):
"""Return a token from the buffer.
Not that it will Ignore white space when consuming tokens.
"""
token = super(DASParser, self).consume(regexp)
self.buffer = self.buffer.lstrip()
return token
def parse(self):
"""Start the parsing, returning a nested dictionary of attributes."""
out = {}
self.consume('attributes')
self.container(out)
return out
def container(self, target):
"""Collect the attributes for a DAP variable."""
self.consume('{')
while not self.peek('}'):
if self.peek(r'[^\s]+\s+{'):
name = self.consume(r'[^\s]+')
target[name] = {}
self.container(target[name])
else:
name, values = self.attribute()
target[name] = values
self.consume('}')
def attribute(self):
"""Parse attributes.
The function will parse attributes from the DAS, converting them to the
corresponding Python object. Returns the name of the attribute and the
attribute(s).
"""
type = self.consume(r'[^\s]+')
name = self.consume(r'[^\s]+')
values = []
while not self.peek(';'):
value = self.consume(
r'''
"" # empty attribute
| # or
".*?[^\\]" # from quote up to an unquoted quote
| # or
[^;,]+ # up to semicolon or comma
'''
)
if type.lower() in ['string', 'url']:
value = str(value).strip('"')
elif value.lower() in ['nan', 'nan.', '-nan']:
value = float('nan')
else:
value = ast.literal_eval(value)
values.append(value)
if self.peek(','):
self.consume(',')
self.consume(';')
if len(values) == 1:
values = values[0]
return name, values
def parse_das(das):
"""Parse the DAS, returning nested dictionaries."""
return DASParser(das).parse()
def add_attributes(dataset, attributes):
"""Add attributes from a parsed DAS to a dataset.
Returns the dataset with added attributes.
"""
dataset.attributes['NC_GLOBAL'] = attributes.get('NC_GLOBAL', {})
dataset.attributes['DODS_EXTRA'] = attributes.get('DODS_EXTRA', {})
for var in list(walk(dataset))[::-1]:
# attributes can be flat, eg, "foo.bar" : {...}
if var.id in attributes:
var.attributes.update(attributes.pop(var.id))
# or nested, eg, "foo" : { "bar" : {...} }
try:
nested = reduce(
operator.getitem, [attributes] + var.id.split('.')[:-1])
k = var.id.split('.')[-1]
value = nested.pop(k)
except KeyError:
pass
else:
try:
var.attributes.update(value)
except (TypeError, ValueError):
# This attribute should be given to the parent.
# Keep around:
nested.update({k: value})
# add attributes that don't belong to any child
for k, v in attributes.items():
dataset.attributes[k] = v
return dataset
|