mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-08-11 15:39:24 +03:00
personalization: EnumParam: implement as value_map, not enum.IntEnum
EnumParam is for labels shown in the UI, not an enum to be used in python code (what the python enum module is written for). So it is semantically wrong to use enum.IntEnum. - the label should be allowed to be any string, not just valid python identifiers. For example, a label like "SUCI-in-USIM" should be possible. - we should not "leak" UI labels into the python namespace. - the value should be allowed to be any type, so that each ConfigurableParameter implementation can use whichever is its internal native type. History: this patch is the original version of EnumParam, which was modified during CR to use enum.IntEnum. However, newer ConfigurableParameters coming up don't match well: - MncLen (labels "2" and "3") - EuiccMandatoryServiceParam (values True and False) - EfUstServiceParam like SuciInUsim (labels "SUCI-in-UE" and "SUCI-in-USIM") So this brings back the original capability for any label string, and any type of value. Change-Id: I690ceccf0ec7ef7067bcaa5cec1303cdaf0f78a4
This commit is contained in:
@@ -420,69 +420,71 @@ class BinaryParam(ConfigurableParameter):
|
|||||||
|
|
||||||
|
|
||||||
class EnumParam(ConfigurableParameter):
|
class EnumParam(ConfigurableParameter):
|
||||||
"""ConfigurableParameter for named integer enumeration values.
|
"""ConfigurableParameter for named value enumerations.
|
||||||
|
|
||||||
Subclasses must define a nested enum.IntEnum named 'Values' listing all valid names and their
|
Subclasses define an own value_map, and implement their own apply_val() and get_values_from_pes().
|
||||||
integer codes. apply_val() and get_values_from_pes() are not implemented here and this must
|
"""
|
||||||
be inherited from another mixin."""
|
value_map = {
|
||||||
|
# For example:
|
||||||
class Values(enum.IntEnum):
|
#'Meaningful label for value 23': 0x23,
|
||||||
pass # subclasses override this
|
# Where 0x23 is a valid value to use for apply_val(), of any valid type.
|
||||||
|
}
|
||||||
|
_value_map_reverse = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_val(cls, val) -> int:
|
def validate_val(cls, val):
|
||||||
if isinstance(val, int):
|
orig_val = val
|
||||||
try:
|
enum_val = None
|
||||||
return int(cls.Values(val))
|
if isinstance(val, str):
|
||||||
except ValueError:
|
enum_name = val
|
||||||
pass
|
enum_val = cls.map_name_to_val(enum_name)
|
||||||
elif isinstance(val, str):
|
|
||||||
member = cls.map_name_to_val(val, strict=False)
|
|
||||||
if member is not None:
|
|
||||||
return member
|
|
||||||
|
|
||||||
valid = ', '.join(m.name for m in cls.Values)
|
# if the str is not one of the known value_map.keys(), is it maybe one of value_map.keys()?
|
||||||
raise ValueError(f"{cls.get_name()}: invalid argument: {val!r}. Valid arguments are: {valid}")
|
if enum_val is None and val in cls.value_map.values():
|
||||||
|
enum_val = val
|
||||||
|
|
||||||
|
if enum_val not in cls.value_map.values():
|
||||||
|
raise ValueError(f"{cls.get_name()}: invalid argument: {orig_val!r}. Valid arguments are:"
|
||||||
|
f" {', '.join(cls.value_map.keys())}")
|
||||||
|
|
||||||
|
return enum_val
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def map_name_to_val(cls, name: str, strict=True) -> int:
|
def map_name_to_val(cls, name:str, strict=True):
|
||||||
"""Return the integer value for a given enum member name. Performs an exact match first,
|
val = cls.value_map.get(name)
|
||||||
then falls back to fuzzy matching (case-insensitive, punctuation-insensitive)."""
|
if val is not None:
|
||||||
try:
|
return val
|
||||||
return int(cls.Values[name])
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
clean = cls.clean_name_str(name)
|
clean_name = cls.clean_name_str(name)
|
||||||
for member in cls.Values:
|
for k, v in cls.value_map.items():
|
||||||
if cls.clean_name_str(member.name) == clean:
|
if clean_name == cls.clean_name_str(k):
|
||||||
return int(member)
|
return v
|
||||||
|
|
||||||
if strict:
|
if strict:
|
||||||
valid = ', '.join(m.name for m in cls.Values)
|
raise ValueError(f"Problem in {cls.get_name()}: {name!r} is not a known value."
|
||||||
raise ValueError(f"{cls.get_name()}: {name!r} is not a known value. Known values are: {valid}")
|
f" Known values are: {cls.value_map.keys()!r}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def map_val_to_name(cls, val, strict=False) -> str:
|
def map_val_to_name(cls, val, strict=False) -> str:
|
||||||
"""Return the enum member name for a given integer value."""
|
if cls._value_map_reverse is None:
|
||||||
try:
|
cls._value_map_reverse = dict((v, k) for k, v in cls.value_map.items())
|
||||||
return cls.Values(val).name
|
|
||||||
except ValueError:
|
name = cls._value_map_reverse.get(val)
|
||||||
if strict:
|
if name:
|
||||||
raise ValueError(f"{cls.get_name()}: {val!r} ({type(val).__name__}) is not a known value.")
|
return name
|
||||||
return None
|
if strict:
|
||||||
|
raise ValueError(f"Problem in {cls.get_name()}: {val!r} ({type(val)}) is not a known value."
|
||||||
|
f" Known values are: {cls.value_map.values()!r}")
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def name_normalize(cls, name: str) -> str:
|
def name_normalize(cls, name:str) -> str:
|
||||||
"""Map a (possibly fuzzy) name to its canonical enum member name."""
|
return cls.map_val_to_name(cls.map_name_to_val(name))
|
||||||
return cls.Values(cls.map_name_to_val(name)).name
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clean_name_str(cls, val: str) -> str:
|
def clean_name_str(cls, val):
|
||||||
"""Strip punctuation and case for fuzzy name comparison.
|
return re.sub('[^0-9A-Za-z-_]', '', val).lower()
|
||||||
Treats hyphens and underscores as equivalent (both removed)."""
|
|
||||||
return re.sub('[^0-9A-Za-z]', '', val).lower()
|
|
||||||
|
|
||||||
|
|
||||||
class Iccid(DecimalParam):
|
class Iccid(DecimalParam):
|
||||||
@@ -1099,17 +1101,17 @@ class AlgorithmID(EnumParam, AlgoConfig):
|
|||||||
"""use validate_val() from EnumParam, and apply_val() from AlgoConfig.
|
"""use validate_val() from EnumParam, and apply_val() from AlgoConfig.
|
||||||
In get_values_from_pes(), return enum value names, not raw values."""
|
In get_values_from_pes(), return enum value names, not raw values."""
|
||||||
name = "Algorithm"
|
name = "Algorithm"
|
||||||
|
# as in pySim/esim/asn1/saip/PE_Definitions-3.3.1.asn
|
||||||
|
value_map = {
|
||||||
|
"Milenage" : 1,
|
||||||
|
"TUAK" : 2,
|
||||||
|
"usim-test" : 3,
|
||||||
|
}
|
||||||
algo_config_key = 'algorithmID'
|
algo_config_key = 'algorithmID'
|
||||||
example_input = "Milenage"
|
example_input = "Milenage"
|
||||||
default_source = param_source.ConstantSource
|
default_source = param_source.ConstantSource
|
||||||
|
|
||||||
# as in pySim/esim/asn1/saip/PE_Definitions-3.3.1.asn
|
# EnumParam.validate_val() returns the int values from value_map
|
||||||
class Values(enum.IntEnum):
|
|
||||||
Milenage = 1
|
|
||||||
TUAK = 2
|
|
||||||
usim_test = 3 # input 'usim-test' also accepted via fuzzy matching
|
|
||||||
|
|
||||||
# EnumParam.validate_val() returns the int values from Values
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_values_from_pes(cls, pes: ProfileElementSequence):
|
def get_values_from_pes(cls, pes: ProfileElementSequence):
|
||||||
|
|||||||
Reference in New Issue
Block a user