def get_dataclass_type_from_forward_ref(forward_ref: Type, Serializable=Serializable) -> Optional[Type]: arg = tpi.get_forward_arg(forward_ref) potential_classes: List[Type] = [] for serializable_class in Serializable.subclasses: if serializable_class.__name__ == arg: potential_classes.append(serializable_class) if not potential_classes: logger.warning( f"Unable to find a corresponding type for forward ref " f"{forward_ref} inside the registered {Serializable} subclasses. " f"(Consider adding {Serializable} as a base class to <{arg}>? )." ) return None elif len(potential_classes) > 1: logger.warning( f"More than one potential {Serializable} subclass was found for " f"forward ref '{forward_ref}'. The appropriate dataclass will be " f"selected based on the matching fields. \n" f"Potential classes: {potential_classes}" ) return Serializable else: assert len(potential_classes) == 1 return potential_classes[0]
def _all_subclasses(typ, *, module=None): """ Return all subclasses of a given type. The type must be one of - :class:`GTScriptAstNode` (returns all subclasses of the given class) - :class:`Union` (return the subclasses of the united) - :class:`ForwardRef` (resolve the reference given the specified module and return its subclasses) - built-in python type: :class:`str`, :class:`int`, `type(None)` (return as is) """ if inspect.isclass(typ) and issubclass(typ, gtscript_ast.GTScriptASTNode): result = { typ, *typ.__subclasses__(), *[ s for c in typ.__subclasses__() for s in PyToGTScript._all_subclasses(c) if not inspect.isabstract(c) ], } return result elif inspect.isclass(typ) and typ in [ gtc.common.AssignmentKind, gtc.common.UnaryOperator, gtc.common.BinaryOperator, ]: # note: other types in gtc.common, e.g. gtc.common.DataType are not valid leaf nodes here as they # map to symbols in the gtscript ast and are resolved there assert issubclass(typ, enum.Enum) return {typ} elif typing_inspect.is_union_type(typ): return { sub_cls for el_cls in typing_inspect.get_args(typ) for sub_cls in PyToGTScript._all_subclasses(el_cls, module=module) } elif isinstance(typ, typing.ForwardRef): type_name = typing_inspect.get_forward_arg(typ) if not hasattr(module, type_name): raise ValueError( f"Reference to type `{type_name}` in `ForwardRef` not found in module {module.__name__}" ) return PyToGTScript._all_subclasses(getattr(module, type_name), module=module) elif typ in [ type_definitions.StrictStr, type_definitions.StrictInt, type_definitions.StrictFloat, str, int, float, type(None), ]: # TODO(tehrengruber): enhance return {typ} raise ValueError(f"Invalid field type {typ}")
def get_dataclass_types_from_forward_ref( forward_ref: Type, serializable_base_class: Type[S] = SerializableMixin) -> List[Type[S]]: arg = tpi.get_forward_arg(forward_ref) potential_classes: List[Type] = [] for serializable_class in serializable_base_class.subclasses: if serializable_class.__name__ == arg: potential_classes.append(serializable_class) return potential_classes
def _resolve_forward_ref(self, t: Type[Any]) -> Type[Any]: if isinstance(t, str) or is_forward_ref(t): if isinstance(t, str): name = t else: name = get_forward_arg(t) if name in self.forward_ref_dict: return self.forward_ref_dict[name] else: raise Exception(f"Unknown forward reference '{name}'") else: return t
def _get_type_name(t: Type) -> str: if hasattr(t, "__name__"): return t.__name__ else: origin = get_origin(t) if origin: name = MappingException._get_type_name(origin) elif is_forward_ref(t): name = get_forward_arg(t) else: name = t._name args = list(map(lambda x: MappingException._get_type_name(x), get_args(t))) if len(args) != 0: return f"{name}[{', '.join(args)}]" else: return name
def get_parsing_fn(t: Type[T]) -> Callable[[Any], T]: """Gets a parsing function for the given type or type annotation. Args: t (Type[T]): A type or type annotation. Returns: Callable[[Any], T]: A function that will parse a value of the given type from the command-line when available, or a no-op function that will return the raw value, when a parsing fn cannot be found or constructed. """ if t in _parsing_fns: logger.debug(f"The type {t} has a dedicated parsing function.") return _parsing_fns[t] elif t is Any: logger.debug(f"parsing an Any type: {t}") return no_op # TODO: Do we want to support parsing a Dict from command-line? # elif is_dict(t): # logger.debug(f"parsing a Dict field: {t}") # args = get_type_arguments(t) # if len(args) != 2: # args = (Any, Any) # return parse_dict(*args) # TODO: This would require some sort of 'postprocessing' step to convert a # list to a Set or something like that. # elif is_set(t): # logger.debug(f"parsing a Set field: {t}") # args = get_type_arguments(t) # if len(args) != 1: # args = (Any,) # return parse_set(args[0]) elif is_tuple(t): logger.debug(f"parsing a Tuple field: {t}") args = get_type_arguments(t) if is_homogeneous_tuple_type(t): if not args: args = (str, ...) parsing_fn = get_parsing_fn(args[0]) else: parsing_fn = parse_tuple(args) parsing_fn.__name__ = str(t) return parsing_fn elif is_list(t): logger.debug(f"parsing a List field: {t}") args = get_type_arguments(t) assert len(args) == 1 return parse_list(args[0]) elif is_union(t): logger.debug(f"parsing a Union field: {t}") args = get_type_arguments(t) return parse_union(*args) elif is_enum(t): logger.debug(f"Parsing an Enum field of type {t}") return parse_enum(t) # import typing_inspect as tpi # from .serializable import get_dataclass_type_from_forward_ref, Serializable if tpi.is_forward_ref(t): forward_arg = tpi.get_forward_arg(t) for t, fn in _parsing_fns.items(): if getattr(t, "__name__", str(t)) == forward_arg: return fn if tpi.is_typevar(t): bound = tpi.get_bound(t) logger.debug(f"parsing a typevar: {t}, bound type is {bound}.") if bound is not None: return get_parsing_fn(bound) logger.debug(f"Couldn't find a parsing function for type {t}, will try " f"to use the type directly.") return t
def test_get_forward_arg(self): tp = List["FRef"] fr = get_args(tp)[0] self.assertEqual(get_forward_arg(fr), "FRef") self.assertEqual(get_forward_arg(tp), None)
def sanitize_if_forward_ref(subscripted_type: type) -> Union[type, str]: if is_forward_ref(subscripted_type): return get_forward_arg(subscripted_type) return subscripted_type