Convert a CLI string argument to the proper Python type based on a type hint. Args: value_str: The raw string from the CLI. type_hint: The type annotation from the function signature. model: The main open IFC model, needed to resolve entity instance references by ID.
(
value_str: str,
type_hint,
model: ifcopenshell.file | None = None,
lookup_file: ifcopenshell.file | None = None,
)
| 26 | |
| 27 | |
| 28 | def coerce_value( |
| 29 | value_str: str, |
| 30 | type_hint, |
| 31 | model: ifcopenshell.file | None = None, |
| 32 | lookup_file: ifcopenshell.file | None = None, |
| 33 | ): |
| 34 | """Convert a CLI string argument to the proper Python type based on a type hint. |
| 35 | |
| 36 | Args: |
| 37 | value_str: The raw string from the CLI. |
| 38 | type_hint: The type annotation from the function signature. |
| 39 | model: The main open IFC model, needed to resolve entity instance references by ID. |
| 40 | lookup_file: Override file for entity resolution (e.g. a library file for |
| 41 | project.append_asset). When provided, entity IDs are looked up here instead |
| 42 | of in model. |
| 43 | |
| 44 | Returns: |
| 45 | The converted Python value. |
| 46 | |
| 47 | Raises: |
| 48 | ValueError: If the value cannot be converted. |
| 49 | TypeError: If the type hint is not supported. |
| 50 | """ |
| 51 | # When a library file has been opened, entity IDs are resolved from it, not the main model. |
| 52 | effective_lookup = lookup_file if lookup_file is not None else model |
| 53 | |
| 54 | if type_hint is None: |
| 55 | return value_str |
| 56 | |
| 57 | origin = typing.get_origin(type_hint) |
| 58 | args = typing.get_args(type_hint) |
| 59 | |
| 60 | # Union / Optional |
| 61 | if origin is typing.Union: |
| 62 | non_none_types = [a for a in args if a is not type(None)] |
| 63 | if value_str.lower() == "none": |
| 64 | if type(None) in args: |
| 65 | return None |
| 66 | # Try each non-None type in order |
| 67 | for t in non_none_types: |
| 68 | try: |
| 69 | return coerce_value(value_str, t, model, lookup_file) |
| 70 | except (ValueError, TypeError): |
| 71 | continue |
| 72 | raise ValueError(f"Cannot convert '{value_str}' to any of {non_none_types}") |
| 73 | |
| 74 | # Literal |
| 75 | if origin is typing.Literal: |
| 76 | allowed = args |
| 77 | if value_str in [str(a) for a in allowed]: |
| 78 | # return the actual literal value with proper type |
| 79 | for a in allowed: |
| 80 | if str(a) == value_str: |
| 81 | return a |
| 82 | raise ValueError(f"'{value_str}' is not one of: {', '.join(repr(a) for a in allowed)}") |
| 83 | |
| 84 | # list types |
| 85 | if origin is list: |