import textwrap from stone.backends.python_types import PythonTypesBackend from stone.ir import ( AnnotationType, AnnotationTypeParam, Api, ApiNamespace, ApiRoute, CustomAnnotation, Int32, Struct, StructField, Void, ) MYPY = False if MYPY: import typing # noqa: F401 # pylint: disable=import-error,unused-import,useless-suppression import unittest class TestGeneratedPythonTypes(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _mk_route_schema(self): s = Struct('Route', ApiNamespace('stone_cfg'), None) s.set_attributes(None, [], None) return s def _mock_backend(self): # type: () -> PythonTypesBackend return PythonTypesBackend( target_folder_path='output', args=['-r', 'dropbox.dropbox.Dropbox.{ns}_{route}', '--package', 'output']) def _evaluate_namespace(self, ns): # type: (ApiNamespace) -> typing.Text backend = self._mock_backend() route_schema = self._mk_route_schema() backend._generate_routes(route_schema, ns) return backend.output_buffer_to_string() def _evaluate_namespace_definition(self, api, ns): # type: (Api, ApiNamespace) -> typing.Text backend = self._mock_backend() backend._generate_base_namespace_module(api, ns) return backend.output_buffer_to_string() def _evaluate_struct(self, ns, struct): # type: (ApiNamespace, Struct) -> typing.Text backend = self._mock_backend() backend._generate_struct_class(ns, struct) return backend.output_buffer_to_string() def _evaluate_annotation_type(self, ns, annotation_type): # type: (ApiNamespace, AnnotationType) -> typing.Text backend = self._mock_backend() backend._generate_annotation_type_class(ns, annotation_type) return backend.output_buffer_to_string() def test_namespace_comments(self): # type: () -> None ns = ApiNamespace('files') ns.add_doc("Test Doc testing a :type:`Type`.") route_schema = self._mk_route_schema() api = Api('0.0') api.add_route_schema(route_schema) result = self._evaluate_namespace_definition(api, ns) expected = textwrap.dedent("""\ # -*- coding: utf-8 -*- # Auto-generated by Stone, do not modify. # @generated # flake8: noqa # pylint: skip-file \"\"\" Test Doc testing a :class:`Type`. \"\"\" from __future__ import unicode_literals from stone.backends.python_rsrc import stone_base as bb from stone.backends.python_rsrc import stone_validators as bv ROUTES = { } """) self.assertEqual(result, expected) def test_route_with_version_number(self): # type: () -> None route1 = ApiRoute('alpha/get_metadata', 1, None) route1.set_attributes(None, None, Void(), Void(), Void(), {}) route2 = ApiRoute('alpha/get_metadata', 2, None) route2.set_attributes(None, None, Void(), Int32(), Void(), {}) ns = ApiNamespace('files') ns.add_route(route1) ns.add_route(route2) result = self._evaluate_namespace(ns) expected = textwrap.dedent("""\ alpha_get_metadata = bb.Route( 'alpha/get_metadata', 1, False, bv.Void(), bv.Void(), bv.Void(), {}, ) alpha_get_metadata_v2 = bb.Route( 'alpha/get_metadata', 2, False, bv.Void(), bv.Int32(), bv.Void(), {}, ) ROUTES = { 'alpha/get_metadata': alpha_get_metadata, 'alpha/get_metadata:2': alpha_get_metadata_v2, } """) self.assertEqual(result, expected) def test_route_with_version_number_name_conflict(self): # type: () -> None route1 = ApiRoute('alpha/get_metadata', 2, None) route1.set_attributes(None, None, Void(), Int32(), Void(), {}) route2 = ApiRoute('alpha/get_metadata_v2', 1, None) route2.set_attributes(None, None, Void(), Void(), Void(), {}) ns = ApiNamespace('files') ns.add_route(route1) ns.add_route(route2) with self.assertRaises(RuntimeError) as cm: self._evaluate_namespace(ns) self.assertEqual( 'There is a name conflict between {!r} and {!r}'.format(route1, route2), str(cm.exception)) def test_struct_with_custom_annotations(self): # type: () -> None ns = ApiNamespace('files') annotation_type = AnnotationType('MyAnnotationType', ns, None, [ AnnotationTypeParam('test_param', Int32(), None, False, None, None) ]) ns.add_annotation_type(annotation_type) annotation = CustomAnnotation('MyAnnotation', ns, None, 'MyAnnotationType', None, [], {'test_param': 42}) annotation.set_attributes(annotation_type) ns.add_annotation(annotation) struct = Struct('MyStruct', ns, None) struct.set_attributes(None, [ StructField('annotated_field', Int32(), None, None), StructField('unannotated_field', Int32(), None, None), ]) struct.fields[0].set_annotations([annotation]) struct.recursive_custom_annotations = {annotation} result = self._evaluate_struct(ns, struct) expected = textwrap.dedent('''\ class MyStruct(bb.Struct): __slots__ = [ '_annotated_field_value', '_unannotated_field_value', ] _has_required_fields = True def __init__(self, annotated_field=None, unannotated_field=None): self._annotated_field_value = bb.NOT_SET self._unannotated_field_value = bb.NOT_SET if annotated_field is not None: self.annotated_field = annotated_field if unannotated_field is not None: self.unannotated_field = unannotated_field # Instance attribute type: int (validator is set below) annotated_field = bb.Attribute("annotated_field") # Instance attribute type: int (validator is set below) unannotated_field = bb.Attribute("unannotated_field") def _process_custom_annotations(self, annotation_type, field_path, processor): super(MyStruct, self)._process_custom_annotations(annotation_type, field_path, processor) if annotation_type is MyAnnotationType: self.annotated_field = bb.partially_apply(processor, MyAnnotationType(test_param=42))('{}.annotated_field'.format(field_path), self.annotated_field) MyStruct_validator = bv.Struct(MyStruct) ''') # noqa self.maxDiff = None self.assertEqual(result, expected) def test_annotation_type_class(self): # type: () -> None ns = ApiNamespace('files') annotation_type = AnnotationType('MyAnnotationType', ns, "documented", [ AnnotationTypeParam( 'test_param', Int32(), "test parameter", False, None, None, ), AnnotationTypeParam( 'test_default_param', Int32(), None, True, 5, None, ), ]) result = self._evaluate_annotation_type(ns, annotation_type) expected = textwrap.dedent('''\ class MyAnnotationType(bb.AnnotationType): """ documented :ivar test_param: test parameter """ __slots__ = [ '_test_param', '_test_default_param', ] def __init__(self, test_param=None, test_default_param=5): self._test_param = test_param self._test_default_param = test_default_param @property def test_param(self): """ test parameter :rtype: int """ return self._test_param @property def test_default_param(self): """ :rtype: int """ return self._test_default_param ''') self.assertEqual(result, expected) # TODO: add more unit tests for client code generation