# Copyright 2019 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This file contains tools for common tasks in parsing in python strings into non-string values
"""
import ast
import copy
from dateutil import parser
[docs]def tryLiteralEval(source):
try:
source = ast.literal_eval(source)
except (ValueError, SyntaxError):
pass
return source
# the following dict helps avoid the need for an eval() statement
# Is there no better way to go 'bool' -> bool !?
_str_types = {
tp.__name__: tp
for tp in (type(None), bool, int, complex, float, str, bytes, list, tuple, dict)
}
_type_strs = {v: k for k, v in _str_types.items()}
# python's matching truth evaluations of Nones in different primitive types
# str's and unicodes omitted because parseValue denies their use.
_none_types = {
type(None): None,
bool: False,
int: 0,
complex: 0j,
float: 0.0,
list: [],
tuple: (),
dict: {},
}
[docs]def _numericSpecialBehavior(source, rt):
try:
return rt(source), True # convert, report success
except (ValueError, TypeError):
return source, False # fail, report failure
[docs]def parseValue(source, requestedType, allowNone=False, matchingNonetype=True):
"""Tries parse a python value, expecting input to be the right type or a string"""
# misuse prevention
if requestedType == str:
raise TypeError(
"Unreliable and unnecessary to use parseValue for strs and unicodes. "
"Given parameters are {}, {}, {}.".format(source, requestedType, allowNone)
)
# evaluation and special evaluation for numbers
evaluated_source, skip_instance_check = tryLiteralEval(source), False
if requestedType in [int, float, complex]:
evaluated_source, skip_instance_check = _numericSpecialBehavior(
evaluated_source, requestedType
)
# none logic
if allowNone and not evaluated_source:
if matchingNonetype:
return copy.deepcopy(_none_types[requestedType])
else:
return evaluated_source
# assert everything went well
if not skip_instance_check and not isinstance(evaluated_source, requestedType):
msg = "Could not parse {} from source {}."
if allowNone:
msg += " Nor could None be parsed from source."
raise ValueError(msg.format(requestedType, evaluated_source))
return evaluated_source
# -----------------------------------
[docs]def datetimeFromStr(string):
"""Converts an arbitrary string to a datetime object"""
return parser.parse(string)