Esempio n. 1
0
 def as_function(self,
                 *,
                 name: str = 'expr',
                 argument_name: str = 'a',
                 stacklevel: int = 1,
                 locals: Dict[str, Any] = None,
                 globals: Dict[str, Any] = None) -> Callable[[T], T]:
     sourcecode = self.as_string(name=name, argument_name=argument_name)
     if locals is None or globals is None and stacklevel:
         frame = _getframe(stacklevel)
         globals = frame.f_globals if globals is None else globals
         locals = frame.f_locals if locals is None else locals
     new_globals = dict(globals or {})
     new_globals.update(self.globals)
     if DEBUG:
         print(f'SOURCE FOR {self!r} ->\n{sourcecode}')
     return codegen.build_closure(
         '__outer__', sourcecode,
         locals={} if locals is None else locals,
         globals=new_globals,
     )
Esempio n. 2
0
    def _BUILD_init(cls) -> Callable[[], None]:
        # generate init function that set field values from arguments,
        # and that load data from serialized form.
        #
        #
        # The general template that we will be generating is
        #
        #    def __outer__(Model):   # create __init__ closure
        #       __defaults__ = Model._options.defaults
        #       __descr__ = Model._options.descriptors
        #       {% for field in fields_with_defaults %}
        #       _default_{{ field }}_ = __defaults__["{{ field }}"]
        #       {% endfor %}
        #       {% for field in fields_with_init %}
        #       _init_{{ field }}_ = __descr__["{{ field }}"].to_python
        #
        #       def __init__(self, {{ sig }}, *, __strict__=True, **kwargs):
        #          self.__evaluated_fields__ = set()
        #          if __strict__:  # creating model from Python
        #              {% for field in fields %}
        #              self.{{ field }} = {{ field }}
        #              {% endfor %}
        #              if kwargs:
        #                 # raise error for additional arguments
        #          else:
        #              {% for field in fields %}
        #              {% if OPTIONAL_FIELD(field) %}
        #              if {{ field }} is not None:
        #                  self.{{ field }} = _init_{{ field }}({{ field }})
        #              else:
        #                  self.{{ field }} = _default_{{ field }}
        #              {% else %}
        #                  self.{{ field }} = _init_{{ field }}({{ field }}
        #              # any additional kwargs are added as fields
        #              # when loading from serialized data.
        #              self.__dict__.update(kwargs)
        #         self.__post_init__()
        #     return __init__
        #
        options = cls._options
        field_positions = options.fieldpos
        optional = options.optionalset
        needs_validation = options.validation
        descriptors = options.descriptors
        has_post_init = hasattr(cls, '__post_init__')

        closures: Dict[str, str] = {
            '__defaults__': 'Model._options.defaults',
            '__descr__': 'Model._options.descriptors',
        }

        kwonlyargs = ['*', '__strict__=True', '__faust=None', '**kwargs']
        # these are sets, but we care about order as we will
        # be generating signature arguments in the correct order.
        #
        # The order is decided by the order of fields in the class):
        #
        #  class Foo(Record):
        #      c: int
        #      a: int
        #
        # becomes:
        #
        #   def __init__(self, c, a):
        #       self.c = c
        #       self.a = a
        optional_fields: Dict[str, bool] = OrderedDict()
        required_fields: Dict[str, bool] = OrderedDict()

        def generate_setter(field: str, getval: str) -> str:
            """Generate code that sets attribute for field in class.

            Arguments:
                field: Name of field.
                getval: Source code that initializes value for field,
                    can be the field name itself for no initialization
                    or for example: ``f"self._prepare_value({field})"``.
                out: Destination list where new source code lines are added.
                """
            if field in optional:
                optional_fields[field] = True
                default_var = f'_default_{field}_'
                closures[default_var] = f'__defaults__["{field}"]'
                return (f'    self.{field} = {getval} '
                        f'if {field} is not None else {default_var}')
            else:
                required_fields[field] = True
                return f'    self.{field} = {getval}'

        def generate_prepare_value(field: str) -> str:
            descriptor = descriptors[field]
            if descriptor.lazy_coercion:
                return field  # no initialization
            else:
                # call descriptor.to_python
                init_field_var = f'_init_{field}_'
                closures[init_field_var] = f'__descr__["{field}"].to_python'
                return f'{init_field_var}({field})'

        preamble = [
            'self.__evaluated_fields__ = set()',
        ]

        data_setters = ['if __strict__:'] + [
            generate_setter(field, field)
            for field in field_positions.values()
        ]

        data_rest = [
            '    if kwargs:',
            '        from mode.utils.text import pluralize',
            '        message = "{} got unexpected {}: {}".format(',
            '            self.__class__.__name__,',
            '            pluralize(kwargs.__len__(), "argument"),',
            '            ", ".join(map(str, sorted(kwargs))))',
            '        raise TypeError(message)',
        ]

        init_setters = ['else:']
        if field_positions:
            init_setters.extend(
                generate_setter(field, generate_prepare_value(field))
                for field in field_positions.values()
            )
        init_setters.append('    self.__dict__.update(kwargs)')

        postamble = []
        if has_post_init:
            postamble.append('self.__post_init__()')
        if needs_validation:
            postamble.append('self.validate_or_raise()')

        signature = list(chain(
            ['self'],
            [f'{field}' for field in required_fields],
            [f'{field}=None' for field in optional_fields],
            kwonlyargs,
        ))

        sourcecode = codegen.build_closure_source(
            name='__init__',
            args=signature,
            body=list(chain(
                preamble,
                data_setters,
                data_rest,
                init_setters,
                postamble,
            )),
            closures=closures,
            outer_args=['Model'],
        )

        # TIP final sourcecode also available
        # as .__sourcecode__ on returned method
        # (print(Model.__init__.__sourcecode__)
        return codegen.build_closure(
            '__outer__', sourcecode, cls,
            globals={},
            locals={},
        )
Esempio n. 3
0
    def _BUILD_init(cls) -> Callable[[], None]:
        kwonlyargs = ['*', '__strict__=True', '__faust=None', '**kwargs']
        options = cls._options
        field_positions = options.fieldpos
        optional = options.optionalset
        needs_validation = options.validation
        descriptors = options.descriptors
        has_post_init = hasattr(cls, '__post_init__')
        required = []
        opts = []
        preamble = [
            'self.__evaluated_fields__ = set()',
            '__defaults = self._options.defaults',
        ]
        _globals = {}
        _closures = {}

        def generate_setter(field: str, getval: str, out: List[str]) -> bool:
            """Generate code that sets attribute for field in class.

            Arguments:
                field: Name of field.
                getval: Source code that initializes value for field,
                    can be the field name itself for no inititalization
                    or for example: ``f"self._prepare_value({field})"``.
                out: Destination list where new source code lines are added.
                """
            if field in optional:
                out.extend([
                    f'    if {field} is not None:',
                    f'        self.{field} = {getval}',
                    f'    else:',
                    f'        self.{field} = __defaults["{field}"]',
                ])
                return True
            else:
                out.append(f'    self.{field} = {getval}')
                return False

        def generate_prepare_value(field: str) -> str:
            descriptor = descriptors[field]
            if descriptor.lazy_coercion:
                return field  # no initialization
            else:
                # Call descriptor.to_python
                local_field_init_name = f'_{field}_init_'
                global_field_init_name = f'__{field}_init'
                _globals[global_field_init_name] = descriptor.to_python
                _closures[local_field_init_name] = global_field_init_name
                return f'{local_field_init_name}({field})'

        data_setters = ['if __strict__:']
        for field in field_positions.values():
            is_optional = generate_setter(field, field, data_setters)
            if is_optional:
                opts.append(f'{field}=None')
            else:
                required.append(field)

        data_rest = [
            '    if kwargs:',
            '        from mode.utils.text import pluralize',
            '        message = "{} got unexpected {}: {}".format(',
            '            self.__class__.__name__,',
            '            pluralize(kwargs.__len__(), "argument"),',
            '            ", ".join(map(str, sorted(kwargs))))',
            '        raise TypeError(message)',
        ]

        if field_positions:
            init_setters = ['else:']
            for field in field_positions.values():
                fieldval = generate_prepare_value(field)
                generate_setter(field, fieldval, init_setters)
        else:
            # if there are no fields we cannot generate
            # the lines aboe as there will no be no code
            # in the else block.
            init_setters = []

        init_rest = ['self.__dict__.update(kwargs)']
        if has_post_init:
            init_rest.extend([
                'self.__post_init__()',
            ])

        if needs_validation:
            init_rest.extend([
                'self.validate_or_raise()',
            ])

        if _closures:
            source = codegen.build_closure_source(
                name='__init__',
                args=['self'] + required + opts + kwonlyargs,
                closures=_closures,
                body=(preamble + data_setters + data_rest + init_setters +
                      init_rest),
            )
            return codegen.build_closure(
                '__outer__',
                source,
                globals=_globals,
                locals={},
            )
        else:
            return codegen.InitMethod(
                required + opts + kwonlyargs,
                preamble + data_setters + data_rest + init_setters + init_rest,
                globals=_globals,
                locals={},
            )