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
|
# Copyright 2024 The ML Collections Authors.
#
# 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.
"""Custom parser to override tuples in the config dict."""
import ast
import collections.abc
from absl import flags
class TupleParser(flags.ArgumentParser):
"""Parser for tuple arguments.
Custom flag parser for Tuple objects that is based on the existing parsers
in `absl.flags`. This parser can be used to read in and override tuple
arguments. It outputs a `tuple` object from an existing `tuple` or `str`.
The ony requirement is that the overriding parameter should be a `tuple`
as well. The overriding parameter can have a different number of elements
of different type than the original. For a detailed list of what `str`
arguments are supported for overriding, look at `ast.literal_eval` from the
Python Standard Library.
"""
def parse(self, argument):
"""Returns a `tuple` representing the input `argument`.
Args:
argument: The argument to be parsed. Valid types are `tuple` and
`str` or a single object. `str` arguments are parsed and converted to a
tuple, a single object is converted to a tuple of length 1, and an empty
`tuple` is returned for arguments `NoneType`.
Returns:
A `TupleType` representing the input argument as a `tuple`.
"""
if isinstance(argument, tuple):
return argument
elif isinstance(argument, str):
return _convert_str_to_tuple(argument)
elif argument is None:
return ()
else:
return (argument,)
def flag_type(self):
return 'tuple'
def _convert_str_to_tuple(string):
"""Function to convert a Python `str` object to a `tuple`.
Args:
string: The `str` to be converted.
Returns:
A `tuple` version of the string.
Raises:
ValueError: If the string is not a well formed `tuple`.
"""
# literal_eval converts strings to int, tuple, list, float and dict,
# booleans and None. It can also handle nested tuples.
# It does not, however, handle elements of type set.
try:
value = ast.literal_eval(string)
except ValueError:
# A ValueError is raised by literal_eval if the string is not well
# formed. This is probably because string represents a single string
# element, e.g. string='abc', and a tuple of strings field was overridden by
# repeated use of a flag (ie `--flag a --flag b` instead of
# `--flag '("a", "b")'`).
value = string
except SyntaxError as exc:
# The only other error that may be raised is a `SyntaxError` because
# `literal_eval` calls the Python in-built `compile`. This error is
# caused by parsing issues.
if ',' not in string and ' ' not in string:
# Most likely passed a single string that contained an operator -- e.g.
# '/path/to/file' or 'file_pattern*'. If a comma isn't in the string, then
# it can't have been a tuple, so assume it's an unquoted string.
# If passed strings containing a comma (user probably expects conversion
# to a tuple) or whitespace (user might expect implicit conversion to
# tuple?) raise an exception.
value = string
else:
msg = (
f'Error while parsing string: {string} as tuple. If you intended to'
' pass the argument as a single element, use quotes such as `--flag'
f' {repr(repr(string))}, otherwise insert quotes around each element.'
)
if ' ' in string:
msg += (
' Use commas instead of whitespace as the separator between'
' elements.'
)
raise ValueError(msg) from exc
# Make sure we return a tuple.
if isinstance(value, tuple):
return value
elif (isinstance(value, collections.abc.Iterable) and
not isinstance(value, str)):
return tuple(value)
else:
return (value,)
|