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(fmt_namespace(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(fmt_namespace(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(self, api): """ Generates a module for each namespace. Each namespace will have Python classes to represent data types and routes in the Stone spec. """ with self.output_to_relative_path('__init__.py', mode='ab'): pass with self.output_to_relative_path('stone_base.py'): self.emit("from stone.backends.python_rsrc.stone_base import *") with self.output_to_relative_path('stone_serializers.py'): self.emit( "from stone.backends.python_rsrc.stone_serializers import *") with self.output_to_relative_path('stone_validators.py'): self.emit( "from stone.backends.python_rsrc.stone_validators import *") for namespace in api.namespaces.values(): reserved_namespace_name = fmt_namespace(namespace.name) with self.output_to_relative_path( '{}.py'.format(reserved_namespace_name)): self._generate_base_namespace_module(api, namespace) if reserved_namespace_name != namespace.name: with self.output_to_relative_path('{}.py'.format( namespace.name)): self._generate_dummy_namespace_module( reserved_namespace_name)
def generate(self, api): """ Generates a module for each namespace. Each namespace will have Python classes to represent data types and routes in the Stone spec. """ rsrc_folder = os.path.join(os.path.dirname(__file__), 'python_rsrc') self.logger.info('Copying stone_validators.py to output folder') shutil.copy(os.path.join(rsrc_folder, 'stone_validators.py'), self.target_folder_path) self.logger.info('Copying stone_serializers.py to output folder') shutil.copy(os.path.join(rsrc_folder, 'stone_serializers.py'), self.target_folder_path) self.logger.info('Copying stone_base.py to output folder') shutil.copy(os.path.join(rsrc_folder, 'stone_base.py'), self.target_folder_path) for namespace in api.namespaces.values(): reserved_namespace_name = fmt_namespace(namespace.name) with self.output_to_relative_path( '{}.py'.format(reserved_namespace_name)): self._generate_base_namespace_module(api, namespace) if reserved_namespace_name != namespace.name: with self.output_to_relative_path('{}.py'.format( namespace.name)): self._generate_dummy_namespace_module( reserved_namespace_name)
def _generate_python_value(self, namespace, value): if is_tag_ref(value): return '{}.{}.{}'.format( fmt_namespace(namespace.name), class_name_for_data_type(value.union_data_type), fmt_var(value.tag_name)) else: return fmt_obj(value)
def _generate_python_value(self, namespace, value): if is_tag_ref(value): return '{}.{}.{}'.format( fmt_namespace(namespace.name), class_name_for_data_type(value.union_data_type), fmt_var(value.tag_name)) else: return fmt_obj(value)
def _generate_imports(self, namespaces): # Only import namespaces that have user-defined types defined. self.emit('from . import (') with self.indent(): for namespace in namespaces: if namespace.data_types: self.emit(fmt_namespace(namespace.name) + ',') self.emit(')')
def _generate_imports(self, namespaces): # Only import namespaces that have user-defined types defined. self.emit('from . import (') with self.indent(): for namespace in namespaces: if namespace.data_types: self.emit(fmt_namespace(namespace.name) + ',') self.emit(')')
def generate(self, api): # type: (Api) -> None """ Generates a module for each namespace. Each namespace will have Python classes to represent data types and routes in the Stone spec. """ for namespace in api.namespaces.values(): with self.output_to_relative_path('{}.pyi'.format(fmt_namespace(namespace.name))): self._generate_base_namespace_module(namespace)
def _generate_python_value(self, ns, value): if is_tag_ref(value): ref = '{}.{}'.format( class_name_for_data_type(value.union_data_type), fmt_var(value.tag_name)) if ns != value.union_data_type.namespace: ref = '{}.{}'.format( fmt_namespace(value.union_data_type.namespace.name), ref) return ref else: return fmt_obj(value)
def generate(self, api): # type: (Api) -> None """ Generates a module for each namespace. Each namespace will have Python classes to represent data types and routes in the Stone spec. """ for namespace in api.namespaces.values(): with self.output_to_relative_path('{}.pyi'.format(fmt_namespace(namespace.name))): self._generate_base_namespace_module(namespace)
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 _generate_imports(self, namespaces): # Only import namespaces that have user-defined types defined. for namespace in namespaces: if namespace.data_types: self.emit('from {} import {}'.format(self.args.types_package, fmt_namespace(namespace.name)))
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 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 'int' 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 '{}.{}'.format( fmt_namespace(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 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 'int' 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 '{}.{}'.format( fmt_namespace(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)