def _generate_union_serializer(self, data_type): with self.serializer_block(data_type): with self.serializer_func(data_type), self.block('switch value'): for field in data_type.all_fields: field_type = field.data_type case = '.{}{}'.format(fmt_var(field.name), '' if is_void_type(field_type) else '(let arg)') self.emit('case {}:'.format(case)) with self.indent(): if is_void_type(field_type): self.emit('var d = [String: JSON]()') elif (is_struct_type(field_type) and not field_type.has_enumerated_subtypes()): self.emit('var d = Serialization.getFields({}.serialize(arg))'.format( fmt_serial_obj(field_type))) else: self.emit('var d = ["{}": {}.serialize(arg)]'.format( field.name, fmt_serial_obj(field_type))) self.emit('d[".tag"] = .str("{}")'.format(field.name)) self.emit('return .dictionary(d)') with self.deserializer_func(data_type): with self.block("switch json"): self.emit("case .dictionary(let d):") with self.indent(): self.emit('let tag = Serialization.getTag(d)') with self.block('switch tag'): for field in data_type.all_fields: field_type = field.data_type self.emit('case "{}":'.format(field.name)) tag_type = self._tag_type(data_type, field) with self.indent(): if is_void_type(field_type): self.emit('return {}'.format(tag_type)) else: if (is_struct_type(field_type) and not field_type.has_enumerated_subtypes()): subdict = 'json' else: subdict = 'd["{}"] ?? .null'.format(field.name) self.emit('let v = {}.deserialize({})'.format( fmt_serial_obj(field_type), subdict )) self.emit('return {}(v)'.format(tag_type)) self.emit('default:') with self.indent(): if data_type.catch_all_field: self.emit('return {}'.format( self._tag_type(data_type, data_type.catch_all_field) )) else: self.emit('fatalError("Unknown tag \\(tag)")') self.emit("default:") with self.indent(): self.emit('fatalError("Failed to deserialize")')
def _generate_union_serializer(self, data_type): with self.serializer_block(data_type): with self.serializer_func(data_type), self.block('switch value'): for field in data_type.all_fields: field_type = field.data_type case = '.{}{}'.format(fmt_var(field.name), '' if is_void_type(field_type) else '(let arg)') self.emit('case {}:'.format(case)) with self.indent(): if is_void_type(field_type): self.emit('var d = [String: JSON]()') elif (is_struct_type(field_type) and not field_type.has_enumerated_subtypes()): self.emit('var d = Serialization.getFields({}.serialize(arg))'.format( fmt_serial_obj(field_type))) else: self.emit('var d = ["{}": {}.serialize(arg)]'.format( field.name, fmt_serial_obj(field_type))) self.emit('d[".tag"] = .str("{}")'.format(field.name)) self.emit('return .dictionary(d)') with self.deserializer_func(data_type): with self.block("switch json"): self.emit("case .dictionary(let d):") with self.indent(): self.emit('let tag = Serialization.getTag(d)') with self.block('switch tag'): for field in data_type.all_fields: field_type = field.data_type self.emit('case "{}":'.format(field.name)) tag_type = self._tag_type(data_type, field) with self.indent(): if is_void_type(field_type): self.emit('return {}'.format(tag_type)) else: if (is_struct_type(field_type) and not field_type.has_enumerated_subtypes()): subdict = 'json' else: subdict = 'd["{}"] ?? .null'.format(field.name) self.emit('let v = {}.deserialize({})'.format( fmt_serial_obj(field_type), subdict )) self.emit('return {}(v)'.format(tag_type)) self.emit('default:') with self.indent(): if data_type.catch_all_field: self.emit('return {}'.format( self._tag_type(data_type, data_type.catch_all_field) )) else: self.emit('fatalError("Unknown tag \\(tag)")') self.emit("default:") with self.indent(): self.emit('fatalError("Failed to deserialize")')
def _generate_route_signature( self, route, namespace, # pylint: disable=unused-argument route_args, extra_args, doc_list, task_type_name, func_suffix): """Generates route method signature for the given route.""" for name, _, typ in extra_args: route_args.append((name, typ)) deprecated = 'DEPRECATED: ' if route.deprecated else '' func_name = '{}{}'.format(fmt_var(route.name), func_suffix) self.emit(comment_prefix) if route.doc: route_doc = self.process_doc(route.doc, self._docf) else: route_doc = 'The {} route'.format(func_name) self.emit_wrapped_text(deprecated + route_doc, prefix=comment_prefix, width=120) self.emit(comment_prefix) for name, doc in doc_list: self.emit_wrapped_text('@param {} {}'.format( name, doc if doc else undocumented), prefix=comment_prefix, width=120) self.emit(comment_prefix) output = ( '@return Through the response callback, the caller will ' + 'receive a `{}` object on success or a `{}` object on failure.') output = output.format( fmt_type(route.result_data_type, tag=False, no_ptr=True), fmt_type(route.error_data_type, tag=False, no_ptr=True)) self.emit_wrapped_text(output, prefix=comment_prefix, width=120) self.emit(comment_prefix) result_type_str = fmt_type(route.result_data_type) if not is_void_type( route.result_data_type) else 'DBNilObject *' error_type_str = fmt_type(route.error_data_type) if not is_void_type( route.error_data_type) else 'DBNilObject *' return_type = '{}<{}, {}> *'.format(task_type_name, result_type_str, error_type_str) deprecated = self._get_deprecation_warning(route) route_signature = fmt_signature( func=func_name, args=fmt_func_args_declaration(route_args), return_type='{}'.format(return_type)) self.emit('{}{};'.format(route_signature, deprecated)) self.emit()
def _generate_route_signature( self, route, namespace, # pylint: disable=unused-argument route_args, extra_args, doc_list, task_type_name, func_suffix): """Generates route method signature for the given route.""" for name, _, typ in extra_args: route_args.append((name, typ)) deprecated = 'DEPRECATED: ' if route.deprecated else '' func_name = '{}{}'.format(fmt_var(route.name), func_suffix) self.emit(comment_prefix) if route.doc: route_doc = self.process_doc(route.doc, self._docf) else: route_doc = 'The {} route'.format(func_name) self.emit_wrapped_text( deprecated + route_doc, prefix=comment_prefix, width=120) self.emit(comment_prefix) for name, doc in doc_list: self.emit_wrapped_text( '@param {} {}'.format(name, doc if doc else undocumented), prefix=comment_prefix, width=120) self.emit(comment_prefix) output = ( '@return Through the response callback, the caller will ' + 'receive a `{}` object on success or a `{}` object on failure.') output = output.format( fmt_type(route.result_data_type, tag=False, no_ptr=True), fmt_type(route.error_data_type, tag=False, no_ptr=True)) self.emit_wrapped_text(output, prefix=comment_prefix, width=120) self.emit(comment_prefix) result_type_str = fmt_type(route.result_data_type) if not is_void_type( route.result_data_type) else 'DBNilObject *' error_type_str = fmt_type(route.error_data_type) if not is_void_type( route.error_data_type) else 'DBNilObject *' return_type = '{}<{}, {}> *'.format(task_type_name, result_type_str, error_type_str) deprecated = self._get_deprecation_warning(route) route_signature = fmt_signature( func=func_name, args=fmt_func_args_declaration(route_args), return_type='{}'.format(return_type)) self.emit('{}{};'.format(route_signature, deprecated)) self.emit()
def _generate_request(self, namespace, route): out = self.emit auth = route.attrs.get('auth', '') host = route.attrs.get('host', 'api') style = route.attrs.get('style', 'rpc') body = 'nil' if not is_void_type(route.arg_data_type): out('dbx.Config.LogDebug("arg: %v", arg)') out('b, err := json.Marshal(arg)') with self.block('if err != nil'): out('return') out() if host != 'content': body = 'bytes.NewReader(b)' if style == 'upload': body = 'content' headers = {} if not is_void_type(route.arg_data_type): if host == 'content' or style in ['upload', 'download']: headers["Dropbox-API-Arg"] = "dropbox.HTTPHeaderSafeJSON(b)" else: headers["Content-Type"] = '"application/json"' if style == 'upload': headers["Content-Type"] = '"application/octet-stream"' out('headers := map[string]string{') for k, v in sorted(headers.items()): out('\t"{}": {},'.format(k, v)) out('}') if fmt_var(route.name) == "Download": out('for k, v := range arg.ExtraHeaders { headers[k] = v }') if auth != 'noauth' and auth != 'team': with self.block('if dbx.Config.AsMemberID != ""'): out('headers["Dropbox-API-Select-User"] = dbx.Config.AsMemberID' ) out() fn = route.name if route.version != 1: fn += '_v%d' % route.version authed = 'false' if auth == 'noauth' else 'true' out('req, err := (*dropbox.Context)(dbx).NewRequest("{}", "{}", {}, "{}", "{}", headers, {})' .format(host, style, authed, namespace.name, fn, body)) with self.block('if err != nil'): out('return') out('dbx.Config.LogInfo("req: %v", req)') out()
def _generate_union_type(self, union_type, indent_spaces): """ Generates a TypeScript interface for a stone union. """ # Emit an interface for each variant. TypeScript 2.0 supports these tagged unions. # https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#tagged-union-types parent_type = union_type.parent_type namespace = union_type.namespace union_type_name = fmt_type_name(union_type, namespace) variant_type_names = [] if parent_type: variant_type_names.append(fmt_type_name(parent_type, namespace)) for variant in union_type.fields: if variant.doc: self._emit_tsdoc_header(variant.doc) variant_name = '%s%s' % (union_type_name, fmt_pascal(variant.name)) variant_type_names.append(variant_name) self.emit('interface %s {' % variant_name) with self.indent(dent=indent_spaces): # Since field contains non-alphanumeric character, we need to enclose # it in quotation marks. self.emit("'.tag': '%s';" % variant.name) if is_void_type(variant.data_type) is False: self.emit("%s: %s;" % (variant.name, fmt_type(variant.data_type, namespace))) self.emit('}') self.emit() if union_type.doc: self._emit_tsdoc_header(union_type.doc) self.emit('type %s = %s;' % (union_type_name, ' | '.join(variant_type_names))) self.emit()
def _emit_closed_union_test(self, ns, typ): ns_name = self.namespace_name(ns) type_name = self.struct_name(typ) with self._test_fn("ClosedUnion_" + type_name): self.emit( u'// This test ensures that an exhaustive match compiles.') self.emit(u'let x: Option<::dropbox_sdk::{}::{}> = None;'.format( ns_name, self.enum_name(typ))) self.emit(u'match x {') with self.indent(): var_exps = [] for variant in self.get_enum_variants(typ): v_name = self.enum_variant_name(variant) var_exp = u'::dropbox_sdk::{}::{}::{}'.format( ns_name, type_name, v_name) if not ir.is_void_type(variant.data_type): var_exp += u'(_)' var_exps += [var_exp] self.generate_multiline_list( [u'None'] + [u'Some({})'.format(exp) for exp in var_exps], sep=' | ', skip_last_sep=True, delim=('', ''), after=' => ()') self.emit(u'}') self.emit()
def _generate_union_class_variant_creators(self, ns, data_type): # type: (ApiNamespace, Union) -> None """ Generate the following section in the 'union Shape' example: @classmethod def circle(cls, val: float) -> Shape: ... """ union_type = class_name_for_data_type(data_type) for field in data_type.fields: if not is_void_type(field.data_type): field_name_reserved_check = fmt_func(field.name, check_reserved=True) val_type = self.map_stone_type_to_pep484_type( ns, field.data_type) self.emit('@classmethod') self.emit( 'def {field_name}(cls, val: {val_type}) -> {union_type}: ...' .format( field_name=field_name_reserved_check, val_type=val_type, union_type=union_type, )) self.emit()
def _generate_union_class_variant_creators(self, ns, data_type): """ Each non-symbol, non-any variant has a corresponding class method that can be used to construct a union with that variant selected. """ for field in data_type.fields: if not is_void_type(field.data_type): 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 else: field_dt = field.data_type self.emit('@classmethod') self.emit('def {}(cls, val):'.format(field_name_reserved_check)) with self.indent(): self.emit('"""') self.emit_wrapped_text( 'Create an instance of this class set to the ``%s`` ' 'tag with value ``val``.' % field_name) self.emit() self.emit(':param {} val:'.format( self._python_type_mapping(ns, field_dt))) self.emit(':rtype: {}'.format( fmt_class(data_type.name))) self.emit('"""') self.emit("return cls('{}', val)".format(field_name)) self.emit()
def _generate_union(self, union_type): """ Emits a JSDoc @typedef for a union type. """ union_name = fmt_type_name(union_type) self._emit_jsdoc_header(union_type.doc) self.emit(' * @typedef {Object} %s' % union_name) variant_types = [] for variant in union_type.all_fields: variant_types.append("'%s'" % variant.name) variant_data_type, _, _ = unwrap(variant.data_type) # Don't emit fields for void types. if not is_void_type(variant_data_type): variant_doc = ' - Available if .tag is %s.' % variant.name if variant.doc: variant_doc += ' ' + variant.doc self.emit_wrapped_text( '@property {%s} [%s]%s' % ( fmt_type(variant_data_type), variant.name, variant_doc, ), prefix=' * ', ) jsdoc_tag_union = fmt_jsdoc_union(variant_types) self.emit(' * @property {%s} .tag - Tag identifying the union variant.' % jsdoc_tag_union) self.emit(' */')
def _get_namespace_route_imports(self, namespace, include_route_args=True, include_route_deep_args=False): result = [] 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) for route in namespace.routes: if include_route_args: data_type, _ = unwrap_nullable(route.arg_data_type) _unpack_and_store_data_type(data_type) elif include_route_deep_args: data_type, _ = unwrap_nullable(route.arg_data_type) if is_union_type(data_type) or is_list_type(data_type): _unpack_and_store_data_type(data_type) elif not is_void_type(data_type): for field in data_type.all_fields: data_type, _ = unwrap_nullable(field.data_type) if (is_struct_type(data_type) or is_union_type(data_type) or is_list_type(data_type)): _unpack_and_store_data_type(data_type) _unpack_and_store_data_type(route.result_data_type) _unpack_and_store_data_type(route.error_data_type) return result
def _get_namespace_route_imports(self, namespace, include_route_args=True, include_route_deep_args=False): result = [] 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, _ = unwrap_nullable(data_type.data_type) if not is_void_type(data_type) and is_user_defined_type(data_type): result.append(data_type) for route in namespace.routes: if include_route_args: data_type, _ = unwrap_nullable(route.arg_data_type) _unpack_and_store_data_type(data_type) elif include_route_deep_args: data_type, _ = unwrap_nullable(route.arg_data_type) if is_union_type(data_type) or is_list_type(data_type): _unpack_and_store_data_type(data_type) elif not is_void_type(data_type): for field in data_type.all_fields: data_type, _ = unwrap_nullable(field.data_type) if (is_struct_type(data_type) or is_union_type(data_type) or is_list_type(data_type)): _unpack_and_store_data_type(data_type) _unpack_and_store_data_type(route.result_data_type) _unpack_and_store_data_type(route.error_data_type) return result
def _generate_union_class_variant_creators(self, ns, data_type): """ Each non-symbol, non-any variant has a corresponding class method that can be used to construct a union with that variant selected. """ for field in data_type.fields: if not is_void_type(field.data_type): 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 else: field_dt = field.data_type self.emit('@classmethod') self.emit( 'def {}(cls, val):'.format(field_name_reserved_check)) with self.indent(): self.emit('"""') self.emit_wrapped_text( 'Create an instance of this class set to the ``%s`` ' 'tag with value ``val``.' % field_name) self.emit() self.emit(':param {} val:'.format( self._python_type_mapping(ns, field_dt))) self.emit(':rtype: {}'.format(fmt_class(data_type.name))) self.emit('"""') self.emit("return cls('{}', val)".format(field_name)) self.emit()
def _generate_union(self, union_type): """ Emits a JSDoc @typedef for a union type. """ union_name = fmt_type_name(union_type) self._emit_jsdoc_header(union_type.doc) self.emit(' * @typedef {Object} %s' % union_name) variant_types = [] for variant in union_type.all_fields: variant_types.append("'%s'" % variant.name) variant_data_type, _, _ = unwrap(variant.data_type) # Don't emit fields for void types. if not is_void_type(variant_data_type): variant_doc = ' - Available if .tag is %s.' % variant.name if variant.doc: variant_doc += ' ' + variant.doc self.emit_wrapped_text( '@property {%s} [%s]%s' % ( fmt_type(variant_data_type), variant.name, variant_doc, ), prefix=' * ', ) jsdoc_tag_union = fmt_jsdoc_union(variant_types) self.emit( ' * @property {%s} .tag - Tag identifying the union variant.' % jsdoc_tag_union) self.emit(' */')
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)) elif is_nullable_type(data_type): return 'Nullable[{}]'.format( self._format_type_in_doc(namespace, data_type.data_type), ) elif is_list_type(data_type): return 'List[{}]'.format( self._format_type_in_doc(namespace, data_type.data_type), ) elif is_map_type(data_type): return 'Map[{}, {}]'.format( self._format_type_in_doc(namespace, data_type.key_data_type), self._format_type_in_doc(namespace, data_type.value_data_type), ) else: return fmt_type(data_type)
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)
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, _ = unwrap_nullable(data_type.data_type) if not is_void_type(data_type) and is_user_defined_type(data_type): result.append(data_type)
def _generate_route_signature(self, namespace, route): req = fmt_type(route.arg_data_type, namespace) res = fmt_type(route.result_data_type, namespace, use_interface=True) fn = fmt_var(route.name) style = route.attrs.get('style', 'rpc') arg = '' if is_void_type(route.arg_data_type) else 'arg {req}' ret = '(err error)' if is_void_type(route.result_data_type) else \ '(res {res}, err error)' signature = '{fn}(' + arg + ') ' + ret if style == 'download': signature = '{fn}(' + arg + \ ') (res {res}, content io.ReadCloser, err error)' elif style == 'upload': signature = '{fn}(' + arg + ', content io.Reader) ' + ret if is_void_type(route.arg_data_type): signature = '{fn}(content io.Reader) ' + ret return signature.format(fn=fn, req=req, res=res)
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
def _generate_union_type(self, union_type, indent_spaces): """ Generates a TypeScript interface for a stone union. """ # Emit an interface for each variant. TypeScript 2.0 supports these tagged unions. # https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#tagged-union-types parent_type = union_type.parent_type namespace = union_type.namespace union_type_name = fmt_type_name(union_type, namespace) variant_type_names = [] if parent_type: variant_type_names.append(fmt_type_name(parent_type, namespace)) def _is_struct_without_enumerated_subtypes(data_type): """ :param data_type: any data type. :return: True if the given data type is a struct which has no enumerated subtypes. """ return is_struct_type(data_type) and ( not data_type.has_enumerated_subtypes()) for variant in union_type.fields: if variant.doc: self._emit_tsdoc_header(variant.doc) variant_name = '%s%s' % (union_type_name, fmt_pascal(variant.name)) variant_type_names.append(variant_name) is_struct_without_enumerated_subtypes = _is_struct_without_enumerated_subtypes( variant.data_type) if is_struct_without_enumerated_subtypes: self.emit( 'export interface %s extends %s {' % (variant_name, fmt_type(variant.data_type, namespace))) else: self.emit('export interface %s {' % variant_name) with self.indent(dent=indent_spaces): # Since field contains non-alphanumeric character, we need to enclose # it in quotation marks. self.emit("'.tag': '%s';" % variant.name) if is_void_type(variant.data_type) is False and ( not is_struct_without_enumerated_subtypes): self.emit( "%s: %s;" % (variant.name, fmt_type(variant.data_type, namespace))) self.emit('}') self.emit() if union_type.doc: self._emit_tsdoc_header(union_type.doc) self.emit('export type %s = %s;' % (union_type_name, ' | '.join(variant_type_names))) self.emit()
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_namespaced_var(ns.name, data_type.name, field.name), self.process_doc(field.doc, self._docf)) elif is_user_defined_type(field.data_type): if data_type.namespace.name != ns.name: formatted_var = fmt_namespaced_var( ns.name, data_type.name, field.name) else: formatted_var = '{}.{}'.format(data_type.name, fmt_var(field.name)) ivar_doc = ':ivar {} {}: {}'.format( fmt_class(field.data_type.name), formatted_var, self.process_doc(field.doc, self._docf)) else: ivar_doc = ':ivar {} {}: {}'.format( self._python_type_mapping(ns, field.data_type), fmt_namespaced_var(ns.name, data_type.name, 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_custom_annotations(ns, data_type) self.emit('{0}_validator = bv.Union({0})'.format( class_name_for_data_type(data_type))) self.emit()
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)
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)
def _generate_route_m(self, route, namespace, route_args, extra_args, task_type_name, func_suffix): """Generates route method implementation for the given route.""" user_args = list(route_args) transport_args = [ ('route', 'route'), ('arg', 'arg' if not is_void_type(route.arg_data_type) else 'nil'), ] for name, value, typ in extra_args: user_args.append((name, typ)) transport_args.append((name, value)) with self.block_func( func='{}{}'.format(fmt_route_func(route), func_suffix), args=fmt_func_args_declaration(user_args), return_type='{} *'.format(task_type_name)): self.emit('DBRoute *route = {}.{};'.format( fmt_route_obj_class(namespace.name), fmt_route_var(namespace.name, route))) if is_union_type(route.arg_data_type): self.emit('{} *arg = {};'.format( fmt_class_prefix(route.arg_data_type), fmt_var(route.arg_data_type.name))) elif not is_void_type(route.arg_data_type): init_call = fmt_func_call( caller=fmt_alloc_call( caller=fmt_class_prefix(route.arg_data_type)), callee=self._cstor_name_from_fields_names(route_args), args=fmt_func_args([(f[0], f[0]) for f in route_args])) self.emit('{} *arg = {};'.format( fmt_class_prefix(route.arg_data_type), init_call)) request_call = fmt_func_call( caller='self.client', callee='request{}'.format( fmt_camel_upper(route.attrs.get('style'))), args=fmt_func_args(transport_args)) self.emit('return {};'.format(request_call)) self.emit()
def _generate_route_m(self, route, namespace, route_args, extra_args, task_type_name, func_suffix): """Generates route method implementation for the given route.""" user_args = list(route_args) transport_args = [ ('route', 'route'), ('arg', 'arg' if not is_void_type(route.arg_data_type) else 'nil'), ] for name, value, typ in extra_args: user_args.append((name, typ)) transport_args.append((name, value)) with self.block_func( func='{}{}'.format(fmt_var(route.name), func_suffix), args=fmt_func_args_declaration(user_args), return_type='{} *'.format(task_type_name)): self.emit('DBRoute *route = {}.{};'.format( fmt_route_obj_class(namespace.name), fmt_route_var(namespace.name, route.name))) if is_union_type(route.arg_data_type): self.emit('{} *arg = {};'.format( fmt_class_prefix(route.arg_data_type), fmt_var(route.arg_data_type.name))) elif not is_void_type(route.arg_data_type): init_call = fmt_func_call( caller=fmt_alloc_call( caller=fmt_class_prefix(route.arg_data_type)), callee=self._cstor_name_from_fields_names(route_args), args=fmt_func_args([(f[0], f[0]) for f in route_args])) self.emit('{} *arg = {};'.format( fmt_class_prefix(route.arg_data_type), init_call)) request_call = fmt_func_call( caller='self.client', callee='request{}'.format( fmt_camel_upper(route.attrs.get('style'))), args=fmt_func_args(transport_args)) self.emit('return {};'.format(request_call)) self.emit()
def _generate_union_class_symbol_creators(self, data_type): """ Class attributes that represent a symbol are set after the union class definition. """ class_name = fmt_class(data_type.name) lineno = self.lineno for field in data_type.fields: if is_void_type(field.data_type): field_name = fmt_func(field.name) self.emit("{0}.{1} = {0}('{1}')".format(class_name, field_name)) if lineno != self.lineno: self.emit()
def _generate_union_class_symbol_creators(self, data_type): """ Class attributes that represent a symbol are set after the union class definition. """ class_name = fmt_class(data_type.name) lineno = self.lineno for field in data_type.fields: if is_void_type(field.data_type): field_name = fmt_func(field.name) self.emit("{0}.{1} = {0}('{1}')".format(class_name, field_name)) if lineno != self.lineno: self.emit()
def _generate_union_type(self, union_type, indent_spaces): """ Generates a TypeScript interface for a stone union. """ # Emit an interface for each variant. TypeScript 2.0 supports these tagged unions. # https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#tagged-union-types parent_type = union_type.parent_type namespace = union_type.namespace union_type_name = fmt_type_name(union_type, namespace) variant_type_names = [] if parent_type: variant_type_names.append(fmt_type_name(parent_type, namespace)) def _is_struct_without_enumerated_subtypes(data_type): """ :param data_type: any data type. :return: True if the given data type is a struct which has no enumerated subtypes. """ return is_struct_type(data_type) and ( not data_type.has_enumerated_subtypes()) for variant in union_type.fields: if variant.doc: self._emit_tsdoc_header(variant.doc) variant_name = '%s%s' % (union_type_name, fmt_pascal(variant.name)) variant_type_names.append(variant_name) is_struct_without_enumerated_subtypes = _is_struct_without_enumerated_subtypes( variant.data_type) if is_struct_without_enumerated_subtypes: self.emit('export interface %s extends %s {' % ( variant_name, fmt_type(variant.data_type, namespace))) else: self.emit('export interface %s {' % variant_name) with self.indent(dent=indent_spaces): # Since field contains non-alphanumeric character, we need to enclose # it in quotation marks. self.emit("'.tag': '%s';" % variant.name) if is_void_type(variant.data_type) is False and ( not is_struct_without_enumerated_subtypes ): self.emit("%s: %s;" % (variant.name, fmt_type(variant.data_type, namespace))) self.emit('}') self.emit() if union_type.doc: self._emit_tsdoc_header(union_type.doc) self.emit('export type %s = %s;' % (union_type_name, ' | '.join(variant_type_names))) self.emit()
def make_test_field(field_name, stone_type, rust_generator, reference_impls): rust_name = rust_generator.field_name_raw( field_name) if field_name is not None else None typ, option = ir.unwrap_nullable(stone_type) inner = None value = None if ir.is_struct_type(typ): if typ.has_enumerated_subtypes(): variant = typ.get_enumerated_subtypes()[0] inner = TestPolymorphicStruct(rust_generator, typ, reference_impls, variant) else: inner = TestStruct(rust_generator, typ, reference_impls) value = inner.value elif ir.is_union_type(typ): # Pick the first tag. # We could generate tests for them all, but it would lead to a huge explosion of tests, and # the types themselves are tested elsewhere. if len(typ.fields) == 0: # there must be a parent type; go for it variant = typ.all_fields[0] else: variant = typ.fields[0] inner = TestUnion(rust_generator, typ, reference_impls, variant) value = inner.value elif ir.is_list_type(typ): inner = TestList(rust_generator, typ.data_type, reference_impls) value = [inner.value] elif ir.is_map_type(typ): inner = TestMap(rust_generator, typ, reference_impls) value = inner.value elif ir.is_string_type(typ): if typ.pattern: value = Unregex(typ.pattern, typ.min_length).generate() elif typ.min_length: value = 'a' * typ.min_length else: value = 'something' elif ir.is_numeric_type(typ): value = typ.max_value or typ.maximum or 1e307 elif ir.is_boolean_type(typ): value = True elif ir.is_timestamp_type(typ): value = datetime.datetime.utcfromtimestamp(2**33 - 1) elif ir.is_bytes_type(typ): value = bytes([0, 1, 2, 3, 4, 5]) elif not ir.is_void_type(typ): raise RuntimeError(u'Error: unhandled field type of {}: {}'.format( field_name, typ)) return TestField(rust_name, value, inner, typ, option)
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()
def _generate_union_class_vars(self, ns, data_type): # type: (ApiNamespace, Union) -> None lineno = self.lineno # Generate stubs for class variables so that IDEs like PyCharms have an # easier time detecting their existence. for field in data_type.fields: if is_void_type(field.data_type): field_name = fmt_var(field.name) field_type = class_name_for_data_type(data_type, ns) self.emit('{field_name} = ... # type: {field_type}'.format( field_name=field_name, field_type=field_type, )) if lineno != self.lineno: self.emit()
def _generate_union_class_vars(self, ns, data_type): # type: (ApiNamespace, Union) -> None lineno = self.lineno # Generate stubs for class variables so that IDEs like PyCharms have an # easier time detecting their existence. for field in data_type.fields: if is_void_type(field.data_type): field_name = fmt_var(field.name) field_type = class_name_for_data_type(data_type, ns) self.emit('{field_name} = ... # type: {field_type}'.format( field_name=field_name, field_type=field_type, )) if lineno != self.lineno: self.emit()
def _generate_union_class_get_helpers(self, ns, data_type): # type: (ApiNamespace, Union) -> None """ Generates the following section in the 'union Shape' example: def get_circle(self) -> float: ... """ for field in data_type.fields: field_name = fmt_func(field.name) if not is_void_type(field.data_type): # generate getter for field val_type = self.map_stone_type_to_pep484_type(ns, field.data_type) self.emit('def get_{field_name}(self) -> {val_type}: ...'.format( field_name=field_name, val_type=val_type, )) self.emit()
def _generate_result(self, route): out = self.emit if is_struct_type(route.result_data_type) and \ route.result_data_type.has_enumerated_subtypes(): out('var tmp %sUnion' % fmt_var(route.result_data_type.name, export=False)) with self.block('err = json.Unmarshal(body, &tmp);' 'if err != nil'): out('return') with self.block('switch tmp.Tag'): for t in route.result_data_type.get_enumerated_subtypes(): with self.block('case "%s":' % t.name, delim=(None, None)): self.emit('res = tmp.%s' % fmt_var(t.name)) elif not is_void_type(route.result_data_type): with self.block('err = json.Unmarshal(body, &res);' 'if err != nil'): out('return') out() out('return')
def _get_route_args(self, namespace, route): data_type = route.arg_data_type arg_type = fmt_type(data_type) if is_struct_type(data_type): arg_list = self._struct_init_args(data_type, namespace=namespace) doc_list = [(fmt_var(f.name), self.process_doc(f.doc, self._docf) if f.doc else undocumented) for f in data_type.fields if f.doc] elif is_union_type(data_type): arg_list = [(fmt_var(data_type.name), '{}.{}'.format( fmt_class(namespace.name), fmt_class(data_type.name)))] doc_list = [(fmt_var(data_type.name), self.process_doc(data_type.doc, self._docf) if data_type.doc else 'The {} union'.format(fmt_class(data_type.name)))] else: arg_list = [] if is_void_type(data_type) else [('request', arg_type)] doc_list = [] return arg_list, doc_list
def _generate_union_class_get_helpers(self, ns, data_type): # type: (ApiNamespace, Union) -> None """ Generates the following section in the 'union Shape' example: def get_circle(self) -> float: ... """ for field in data_type.fields: field_name = fmt_func(field.name) if not is_void_type(field.data_type): # generate getter for field val_type = self.map_stone_type_to_pep484_type(ns, field.data_type) self.emit('def get_{field_name}(self) -> {val_type}: ...'.format( field_name=field_name, val_type=val_type, )) self.emit()
def _get_route_args(self, namespace, route): data_type = route.arg_data_type arg_type = fmt_type(data_type) if is_struct_type(data_type): arg_list = self._struct_init_args(data_type, namespace=namespace) doc_list = [(fmt_var(f.name), self.process_doc(f.doc, self._docf) if f.doc else undocumented) for f in data_type.fields if f.doc] elif is_union_type(data_type): arg_list = [(fmt_var(data_type.name), '{}.{}'.format( fmt_class(namespace.name), fmt_class(data_type.name)))] doc_list = [(fmt_var(data_type.name), self.process_doc(data_type.doc, self._docf) if data_type.doc else 'The {} union'.format(fmt_class(data_type.name)))] else: arg_list = [] if is_void_type(data_type) else [('request', arg_type)] doc_list = [] return arg_list, doc_list
def emit_asserts(self, codegen, expression_path): if expression_path[0] == '(' and expression_path[-1] == ')': expression_path = expression_path[1:-1] # strip off superfluous parens with codegen.block(u'match {}'.format(expression_path)): if ir.is_void_type(self._variant_type): codegen.emit(u'::dropbox_sdk::{}::{}::{} => (),'.format( self._rust_namespace_name, self._rust_name, self._rust_variant_name)) else: with codegen.block(u'::dropbox_sdk::{}::{}::{}(ref v) =>'.format( self._rust_namespace_name, self._rust_name, self._rust_variant_name)): self._inner_value.emit_assert(codegen, '(*v)') if self.is_open(): codegen.emit(u'_ => panic!("wrong variant")')
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), ':')
def _generate_union_class_vars(self, data_type): """ Adds a _catch_all_ attribute to each class. Also, adds a placeholder attribute for the construction of union members of void type. """ lineno = self.lineno if data_type.catch_all_field: self.emit("_catch_all = '%s'" % data_type.catch_all_field.name) elif not data_type.parent_type: self.emit('_catch_all = None') # Generate stubs for class variables so that IDEs like PyCharms have an # easier time detecting their existence. for field in data_type.fields: if is_void_type(field.data_type): field_name = fmt_var(field.name) self.emit('# Attribute is overwritten below the class definition') self.emit('{} = None'.format(field_name)) if lineno != self.lineno: self.emit()
def _generate_union_class_vars(self, data_type): """ Adds a _catch_all_ attribute to each class. Also, adds a placeholder attribute for the construction of union members of void type. """ lineno = self.lineno if data_type.catch_all_field: self.emit("_catch_all = '%s'" % data_type.catch_all_field.name) elif not data_type.parent_type: self.emit('_catch_all = None') # Generate stubs for class variables so that IDEs like PyCharms have an # easier time detecting their existence. for field in data_type.fields: if is_void_type(field.data_type): field_name = fmt_var(field.name) self.emit('# Attribute is overwritten below the class definition') self.emit('{} = None'.format(field_name)) if lineno != self.lineno: self.emit()
def _generate_union_class_variant_creators(self, ns, data_type): # type: (ApiNamespace, Union) -> None """ Generate the following section in the 'union Shape' example: @classmethod def circle(cls, val: float) -> Shape: ... """ union_type = class_name_for_data_type(data_type) for field in data_type.fields: if not is_void_type(field.data_type): field_name_reserved_check = fmt_func(field.name, check_reserved=True) val_type = self.map_stone_type_to_pep484_type(ns, field.data_type) self.emit('@classmethod') self.emit('def {field_name}(cls, val: {val_type}) -> {union_type}: ...'.format( field_name=field_name_reserved_check, val_type=val_type, union_type=union_type, )) self.emit()
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), ':')
def _generate_struct_class_init(self, ns, struct): # type: (ApiNamespace, Struct) -> None args = [] for field in struct.all_fields: field_name_reserved_check = fmt_var(field.name, True) field_type = self.map_stone_type_to_pep484_type( ns, field.data_type) # The constructor takes all possible fields as optional arguments if not is_nullable_type(field.data_type) and not is_void_type( field.data_type): self.import_tracker._register_typing_import('Optional') field_type = 'Optional[{}]'.format(field_type) args.append("{field_name}: {field_type} = ...".format( field_name=field_name_reserved_check, field_type=field_type, )) self.generate_multiline_list(before='def __init__', items=["self"] + args, after=' -> None: ...')
def _generate_struct_class_properties(self, ns, struct): # type: (ApiNamespace, Struct) -> None to_emit = [] # type: typing.List[typing.Text] for field in struct.all_fields: field_name_reserved_check = fmt_func(field.name, check_reserved=True) setter_field_type = self.map_stone_type_to_pep484_type(ns, field.data_type) # The field might not have been set since it is optional in the constructor. getter_field_type = setter_field_type if not is_nullable_type(field.data_type) and not is_void_type(field.data_type): self.import_tracker._register_typing_import('Optional') getter_field_type = 'Optional[{}]'.format(setter_field_type) to_emit.extend( self.property_template.format( field_name=field_name_reserved_check, getter_field_type=getter_field_type, setter_field_type=setter_field_type ).split("\n") ) for s in to_emit: self.emit(s)
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
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
def _generate_union_class_get_helpers(self, ns, data_type): """ These are the getters used to access the value of a variant, once the tag has been switched on. """ for field in data_type.fields: field_name = fmt_func(field.name) if not is_void_type(field.data_type): # generate getter for field self.emit('def get_{}(self):'.format(field_name)) with self.indent(): if is_nullable_type(field.data_type): field_dt = field.data_type.data_type else: field_dt = field.data_type self.emit('"""') if field.doc: self.emit_wrapped_text( self.process_doc(field.doc, self._docf)) self.emit() self.emit("Only call this if :meth:`is_%s` is true." % field_name) # 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 not self.is_{}():'.format(field_name)) with self.indent(): self.emit( 'raise AttributeError("tag \'{}\' not set")'.format( field_name)) self.emit('return self._value') self.emit()
def _generate_union_class_get_helpers(self, ns, data_type): """ These are the getters used to access the value of a variant, once the tag has been switched on. """ for field in data_type.fields: field_name = fmt_func(field.name) if not is_void_type(field.data_type): # generate getter for field self.emit('def get_{}(self):'.format(field_name)) with self.indent(): if is_nullable_type(field.data_type): field_dt = field.data_type.data_type else: field_dt = field.data_type self.emit('"""') if field.doc: self.emit_wrapped_text( self.process_doc(field.doc, self._docf)) self.emit() self.emit("Only call this if :meth:`is_%s` is true." % field_name) # 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 not self.is_{}():'.format(field_name)) with self.indent(): self.emit( 'raise AttributeError("tag \'{}\' not set")'.format( field_name)) self.emit('return self._value') self.emit()
def _emit_route(self, namespace, route, req_obj_name, extra_args=None, extra_docs=None): arg_list, doc_list = self._get_route_args(namespace, route) extra_args = extra_args or [] extra_docs = extra_docs or [] arg_type = fmt_type(route.arg_data_type) func_name = fmt_func(route.name) if route.doc: route_doc = self.process_doc(route.doc, self._docf) else: route_doc = 'The {} route'.format(func_name) self.emit_wrapped_text(route_doc, prefix='/// ', width=120) self.emit('///') for name, doc in doc_list + extra_docs: param_doc = '- parameter {}: {}'.format(name, doc if doc is not None else undocumented) self.emit_wrapped_text(param_doc, prefix='/// ', width=120) self.emit('///') output = (' - returns: Through the response callback, the caller will ' + 'receive a `{}` object on success or a `{}` object on failure.') output = output.format(fmt_type(route.result_data_type), fmt_type(route.error_data_type)) self.emit_wrapped_text(output, prefix='/// ', width=120) func_args = [ ('route', '{}.{}'.format(fmt_class(namespace.name), func_name)), ] client_args = [] return_args = [('route', 'route')] for name, value, typ in extra_args: arg_list.append((name, typ)) func_args.append((name, value)) client_args.append((name, value)) rtype = fmt_serial_type(route.result_data_type) etype = fmt_serial_type(route.error_data_type) self._maybe_generate_deprecation_warning(route) with self.function_block('@discardableResult open func {}'.format(func_name), args=self._func_args(arg_list, force_first=False), return_type='{}<{}, {}>'.format(req_obj_name, rtype, etype)): self.emit('let route = {}.{}'.format(fmt_class(namespace.name), func_name)) if is_struct_type(route.arg_data_type): args = [(name, name) for name, _ in self._struct_init_args(route.arg_data_type)] func_args += [('serverArgs', '{}({})'.format(arg_type, self._func_args(args)))] self.emit('let serverArgs = {}({})'.format(arg_type, self._func_args(args))) elif is_union_type(route.arg_data_type): self.emit('let serverArgs = {}'.format(fmt_var(route.arg_data_type.name))) if not is_void_type(route.arg_data_type): return_args += [('serverArgs', 'serverArgs')] return_args += client_args txt = 'return client.request({})'.format( self._func_args(return_args, not_init=True) ) self.emit(txt) self.emit()
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('"""')
def _generate_route_helper(self, namespace, route, download_to_file=False): """Generate a Python method that corresponds to a route. :param namespace: Namespace that the route belongs to. :param stone.ir.ApiRoute route: IR node for the route. :param bool download_to_file: Whether a special version of the route that downloads the response body to a file should be generated. This can only be used for download-style routes. """ arg_data_type = route.arg_data_type result_data_type = route.result_data_type request_binary_body = route.attrs.get('style') == 'upload' response_binary_body = route.attrs.get('style') == 'download' if download_to_file: assert response_binary_body, 'download_to_file can only be set ' \ 'for download-style routes.' self._generate_route_method_decl(namespace, route, arg_data_type, request_binary_body, method_name_suffix='_to_file', extra_args=['download_path']) else: self._generate_route_method_decl(namespace, route, arg_data_type, request_binary_body) with self.indent(): extra_request_args = None extra_return_arg = None footer = None if request_binary_body: extra_request_args = [('f', 'bytes', 'Contents to upload.')] elif download_to_file: extra_request_args = [('download_path', 'str', 'Path on local machine to save file.')] if response_binary_body and not download_to_file: extra_return_arg = ':class:`requests.models.Response`' footer = DOCSTRING_CLOSE_RESPONSE if route.doc: func_docstring = self.process_doc(route.doc, self._docf) else: func_docstring = None self._generate_docstring_for_func( namespace, arg_data_type, result_data_type, route.error_data_type, overview=func_docstring, extra_request_args=extra_request_args, extra_return_arg=extra_return_arg, footer=footer, ) self._maybe_generate_deprecation_warning(route) # Code to instantiate a class for the request data type if is_void_type(arg_data_type): self.emit('arg = None') elif is_struct_type(arg_data_type): self.generate_multiline_list( [f.name for f in arg_data_type.all_fields], before='arg = {}.{}'.format( fmt_namespace(arg_data_type.namespace.name), fmt_class(arg_data_type.name)), ) elif not is_union_type(arg_data_type): raise AssertionError('Unhandled request type %r' % arg_data_type) # Code to make the request args = [ '{}.{}'.format(fmt_namespace(namespace.name), fmt_func(route.name, version=route.version)), "'{}'".format(namespace.name), 'arg'] if request_binary_body: args.append('f') else: args.append('None') self.generate_multiline_list(args, 'r = self.request', compact=False) if download_to_file: self.emit('self._save_body_to_file(download_path, r[1])') if is_void_type(result_data_type): self.emit('return None') else: self.emit('return r[0]') else: if is_void_type(result_data_type): self.emit('return None') else: self.emit('return r') self.emit()
def _format_tag_type(self, namespace, data_type): # pylint: disable=unused-argument if is_void_type(data_type): return '' else: return '({})'.format(fmt_type(data_type))
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)
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