def _make_output_class(f, json_encoders=None): return_annot = typing.get_type_hints(f).get('return', inspect._empty) if return_annot is inspect._empty: raise TypeError( f"Unable to construct a Task from function '{f.__qualname__}': " "the annotation for the return value is missing. " "This may be a type, or a subclass of TaskOutput.") json_encoders_arg = json_encoders if json_encoders else {} class Config: # NB: MIGHT NOT be used if `f` includes a 'return' annotation json_encoders = { **json_encoders_arg, **smttask_json_encoders, **base.TaskOutput.Config.json_encoders } if lenient_issubclass(return_annot, base.TaskOutput): # Nothing to do Outputs = return_annot # Add the json_encoders to the Output type, but only if they were not # given explicitely in the Output type. if json_encoders: if hasattr(Outputs, 'Config') and not hasattr( Outputs.Config, 'json_encoders'): Outputs.Config.json_encoders = Config.json_encoders elif not hasattr(Outputs, 'Config'): Outputs.Config = Config else: assert isinstance(return_annot, (type, typing._GenericAlias)) # A bare annotation does not define a variable name; we set it to the # empty string (i.e., the variable is only identified by the task name) Outputs = ModelMetaclass(f"{f.__qualname__}.Outputs", (base.TaskOutput, ), { '__annotations__': { "": return_annot }, 'Config': Config }) # Set correct module; workaround for https://bugs.python.org/issue28869 Outputs.__module__ = f.__module__ # update_forward_refs required for 3.9+ style annotations Outputs.update_forward_refs() return Outputs
def _make_input_class(f, json_encoders=None): defaults = {} annotations = {} json_encoders_arg = json_encoders if json_encoders else {} class Config: # Override the lenience of the base TaskInput class and only allow expected arguments extra = 'forbid' json_encoders = { **json_encoders_arg, **smttask_json_encoders, **base.TaskInput.Config.json_encoders } for nm, param in inspect.signature(f).parameters.items(): if param.annotation is inspect._empty: raise TypeError( "Constructing a Task requires that all function arguments " f"be annotated. Offender: argument '{nm}' of '{f.__qualname__}'." ) annotation = param.annotation if isinstance(annotation, str): # HACK to resolve forward refs globalns = sys.modules[f.__module__].__dict__.copy() annotation = evaluate_forwardref(ForwardRef(annotation), globalns=globalns, localns=None) annotations[nm] = Union[base.Task, annotation] if param.default is not inspect._empty: defaults[nm] = param.default Inputs = ModelMetaclass(f"{f.__qualname__}.Inputs", (base.TaskInput, ), { **defaults, 'Config': Config, '__annotations__': annotations }) # Set correct module; workaround for https://bugs.python.org/issue28869 Inputs.__module__ = f.__module__ # update_forward_refs required for 3.9+ style annotations # DEVNOTE: When one inspects Inputs.__fields__, some types may still contain 'ForwardRef' # I don't know why that is, but checking Inputs.__fields__[field name].__args__[ForwardRef index].__forward_evaluated__ should still be True # and [...].__forward_value__ should be the expected type Inputs.update_forward_refs() return Inputs