Exemplo n.º 1
0
    def _get_imports_h(self, data_types):
        """Emits all necessary header file imports for the given Stone data type."""
        if not isinstance(data_types, list):
            data_types = [data_types]

        import_classes = []

        for data_type in data_types:

            if is_user_defined_type(data_type):
                import_classes.append(fmt_class_prefix(data_type))

            for field in data_type.all_fields:
                data_type, _ = unwrap_nullable(field.data_type)

                # unpack list or map
                while is_list_type(data_type) or is_map_type(data_type):
                    data_type = (data_type.value_data_type if
                        is_map_type(data_type) else data_type.data_type)

                if is_user_defined_type(data_type):
                    import_classes.append(fmt_class_prefix(data_type))

        import_classes = list(set(import_classes))
        import_classes.sort()

        return import_classes
Exemplo n.º 2
0
    def _get_imports_m(self, data_types, default_imports):
        """Emits all necessary implementation file imports for the given Stone data type."""
        if not isinstance(data_types, list):
            data_types = [data_types]

        import_classes = default_imports

        for data_type in data_types:
            import_classes.append(fmt_class_prefix(data_type))

            if data_type.parent_type:
                import_classes.append(fmt_class_prefix(data_type.parent_type))

            if is_struct_type(
                    data_type) and data_type.has_enumerated_subtypes():
                for _, subtype in data_type.get_all_subtypes_with_tags():
                    import_classes.append(fmt_class_prefix(subtype))

            for field in data_type.all_fields:
                data_type, _ = unwrap_nullable(field.data_type)

                # unpack list or map
                while is_list_type(data_type) or is_map_type(data_type):
                    data_type = (data_type.value_data_type if
                        is_map_type(data_type) else data_type.data_type)

                if is_user_defined_type(data_type):
                    import_classes.append(fmt_class_prefix(data_type))

        if import_classes:
            import_classes = list(set(import_classes))
            import_classes.sort()

        return import_classes
Exemplo n.º 3
0
    def block_h_from_data_type(self, data_type, protocol=None):
        assert is_user_defined_type(data_type), \
            'Expected user-defined type, got %r' % type(data_type)

        if not protocol:
            extensions = []
            if data_type.parent_type and is_struct_type(data_type):
                extensions.append(fmt_class_prefix(data_type.parent_type))
            else:
                if is_union_type(data_type):
                    # Use a handwritten base class
                    extensions.append('NSObject')
                else:
                    extensions.append('NSObject')

            extend_suffix = ' : {}'.format(
                ', '.join(extensions)) if extensions else ''
        else:
            base = fmt_class_prefix(data_type.parent_type) if (
                data_type.parent_type and
                not is_union_type(data_type)) else 'NSObject'
            extend_suffix = ' : {} <{}>'.format(base, ', '.join(protocol))
        with self.block(
                '@interface {}{}'.format(
                    fmt_class_prefix(data_type), extend_suffix),
                delim=('', '@end'),
                dent=0):
            self.emit()
            yield
Exemplo n.º 4
0
        def _unpack_and_store_data_type(data_type):
            data_type, _ = unwrap_nullable(data_type)
            if is_list_type(data_type):
                while is_list_type(data_type):
                    data_type = data_type.data_type

            if not is_void_type(data_type) and is_user_defined_type(data_type):
                result.append(data_type)
Exemplo n.º 5
0
 def _generate_alias_definition(self, namespace, alias):
     # type: (ApiNamespace, Alias) -> None
     unwrapped_dt, _ = unwrap_aliases(alias)
     if is_user_defined_type(unwrapped_dt):
         # If the alias is to a composite type, we want to alias the
         # generated class as well.
         self.emit('{} = {}'.format(
             alias.name,
             class_name_for_data_type(alias.data_type, namespace)))
Exemplo n.º 6
0
def fmt_serial_obj(data_type):
    data_type, _ = unwrap_nullable(data_type)

    if is_user_defined_type(data_type):
        result = fmt_serial_class(fmt_class_prefix(data_type))
    else:
        result = _serial_table.get(data_type.__class__,
                                   fmt_class(data_type.name))

    return result
Exemplo n.º 7
0
 def _format_type_in_doc(self, namespace, data_type):
     """
     Returns a string that can be recognized by Sphinx as a type reference
     in a docstring.
     """
     if is_void_type(data_type):
         return 'None'
     elif is_user_defined_type(data_type):
         return ':class:`{}.{}.{}`'.format(
             self.args.types_package, namespace.name, fmt_type(data_type))
     else:
         return fmt_type(data_type)
Exemplo n.º 8
0
def fmt_type_name(data_type):
    """
    Returns the JSDoc name for the given data type.
    (Does not attempt to enumerate subtypes.)
    """
    if is_user_defined_type(data_type):
        return fmt_pascal('%s%s' % (data_type.namespace.name, data_type.name))
    else:
        fmted_type = _base_type_table.get(data_type.__class__, 'Object')
        if is_list_type(data_type):
            fmted_type += '.<' + fmt_type(data_type.data_type) + '>'
        return fmted_type
Exemplo n.º 9
0
 def _generate_alias_definition(self, namespace, alias):
     v = generate_validator_constructor(namespace, alias.data_type)
     if alias.doc:
         self.emit_wrapped_text(
             self.process_doc(alias.doc, self._docf), prefix='# ')
     self.emit('{}_validator = {}'.format(alias.name, v))
     unwrapped_dt, _ = unwrap_aliases(alias)
     if is_user_defined_type(unwrapped_dt):
         # If the alias is to a composite type, we want to alias the
         # generated class as well.
         self.emit('{} = {}'.format(
             alias.name,
             class_name_for_data_type(alias.data_type, namespace)))
Exemplo n.º 10
0
 def _class_declaration_for_type(self, ns, data_type):
     assert is_user_defined_type(data_type), \
         'Expected struct, got %r' % type(data_type)
     if data_type.parent_type:
         extends = class_name_for_data_type(data_type.parent_type, ns)
     else:
         if is_union_type(data_type):
             # Use a handwritten base class
             extends = 'bb.Union'
         else:
             extends = 'object'
     return 'class {}({}):'.format(
         class_name_for_data_type(data_type), extends)
Exemplo n.º 11
0
def fmt_type(data_type):
    data_type, nullable = unwrap_nullable(data_type)

    if is_user_defined_type(data_type):
        result = '{}.{}'.format(fmt_class(data_type.namespace.name),
                                fmt_class(data_type.name))
    else:
        result = _type_table.get(data_type.__class__, fmt_class(data_type.name))

        if is_list_type(data_type):
            result = result + '<{}>'.format(fmt_type(data_type.data_type))

    return result if not nullable else result + '?'
Exemplo n.º 12
0
def fmt_route_type(data_type, tag=False, has_default=False):
    data_type, nullable = unwrap_nullable(data_type)

    if is_user_defined_type(data_type):
        result = '{} *'.format(fmt_class_prefix(data_type))
    else:
        result = _primitive_table_user_interface.get(data_type.__class__,
                                                     fmt_class(data_type.name))

        if is_list_type(data_type):
            data_type, _ = unwrap_nullable(data_type.data_type)
            result = result + '<{}> *'.format(fmt_type(data_type))
        elif is_map_type(data_type):
            data_type, _ = unwrap_nullable(data_type.value_data_type)
            result = result + '<NSString *, {}>'.format(fmt_type(data_type))

    if is_user_defined_type(data_type) and tag:
        if nullable or has_default:
            result = 'nullable ' + result
        elif not is_void_type(data_type):
            result += ''

    return result
Exemplo n.º 13
0
def class_name_for_data_type(data_type, ns=None):
    """
    Returns the name of the Python class that maps to a user-defined type.
    The name is identical to the name in the spec.

    If ``ns`` is set to a Namespace and the namespace of `data_type` does
    not match, then a namespace prefix is added to the returned name.
    For example, ``foreign_ns.TypeName``.
    """
    assert is_user_defined_type(data_type) or is_alias(data_type), \
        'Expected composite type, got %r' % type(data_type)
    name = fmt_class(data_type.name)
    if ns:
        return prefix_with_ns_if_necessary(name, data_type.namespace, ns)
    return name
Exemplo n.º 14
0
    def _generate_union_class(self, ns, data_type):
        # type: (ApiNamespace, Union) -> None
        """Defines a Python class that represents a union in Stone."""
        self.emit(self._class_declaration_for_type(ns, data_type))
        with self.indent():
            self.emit('"""')
            if data_type.doc:
                self.emit_wrapped_text(
                    self.process_doc(data_type.doc, self._docf))
                self.emit()

            self.emit_wrapped_text(
                'This class acts as a tagged union. Only one of the ``is_*`` '
                'methods will return true. To get the associated value of a '
                'tag (if one exists), use the corresponding ``get_*`` method.')

            if data_type.has_documented_fields():
                self.emit()

            for field in data_type.fields:
                if not field.doc:
                    continue
                if is_void_type(field.data_type):
                    ivar_doc = ':ivar {}: {}'.format(
                        fmt_var(field.name),
                        self.process_doc(field.doc, self._docf))
                elif is_user_defined_type(field.data_type):
                    ivar_doc = ':ivar {} {}: {}'.format(
                        fmt_class(field.data_type.name),
                        fmt_var(field.name),
                        self.process_doc(field.doc, self._docf))
                else:
                    ivar_doc = ':ivar {} {}: {}'.format(
                        self._python_type_mapping(ns, field.data_type),
                        fmt_var(field.name), field.doc)
                self.emit_wrapped_text(ivar_doc, subsequent_prefix='    ')
            self.emit('"""')
            self.emit()

            self._generate_union_class_vars(data_type)
            self._generate_union_class_variant_creators(ns, data_type)
            self._generate_union_class_is_set(data_type)
            self._generate_union_class_get_helpers(ns, data_type)
            self._generate_union_class_repr(data_type)
        self.emit('{0}_validator = bv.Union({0})'.format(
            class_name_for_data_type(data_type)
        ))
        self.emit()
Exemplo n.º 15
0
def fmt_type_name(data_type, inside_namespace=None):
    """
    Produces a TypeScript type name for the given data type.
    inside_namespace should be set to the namespace that the reference
    occurs in, or None if this parameter is not relevant.
    """
    if is_user_defined_type(data_type) or is_alias(data_type):
        if data_type.namespace == inside_namespace:
            return data_type.name
        else:
            return '%s.%s' % (data_type.namespace.name, data_type.name)
    else:
        fmted_type = _base_type_table.get(data_type.__class__, 'Object')
        if is_list_type(data_type):
            fmted_type += '<' + fmt_type(data_type.data_type, inside_namespace) + '>'
        return fmted_type
Exemplo n.º 16
0
def class_name_for_data_type(data_type, ns=None):
    """
    Returns the name of the Python class that maps to a user-defined type.
    The name is identical to the name in the spec.

    If ``ns`` is set to a Namespace and the namespace of `data_type` does
    not match, then a namespace prefix is added to the returned name.
    For example, ``foreign_ns.TypeName``.
    """
    assert is_user_defined_type(data_type) or is_alias(data_type), \
        'Expected composite type, got %r' % type(data_type)
    name = fmt_class(data_type.name)
    if ns and data_type.namespace != ns:
        # If from an imported namespace, add a namespace prefix.
        name = '{}.{}'.format(data_type.namespace.name, name)
    return name
Exemplo n.º 17
0
def fmt_serial_obj(data_type):
    data_type, nullable = unwrap_nullable(data_type)

    if is_user_defined_type(data_type):
        result = '{}.{}Serializer()'
        result = result.format(fmt_class(data_type.namespace.name),
            fmt_class(data_type.name))
    else:
        result = _serial_type_table.get(data_type.__class__, fmt_class(data_type.name))

        if is_list_type(data_type):
            result = result + '({})'.format(fmt_serial_obj(data_type.data_type))
        elif is_timestamp_type(data_type):
            result = result + '("{}")'.format(data_type.format)
        else:
            result = 'Serialization._{}'.format(result)

    return result if not nullable else 'NullableSerializer({})'.format(result)
Exemplo n.º 18
0
def fmt_class_type(data_type, suppress_ptr=False):
    data_type, _ = unwrap_nullable(data_type)

    if is_user_defined_type(data_type):
        result = '{}'.format(fmt_class_prefix(data_type))
    else:
        result = _primitive_table.get(data_type.__class__,
                                      fmt_class(data_type.name))

        if suppress_ptr:
            result = result.replace(' *', '')
            result = result.replace('*', '')

        if is_list_type(data_type):
            data_type, _ = unwrap_nullable(data_type.data_type)
            result = result + '<{}>'.format(fmt_type(data_type))
        elif is_map_type(data_type):
            data_type, _ = unwrap_nullable(data_type.value_data_type)
            result = result + '<NSString *, {}>'.format(fmt_type(data_type))
    return result
Exemplo n.º 19
0
 def _generate_route_method_decl(
         self, namespace, route, arg_data_type, request_binary_body,
         method_name_suffix=None, extra_args=None):
     """Generates the method prototype for a route."""
     method_name = fmt_func(route.name)
     namespace_name = fmt_func(namespace.name)
     if method_name_suffix:
         method_name += method_name_suffix
     args = ['self']
     if extra_args:
         args += extra_args
     if request_binary_body:
         args.append('f')
     if is_struct_type(arg_data_type):
         for field in arg_data_type.all_fields:
             if is_nullable_type(field.data_type):
                 args.append('{}=None'.format(field.name))
             elif field.has_default:
                 # TODO(kelkabany): Decide whether we really want to set the
                 # default in the argument list. This will send the default
                 # over the wire even if it isn't overridden. The benefit is
                 # it locks in a default even if it is changed server-side.
                 if is_user_defined_type(field.data_type):
                     ns = field.data_type.namespace
                 else:
                     ns = None
                 arg = '{}={}'.format(
                     field.name,
                     self._generate_python_value(ns, field.default))
                 args.append(arg)
             else:
                 args.append(field.name)
     elif is_union_type(arg_data_type):
         args.append('arg')
     elif not is_void_type(arg_data_type):
         raise AssertionError('Unhandled request type: %r' %
                              arg_data_type)
     self.generate_multiline_list(
         args, 'def {}_{}'.format(namespace_name, method_name), ':')
Exemplo n.º 20
0
    def _generate_route_method_decl(
            self, namespace, route, arg_data_type, request_binary_body,
            method_name_suffix='', extra_args=None):
        """Generates the method prototype for a route."""
        args = ['self']
        if extra_args:
            args += extra_args
        if request_binary_body:
            args.append('f')
        if is_struct_type(arg_data_type):
            for field in arg_data_type.all_fields:
                if is_nullable_type(field.data_type):
                    args.append('{}=None'.format(field.name))
                elif field.has_default:
                    # TODO(kelkabany): Decide whether we really want to set the
                    # default in the argument list. This will send the default
                    # over the wire even if it isn't overridden. The benefit is
                    # it locks in a default even if it is changed server-side.
                    if is_user_defined_type(field.data_type):
                        ns = field.data_type.namespace
                    else:
                        ns = None
                    arg = '{}={}'.format(
                        field.name,
                        self._generate_python_value(ns, field.default))
                    args.append(arg)
                else:
                    args.append(field.name)
        elif is_union_type(arg_data_type):
            args.append('arg')
        elif not is_void_type(arg_data_type):
            raise AssertionError('Unhandled request type: %r' %
                                 arg_data_type)

        method_name = fmt_func(route.name + method_name_suffix, version=route.version)
        namespace_name = fmt_underscores(namespace.name)
        self.generate_multiline_list(args, 'def {}_{}'.format(namespace_name, method_name), ':')
Exemplo n.º 21
0
def fmt_type(data_type, tag=False, has_default=False, no_ptr=False, is_prop=False):
    data_type, nullable = unwrap_nullable(data_type)

    if is_user_defined_type(data_type):
        base = '{}' if no_ptr else '{} *'
        result = base.format(fmt_class_prefix(data_type))
    else:
        result = _primitive_table.get(data_type.__class__,
                                      fmt_class(data_type.name))

        if is_list_type(data_type):
            data_type, _ = unwrap_nullable(data_type.data_type)
            base = '<{}>' if no_ptr else '<{}> *'
            result = result + base.format(fmt_type(data_type))
        elif is_map_type(data_type):
            data_type, _ = unwrap_nullable(data_type.value_data_type)
            base = '<NSString *, {}>' if no_ptr else '<NSString *, {}> *'
            result = result + base.format(fmt_type(data_type))

    if tag:
        if (nullable or has_default) and not is_prop:
            result = 'nullable ' + result

    return result
Exemplo n.º 22
0
    def _generate_struct_class_properties(self, ns, data_type):
        """
        Each field of the struct has a corresponding setter and getter.
        The setter validates the value being set.
        """
        for field in data_type.fields:
            field_name = fmt_func(field.name, check_reserved=True)
            if is_nullable_type(field.data_type):
                field_dt = field.data_type.data_type
                dt_nullable = True
            else:
                field_dt = field.data_type
                dt_nullable = False

            # generate getter for field
            args = '"{}"'.format(field_name)
            if dt_nullable:
                args += ", nullable=True"
            if is_user_defined_type(field_dt):
                args += ", user_defined=True"
            self.emit('# Instance attribute type: {} (validator is set below)'.
                      format(self._python_type_mapping(ns, field_dt)))
            self.emit("{} = bb.Attribute({})".format(field_name, args))
            self.emit()
Exemplo n.º 23
0
def generate_validator_constructor(ns, data_type):
    """
    Given a Stone data type, returns a string that can be used to construct
    the appropriate validation object in Python.
    """
    dt, nullable_dt = unwrap_nullable(data_type)
    if is_list_type(dt):
        v = generate_func_call(
            'bv.List',
            args=[
                generate_validator_constructor(ns, dt.data_type)],
            kwargs=[
                ('min_items', dt.min_items),
                ('max_items', dt.max_items)],
        )
    elif is_map_type(dt):
        v = generate_func_call(
            'bv.Map',
            args=[
                generate_validator_constructor(ns, dt.key_data_type),
                generate_validator_constructor(ns, dt.value_data_type),
            ]
        )
    elif is_numeric_type(dt):
        v = generate_func_call(
            'bv.{}'.format(dt.name),
            kwargs=[
                ('min_value', dt.min_value),
                ('max_value', dt.max_value)],
        )
    elif is_string_type(dt):
        pattern = None
        if dt.pattern is not None:
            pattern = repr(dt.pattern)
        v = generate_func_call(
            'bv.String',
            kwargs=[
                ('min_length', dt.min_length),
                ('max_length', dt.max_length),
                ('pattern', pattern)],
        )
    elif is_timestamp_type(dt):
        v = generate_func_call(
            'bv.Timestamp',
            args=[repr(dt.format)],
        )
    elif is_user_defined_type(dt):
        v = fmt_class(dt.name) + '_validator'
        if ns.name != dt.namespace.name:
            v = '{}.{}'.format(dt.namespace.name, v)
    elif is_alias(dt):
        # Assume that the alias has already been declared elsewhere.
        name = fmt_class(dt.name) + '_validator'
        if ns.name != dt.namespace.name:
            name = '{}.{}'.format(dt.namespace.name, name)
        v = name
    elif is_boolean_type(dt) or is_bytes_type(dt) or is_void_type(dt):
        v = generate_func_call('bv.{}'.format(dt.name))
    else:
        raise AssertionError('Unsupported data type: %r' % dt)

    if nullable_dt:
        return generate_func_call('bv.Nullable', args=[v])
    else:
        return v
Exemplo n.º 24
0
    def _generate_docstring_for_func(self, namespace, arg_data_type,
                                     result_data_type=None, error_data_type=None,
                                     overview=None, extra_request_args=None,
                                     extra_return_arg=None, footer=None):
        """
        Generates a docstring for a function or method.

        This function is versatile. It will create a docstring using all the
        data that is provided.

        :param arg_data_type: The data type describing the argument to the
            route. The data type should be a struct, and each field will be
            treated as an input parameter of the method.
        :param result_data_type: The data type of the route result.
        :param error_data_type: The data type of the route result in the case
            of an error.
        :param str overview: A description of the route that will be located
            at the top of the docstring.
        :param extra_request_args: [(field name, field type, field doc), ...]
            Describes any additional parameters for the method that aren't a
            field in arg_data_type.
        :param str extra_return_arg: Name of an additional return type that. If
            this is specified, it is assumed that the return of the function
            will be a tuple of return_data_type and extra_return-arg.
        :param str footer: Additional notes at the end of the docstring.
        """
        fields = [] if is_void_type(arg_data_type) else arg_data_type.fields
        if not fields and not overview:
            # If we don't have an overview or any input parameters, we skip the
            # docstring altogether.
            return

        self.emit('"""')
        if overview:
            self.emit_wrapped_text(overview)

        # Description of all input parameters
        if extra_request_args or fields:
            if overview:
                # Add a blank line if we had an overview
                self.emit()

            if extra_request_args:
                for name, data_type_name, doc in extra_request_args:
                    if data_type_name:
                        field_doc = ':param {} {}: {}'.format(data_type_name,
                                                              name, doc)
                        self.emit_wrapped_text(field_doc,
                                               subsequent_prefix='    ')
                    else:
                        self.emit_wrapped_text(
                            ':param {}: {}'.format(name, doc),
                            subsequent_prefix='    ')

            if is_struct_type(arg_data_type):
                for field in fields:
                    if field.doc:
                        if is_user_defined_type(field.data_type):
                            field_doc = ':param {}: {}'.format(
                                field.name, self.process_doc(field.doc, self._docf))
                        else:
                            field_doc = ':param {} {}: {}'.format(
                                self._format_type_in_doc(namespace, field.data_type),
                                field.name,
                                self.process_doc(field.doc, self._docf),
                            )
                        self.emit_wrapped_text(
                            field_doc, subsequent_prefix='    ')
                        if is_user_defined_type(field.data_type):
                            # It's clearer to declare the type of a composite on
                            # a separate line since it references a class in
                            # another module
                            self.emit(':type {}: {}'.format(
                                field.name,
                                self._format_type_in_doc(namespace, field.data_type),
                            ))
                    else:
                        # If the field has no docstring, then just document its
                        # type.
                        field_doc = ':type {}: {}'.format(
                            field.name,
                            self._format_type_in_doc(namespace, field.data_type),
                        )
                        self.emit_wrapped_text(field_doc)

            elif is_union_type(arg_data_type):
                if arg_data_type.doc:
                    self.emit_wrapped_text(':param arg: {}'.format(
                        self.process_doc(arg_data_type.doc, self._docf)),
                        subsequent_prefix='    ')
                self.emit(':type arg: {}'.format(
                    self._format_type_in_doc(namespace, arg_data_type)))

        if overview and not (extra_request_args or fields):
            # Only output an empty line if we had an overview and haven't
            # started a section on declaring types.
            self.emit()

        if extra_return_arg:
            # Special case where the function returns a tuple. The first
            # element is the JSON response. The second element is the
            # the extra_return_arg param.
            args = []
            if is_void_type(result_data_type):
                args.append('None')
            else:
                rtype = self._format_type_in_doc(namespace,
                                                 result_data_type)
                args.append(rtype)
            args.append(extra_return_arg)
            self.generate_multiline_list(args, ':rtype: ')
        else:
            if is_void_type(result_data_type):
                self.emit(':rtype: None')
            else:
                rtype = self._format_type_in_doc(namespace, result_data_type)
                self.emit(':rtype: {}'.format(rtype))

        if not is_void_type(error_data_type) and error_data_type.fields:
            self.emit(':raises: :class:`{}`'.format(self.args.error_class_path))
            self.emit()
            # To provide more clarity to a dev who reads the docstring, suggest
            # the route's error class. This is confusing, however, because we
            # don't know where the error object that's raised will store
            # the more detailed route error defined in stone.
            error_class_name = self.args.error_class_path.rsplit('.', 1)[-1]
            self.emit('If this raises, {} will contain:'.format(error_class_name))
            with self.indent():
                self.emit(self._format_type_in_doc(namespace, error_data_type))

        if footer:
            self.emit()
            self.emit_wrapped_text(footer)
        self.emit('"""')
Exemplo n.º 25
0
def generate_validator_constructor(ns, data_type):
    """
    Given a Stone data type, returns a string that can be used to construct
    the appropriate validation object in Python.
    """
    dt, nullable_dt = unwrap_nullable(data_type)
    if is_list_type(dt):
        v = generate_func_call(
            'bv.List',
            args=[
                generate_validator_constructor(ns, dt.data_type)],
            kwargs=[
                ('min_items', dt.min_items),
                ('max_items', dt.max_items)],
        )
    elif is_map_type(dt):
        v = generate_func_call(
            'bv.Map',
            args=[
                generate_validator_constructor(ns, dt.key_data_type),
                generate_validator_constructor(ns, dt.value_data_type),
            ]
        )
    elif is_numeric_type(dt):
        v = generate_func_call(
            'bv.{}'.format(dt.name),
            kwargs=[
                ('min_value', dt.min_value),
                ('max_value', dt.max_value)],
        )
    elif is_string_type(dt):
        pattern = None
        if dt.pattern is not None:
            pattern = repr(dt.pattern)
        v = generate_func_call(
            'bv.String',
            kwargs=[
                ('min_length', dt.min_length),
                ('max_length', dt.max_length),
                ('pattern', pattern)],
        )
    elif is_timestamp_type(dt):
        v = generate_func_call(
            'bv.Timestamp',
            args=[repr(dt.format)],
        )
    elif is_user_defined_type(dt):
        v = fmt_class(dt.name) + '_validator'
        if ns.name != dt.namespace.name:
            v = '{}.{}'.format(dt.namespace.name, v)
    elif is_alias(dt):
        # Assume that the alias has already been declared elsewhere.
        name = fmt_class(dt.name) + '_validator'
        if ns.name != dt.namespace.name:
            name = '{}.{}'.format(dt.namespace.name, name)
        v = name
    elif is_boolean_type(dt) or is_bytes_type(dt) or is_void_type(dt):
        v = generate_func_call('bv.{}'.format(dt.name))
    else:
        raise AssertionError('Unsupported data type: %r' % dt)

    if nullable_dt:
        return generate_func_call('bv.Nullable', args=[v])
    else:
        return v
Exemplo n.º 26
0
    def _generate_struct_class_properties(self, ns, data_type):
        """
        Each field of the struct has a corresponding setter and getter.
        The setter validates the value being set.
        """
        for field in data_type.fields:
            field_name = fmt_func(field.name)
            field_name_reserved_check = fmt_func(field.name, True)
            if is_nullable_type(field.data_type):
                field_dt = field.data_type.data_type
                dt_nullable = True
            else:
                field_dt = field.data_type
                dt_nullable = False

            # generate getter for field
            self.emit('@property')
            self.emit('def {}(self):'.format(field_name_reserved_check))
            with self.indent():
                self.emit('"""')
                if field.doc:
                    self.emit_wrapped_text(
                        self.process_doc(field.doc, self._docf))
                    # Sphinx wants an extra line between the text and the
                    # rtype declaration.
                    self.emit()
                self.emit(':rtype: {}'.format(
                    self._python_type_mapping(ns, field_dt)))
                self.emit('"""')
                self.emit('if self._{}_present:'.format(field_name))
                with self.indent():
                    self.emit('return self._{}_value'.format(field_name))

                self.emit('else:')
                with self.indent():
                    if dt_nullable:
                        self.emit('return None')
                    elif field.has_default:
                        self.emit('return {}'.format(
                            self._generate_python_value(ns, field.default)))
                    else:
                        self.emit(
                            "raise AttributeError(\"missing required field '%s'\")"
                            % field_name
                        )
            self.emit()

            # generate setter for field
            self.emit('@{}.setter'.format(field_name_reserved_check))
            self.emit('def {}(self, val):'.format(field_name_reserved_check))
            with self.indent():
                if dt_nullable:
                    self.emit('if val is None:')
                    with self.indent():
                        self.emit('del self.{}'.format(field_name_reserved_check))
                        self.emit('return')
                if is_user_defined_type(field_dt):
                    self.emit('self._%s_validator.validate_type_only(val)' %
                              field_name)
                else:
                    self.emit('val = self._{}_validator.validate(val)'.format(field_name))
                self.emit('self._{}_value = val'.format(field_name))
                self.emit('self._{}_present = True'.format(field_name))
            self.emit()

            # generate deleter for field
            self.emit('@{}.deleter'.format(field_name_reserved_check))
            self.emit('def {}(self):'.format(field_name_reserved_check))
            with self.indent():
                self.emit('self._{}_value = None'.format(field_name))
                self.emit('self._{}_present = False'.format(field_name))
            self.emit()
Exemplo n.º 27
0
    def _generate_struct_class_properties(self, ns, data_type):
        """
        Each field of the struct has a corresponding setter and getter.
        The setter validates the value being set.
        """
        for field in data_type.fields:
            field_name = fmt_func(field.name)
            field_name_reserved_check = fmt_func(field.name, True)
            if is_nullable_type(field.data_type):
                field_dt = field.data_type.data_type
                dt_nullable = True
            else:
                field_dt = field.data_type
                dt_nullable = False

            # generate getter for field
            self.emit('@property')
            self.emit('def {}(self):'.format(field_name_reserved_check))
            with self.indent():
                self.emit('"""')
                if field.doc:
                    self.emit_wrapped_text(
                        self.process_doc(field.doc, self._docf))
                    # Sphinx wants an extra line between the text and the
                    # rtype declaration.
                    self.emit()
                self.emit(':rtype: {}'.format(
                    self._python_type_mapping(ns, field_dt)))
                self.emit('"""')
                self.emit('if self._{}_present:'.format(field_name))
                with self.indent():
                    self.emit('return self._{}_value'.format(field_name))

                self.emit('else:')
                with self.indent():
                    if dt_nullable:
                        self.emit('return None')
                    elif field.has_default:
                        self.emit('return {}'.format(
                            self._generate_python_value(ns, field.default)))
                    else:
                        self.emit(
                            "raise AttributeError(\"missing required field '%s'\")"
                            % field_name)
            self.emit()

            # generate setter for field
            self.emit('@{}.setter'.format(field_name_reserved_check))
            self.emit('def {}(self, val):'.format(field_name_reserved_check))
            with self.indent():
                if dt_nullable:
                    self.emit('if val is None:')
                    with self.indent():
                        self.emit(
                            'del self.{}'.format(field_name_reserved_check))
                        self.emit('return')
                if is_user_defined_type(field_dt):
                    self.emit('self._%s_validator.validate_type_only(val)' %
                              field_name)
                else:
                    self.emit('val = self._{}_validator.validate(val)'.format(
                        field_name))
                self.emit('self._{}_value = val'.format(field_name))
                self.emit('self._{}_present = True'.format(field_name))
            self.emit()

            # generate deleter for field
            self.emit('@{}.deleter'.format(field_name_reserved_check))
            self.emit('def {}(self):'.format(field_name_reserved_check))
            with self.indent():
                self.emit('self._{}_value = None'.format(field_name))
                self.emit('self._{}_present = False'.format(field_name))
            self.emit()
Exemplo n.º 28
0
    def _parse_extra_args(self, api, extra_args_raw):
        """
        Parses extra arguments into a map keyed on particular data types.
        """
        extra_args = {}

        def invalid(msg, extra_arg_raw):
            print('Invalid --extra-arg:%s: %s' % (msg, extra_arg_raw),
                  file=sys.stderr)
            sys.exit(1)

        for extra_arg_raw in extra_args_raw:
            try:
                extra_arg = json.loads(extra_arg_raw)
            except ValueError as e:
                invalid(str(e), extra_arg_raw)

            # Validate extra_arg JSON blob
            if 'match' not in extra_arg:
                invalid('No match key', extra_arg_raw)
            elif (not isinstance(extra_arg['match'], list) or
                  len(extra_arg['match']) != 2):
                invalid('match key is not a list of two strings', extra_arg_raw)
            elif (not isinstance(extra_arg['match'][0], six.text_type) or
                  not isinstance(extra_arg['match'][1], six.text_type)):
                print(type(extra_arg['match'][0]))
                invalid('match values are not strings', extra_arg_raw)
            elif 'arg_name' not in extra_arg:
                invalid('No arg_name key', extra_arg_raw)
            elif not isinstance(extra_arg['arg_name'], six.text_type):
                invalid('arg_name is not a string', extra_arg_raw)
            elif 'arg_type' not in extra_arg:
                invalid('No arg_type key', extra_arg_raw)
            elif not isinstance(extra_arg['arg_type'], six.text_type):
                invalid('arg_type is not a string', extra_arg_raw)
            elif ('arg_docstring' in extra_arg and
                  not isinstance(extra_arg['arg_docstring'], six.text_type)):
                invalid('arg_docstring is not a string', extra_arg_raw)

            attr_key, attr_val = extra_arg['match'][0], extra_arg['match'][1]
            extra_args.setdefault(attr_key, {})[attr_val] = \
                (extra_arg['arg_name'], extra_arg['arg_type'],
                 extra_arg.get('arg_docstring'))

        # Extra arguments, keyed on data type objects.
        extra_args_for_types = {}
        # Locate data types that contain extra arguments
        for namespace in api.namespaces.values():
            for route in namespace.routes:
                extra_parameters = []
                if is_user_defined_type(route.arg_data_type):
                    for attr_key in route.attrs:
                        if attr_key not in extra_args:
                            continue
                        attr_val = route.attrs[attr_key]
                        if attr_val in extra_args[attr_key]:
                            extra_parameters.append(extra_args[attr_key][attr_val])
                if len(extra_parameters) > 0:
                    extra_args_for_types[route.arg_data_type] = extra_parameters

        return extra_args_for_types
Exemplo n.º 29
0
def map_stone_type_to_python_type(ns, data_type, override_dict=None):
    # type: (ApiNamespace, DataType, typing.Optional[OverrideDefaultTypesDict]) -> typing.Text
    """
    Args:
        override_dict: lets you override the default behavior for a given type by hooking into
            a callback. (Currently only hooked up for stone's List and Nullable)
    """
    override_dict = override_dict or {}

    if is_string_type(data_type):
        string_override = override_dict.get(String, None)
        if string_override:
            return string_override(ns, data_type, override_dict)
        return 'str'
    elif is_bytes_type(data_type):
        return 'bytes'
    elif is_boolean_type(data_type):
        return 'bool'
    elif is_float_type(data_type):
        return 'float'
    elif is_integer_type(data_type):
        return 'long'
    elif is_void_type(data_type):
        return 'None'
    elif is_timestamp_type(data_type):
        timestamp_override = override_dict.get(Timestamp, None)
        if timestamp_override:
            return timestamp_override(ns, data_type, override_dict)
        return 'datetime.datetime'
    elif is_alias(data_type):
        alias_type = cast(Alias, data_type)
        return map_stone_type_to_python_type(ns, alias_type.data_type,
                                             override_dict)
    elif is_user_defined_type(data_type):
        user_defined_type = cast(UserDefined, data_type)
        class_name = class_name_for_data_type(user_defined_type)
        if user_defined_type.namespace.name != ns.name:
            return '%s.%s_validator' % (user_defined_type.namespace.name,
                                        class_name)
        else:
            return class_name
    elif is_list_type(data_type):
        list_type = cast(List, data_type)
        if List in override_dict:
            return override_dict[List](ns, list_type.data_type, override_dict)

        # PyCharm understands this description format for a list
        return 'list of [{}]'.format(
            map_stone_type_to_python_type(ns, list_type.data_type,
                                          override_dict))
    elif is_map_type(data_type):
        map_type = cast(Map, data_type)
        if Map in override_dict:
            return override_dict[Map](ns, data_type, override_dict)

        return 'dict of [{}:{}]'.format(
            map_stone_type_to_python_type(ns, map_type.key_data_type,
                                          override_dict),
            map_stone_type_to_python_type(ns, map_type.value_data_type,
                                          override_dict))

    elif is_nullable_type(data_type):
        nullable_type = cast(Nullable, data_type)
        if Nullable in override_dict:
            return override_dict[Nullable](ns, nullable_type.data_type,
                                           override_dict)

        return 'Optional[{}]'.format(
            map_stone_type_to_python_type(ns, nullable_type.data_type,
                                          override_dict))
    else:
        raise TypeError('Unknown data type %r' % data_type)
Exemplo n.º 30
0
    def _generate_docstring_for_func(self, namespace, arg_data_type,
                                     result_data_type=None, error_data_type=None,
                                     overview=None, extra_request_args=None,
                                     extra_return_arg=None, footer=None):
        """
        Generates a docstring for a function or method.

        This function is versatile. It will create a docstring using all the
        data that is provided.

        :param arg_data_type: The data type describing the argument to the
            route. The data type should be a struct, and each field will be
            treated as an input parameter of the method.
        :param result_data_type: The data type of the route result.
        :param error_data_type: The data type of the route result in the case
            of an error.
        :param str overview: A description of the route that will be located
            at the top of the docstring.
        :param extra_request_args: [(field name, field type, field doc), ...]
            Describes any additional parameters for the method that aren't a
            field in arg_data_type.
        :param str extra_return_arg: Name of an additional return type that. If
            this is specified, it is assumed that the return of the function
            will be a tuple of return_data_type and extra_return-arg.
        :param str footer: Additional notes at the end of the docstring.
        """
        fields = [] if is_void_type(arg_data_type) else arg_data_type.fields
        if not fields and not overview:
            # If we don't have an overview or any input parameters, we skip the
            # docstring altogether.
            return

        self.emit('"""')
        if overview:
            self.emit_wrapped_text(overview)

        # Description of all input parameters
        if extra_request_args or fields:
            if overview:
                # Add a blank line if we had an overview
                self.emit()

            if extra_request_args:
                for name, data_type_name, doc in extra_request_args:
                    if data_type_name:
                        field_doc = ':param {} {}: {}'.format(data_type_name,
                                                              name, doc)
                        self.emit_wrapped_text(field_doc,
                                               subsequent_prefix='    ')
                    else:
                        self.emit_wrapped_text(
                            ':param {}: {}'.format(name, doc),
                            subsequent_prefix='    ')

            if is_struct_type(arg_data_type):
                for field in fields:
                    if field.doc:
                        if is_user_defined_type(field.data_type):
                            field_doc = ':param {}: {}'.format(
                                field.name, self.process_doc(field.doc, self._docf))
                        else:
                            field_doc = ':param {} {}: {}'.format(
                                self._format_type_in_doc(namespace, field.data_type),
                                field.name,
                                self.process_doc(field.doc, self._docf),
                            )
                        self.emit_wrapped_text(
                            field_doc, subsequent_prefix='    ')
                        if is_user_defined_type(field.data_type):
                            # It's clearer to declare the type of a composite on
                            # a separate line since it references a class in
                            # another module
                            self.emit(':type {}: {}'.format(
                                field.name,
                                self._format_type_in_doc(namespace, field.data_type),
                            ))
                    else:
                        # If the field has no docstring, then just document its
                        # type.
                        field_doc = ':type {}: {}'.format(
                            field.name,
                            self._format_type_in_doc(namespace, field.data_type),
                        )
                        self.emit_wrapped_text(field_doc)

            elif is_union_type(arg_data_type):
                if arg_data_type.doc:
                    self.emit_wrapped_text(':param arg: {}'.format(
                        self.process_doc(arg_data_type.doc, self._docf)),
                        subsequent_prefix='    ')
                self.emit(':type arg: {}'.format(
                    self._format_type_in_doc(namespace, arg_data_type)))

        if overview and not (extra_request_args or fields):
            # Only output an empty line if we had an overview and haven't
            # started a section on declaring types.
            self.emit()

        if extra_return_arg:
            # Special case where the function returns a tuple. The first
            # element is the JSON response. The second element is the
            # the extra_return_arg param.
            args = []
            if is_void_type(result_data_type):
                args.append('None')
            else:
                rtype = self._format_type_in_doc(namespace,
                                                 result_data_type)
                args.append(rtype)
            args.append(extra_return_arg)
            self.generate_multiline_list(args, ':rtype: ')
        else:
            if is_void_type(result_data_type):
                self.emit(':rtype: None')
            else:
                rtype = self._format_type_in_doc(namespace, result_data_type)
                self.emit(':rtype: {}'.format(rtype))

        if not is_void_type(error_data_type) and error_data_type.fields:
            self.emit(':raises: :class:`{}`'.format(self.args.error_class_path))
            self.emit()
            # To provide more clarity to a dev who reads the docstring, suggest
            # the route's error class. This is confusing, however, because we
            # don't know where the error object that's raised will store
            # the more detailed route error defined in stone.
            error_class_name = self.args.error_class_path.rsplit('.', 1)[-1]
            self.emit('If this raises, {} will contain:'.format(error_class_name))
            with self.indent():
                self.emit(self._format_type_in_doc(namespace, error_data_type))

        if footer:
            self.emit()
            self.emit_wrapped_text(footer)
        self.emit('"""')
Exemplo n.º 31
0
def map_stone_type_to_python_type(ns, data_type, override_dict=None):
    # type: (ApiNamespace, DataType, typing.Optional[OverrideDefaultTypesDict]) -> str
    """
    Args:
        override_dict: lets you override the default behavior for a given type by hooking into
            a callback. (Currently only hooked up for stone's List and Nullable)
    """
    override_dict = override_dict or {}

    if is_string_type(data_type):
        return 'str'
    elif is_bytes_type(data_type):
        return 'bytes'
    elif is_boolean_type(data_type):
        return 'bool'
    elif is_float_type(data_type):
        return 'float'
    elif is_integer_type(data_type):
        return 'long'
    elif is_void_type(data_type):
        return 'None'
    elif is_timestamp_type(data_type):
        timestamp_override = override_dict.get(Timestamp, None)
        if timestamp_override:
            return timestamp_override(ns, data_type, override_dict)
        return 'datetime.datetime'
    elif is_alias(data_type):
        alias_type = cast(Alias, data_type)
        return map_stone_type_to_python_type(ns, alias_type.data_type, override_dict)
    elif is_user_defined_type(data_type):
        user_defined_type = cast(UserDefined, data_type)
        class_name = class_name_for_data_type(user_defined_type)
        if user_defined_type.namespace.name != ns.name:
            return '%s.%s_validator' % (
                user_defined_type.namespace.name, class_name)
        else:
            return class_name
    elif is_list_type(data_type):
        list_type = cast(List, data_type)
        if List in override_dict:
            return override_dict[List](ns, list_type.data_type, override_dict)

        # PyCharm understands this description format for a list
        return 'list of [{}]'.format(
            map_stone_type_to_python_type(ns, list_type.data_type, override_dict)
        )
    elif is_map_type(data_type):
        map_type = cast(Map, data_type)
        if Map in override_dict:
            return override_dict[Map](
                ns,
                data_type,
                override_dict
            )

        return 'dict of [{}:{}]'.format(
            map_stone_type_to_python_type(ns, map_type.key_data_type, override_dict),
            map_stone_type_to_python_type(ns, map_type.value_data_type, override_dict)
        )

    elif is_nullable_type(data_type):
        nullable_type = cast(Nullable, data_type)
        if Nullable in override_dict:
            return override_dict[Nullable](ns, nullable_type.data_type, override_dict)

        return 'Optional[{}]'.format(
            map_stone_type_to_python_type(ns, nullable_type.data_type, override_dict)
        )
    else:
        raise TypeError('Unknown data type %r' % data_type)