예제 #1
0
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
예제 #2
0
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