def test_model_to_json(self):
        swagger_dict = yaml.load(Tests.yaml_complex_model)
        spec = ApiSpec(swagger_dict)

        Foo = spec.definitions['Foo']
        Bar = spec.definitions['Bar']

        f = Foo(
            token='abcd',
            bar=Bar(
                a=1,
                b=date.today()
            )
        )

        print("foo: " + pprint.pformat(f))

        j = spec.model_to_json(f)
        self.assertDictEqual(j, {
            'token': 'abcd',
            'bar': {
                'a': 1,
                'b': date.today().isoformat()
            }
        })
    def test_call_on_each_endpoint__too_many_produces(self):
        yaml_str = """
swagger: '2.0'
host: pnt-login.elasticbeanstalk.com
schemes:
  - http
  - https
paths:
  /v1/auth/login:
    post:
      parameters:
        - in: query
          name: whatever
          description: User login credentials.
          required: true
          type: string
      produces:
        - foo/bar
        - bar/baz
      x-bind-server: pnt_login.handlers.do_login
"""
        swagger_dict = yaml.load(yaml_str)
        spec = ApiSpec(swagger_dict)

        with self.assertRaisesRegexp(Exception, "Expecting only one type"):
            spec.call_on_each_endpoint(self.foo)
    def test_call_on_each_endpoint__invalid_produces(self):
        yaml_str = """
swagger: '2.0'
host: pnt-login.elasticbeanstalk.com
schemes:
  - http
  - https
paths:
  /v1/auth/login:
    post:
      parameters:
        - in: query
          name: whatever
          description: User login credentials.
          required: true
          type: string
      produces:
        - foo/bar
      x-bind-server: pnt_login.handlers.do_login
"""
        swagger_dict = yaml.load(yaml_str)
        spec = ApiSpec(swagger_dict)

        with self.assertRaisesRegexp(Exception, "Only 'application/json' or 'text/html' are supported."):
            spec.call_on_each_endpoint(self.foo)
    def test_validate(self):
        swagger_dict = yaml.load(Tests.yaml_complex_model)
        spec = ApiSpec(swagger_dict)

        f = {
            'token': 'abcd',
            'bar': {
                'a': 1,
                'b': date.today().isoformat()
            }
        }

        # validate ok!
        m = spec.validate('Foo', f)

        # missing required property
        f = {'bar': {}}
        try:
            m = spec.validate('Foo', f)
        except Exception as e:
            self.assertTrue("'a' is a required property" in str(e))
        else:
            assert 0

        # invalid type
        f = {'a': 'oaeuaoue'}
        try:
            m = spec.validate('Bar', f)
        except Exception as e:
            self.assertTrue("'oaeuaoue' is not of type 'number'" in str(e))
        else:
            assert 0

        # invalid object reference type
        f = {'bar': '123'}
        try:
            m = spec.validate('Foo', f)
        except Exception as e:
            self.assertTrue("'123' is not of type 'object'" in str(e))
        else:
            assert 0
    def test_json_to_model(self):
        swagger_dict = yaml.load(Tests.yaml_complex_model)
        spec = ApiSpec(swagger_dict)

        j = {
            'token': 'abcd',
            'bar': {
                'a': 1,
                'b': date.today().isoformat()
            }
        }

        m = spec.json_to_model('Foo', j)

        Foo = spec.definitions['Foo']
        Bar = spec.definitions['Bar']

        self.assertEqual(m.token, 'abcd')
        b = m.bar
        self.assertEqual(b.__class__.__name__, 'Bar')
        self.assertEqual(b.a, 1)
        self.assertEqual(str(b.b), str(date.today()) + " 00:00:00")
Esempio n. 6
0
    def __init__(self, name, yaml_str=None, yaml_path=None, timeout=None, error_callback=None, formats=None, do_persist=True, host=None, port=None):
        """An API Specification"""

        self.name = name

        if yaml_path:
            log.info("Loading swagger file at %s" % yaml_path)
            swagger_dict = yaml.load(open(yaml_path))
        elif yaml_str:
            swagger_dict = yaml.load(yaml_str)
        else:
            raise Exception("No swagger file specified")

        self.api_spec = ApiSpec(swagger_dict, formats, host, port)

        if timeout:
            self.client_timeout = timeout

        if error_callback:
            self.error_callback = error_callback

        # Auto-generate class methods for every object model defined
        # in the swagger spec, calling that model's constructor
        # Ex:
        #     klue_api.Version(version='1.2.3')   => return a Version object
        for model_name in self.api_spec.definitions:
            model_generator = generate_model_instantiator(model_name, self.api_spec.definitions)

            # Associate model generator to ApiPool().<api_name>.model.<model_name>
            setattr(self.model, model_name, model_generator)

            # Make this bravado-core model persistent?
            if do_persist:
                spec = swagger_dict['definitions'][model_name]
                if 'x-persist' in spec:
                    self._make_persistent(model_name, spec['x-persist'])

        # Auto-generate client callers
        # so we can write
        # api.call.login(param)  => call /v1/login/ on server with param as json parameter
        callers_dict = generate_client_callers(self.api_spec, self.client_timeout, self.error_callback)
        for method, caller in callers_dict.items():
            setattr(self.client, method, caller)
Esempio n. 7
0
class API():
    """Describes a REST client/server API, with sugar coating:
    - easily instantiating the objects defined in the API
    - auto-generation of client code

    usage: See apipool.py
    """

    # The API's swagger representation
    api_spec = None

    # Object holding the client side code to call the API
    client = APIClient()

    # Object holding constructors for the API's objects
    model = APIModels()

    # Default timeout when calling server endpoint, in sec
    client_timeout = 10

    # Callback to handle exceptions
    error_callback = default_error_callback

    # Flag: true if this api has spawned_api
    is_server = False

    # The api's name
    name = None

    def __init__(self, name, yaml_str=None, yaml_path=None, timeout=None, error_callback=None, formats=None, do_persist=True, host=None, port=None):
        """An API Specification"""

        self.name = name

        if yaml_path:
            log.info("Loading swagger file at %s" % yaml_path)
            swagger_dict = yaml.load(open(yaml_path))
        elif yaml_str:
            swagger_dict = yaml.load(yaml_str)
        else:
            raise Exception("No swagger file specified")

        self.api_spec = ApiSpec(swagger_dict, formats, host, port)

        if timeout:
            self.client_timeout = timeout

        if error_callback:
            self.error_callback = error_callback

        # Auto-generate class methods for every object model defined
        # in the swagger spec, calling that model's constructor
        # Ex:
        #     klue_api.Version(version='1.2.3')   => return a Version object
        for model_name in self.api_spec.definitions:
            model_generator = generate_model_instantiator(model_name, self.api_spec.definitions)

            # Associate model generator to ApiPool().<api_name>.model.<model_name>
            setattr(self.model, model_name, model_generator)

            # Make this bravado-core model persistent?
            if do_persist:
                spec = swagger_dict['definitions'][model_name]
                if 'x-persist' in spec:
                    self._make_persistent(model_name, spec['x-persist'])

        # Auto-generate client callers
        # so we can write
        # api.call.login(param)  => call /v1/login/ on server with param as json parameter
        callers_dict = generate_client_callers(self.api_spec, self.client_timeout, self.error_callback)
        for method, caller in callers_dict.items():
            setattr(self.client, method, caller)


    #
    # WARNING: ugly piece of monkey-patching below. Hopefully will replace
    # with native bravado-core code in the future...
    #
    def _make_persistent(self, model_name, pkg_name):
        """Monkey-patch object persistence (ex: to/from database) into a
        bravado-core model class"""

        # Load class at path pkg_name
        c = get_function(pkg_name)
        for name in ('load_from_db', 'save_to_db'):
            if not hasattr(c, name):
                raise KlueException("Class %s has no static method '%s'" % (pkg_name, name))

        log.info("Making %s persistent via %s" % (model_name, pkg_name))

        # Replace model generator with one that adds 'save_to_db' to every instance
        model = getattr(self.model, model_name)
        n = self._wrap_bravado_model_generator(model, c.save_to_db)
        setattr(self.model, model_name, n)

        # Add class method load_from_db to model generator
        model = getattr(self.model, model_name)
        setattr(model, 'load_from_db', c.load_from_db)

    def _wrap_bravado_model_generator(self, model, method):

        def new_creator(*args, **kwargs):
            r = model(*args, **kwargs)
            r.save_to_db = types.MethodType(method, r)
            return r

        return new_creator

    #
    # End of ugly-monkey-patching
    #

    def spawn_api(self, app, decorator=None):
        """Auto-generate server endpoints implementing the API into this Flask app"""
        if decorator:
            assert type(decorator).__name__ == 'function'
        self.is_server = True
        return spawn_server_api(self.name, app, self.api_spec, self.error_callback, decorator)


    def get_version(self):
        """Return the version of the API (as defined in the swagger file)"""
        return self.api_spec.version


    def model_to_json(self, object):
        """Take a model instance and return it as a json struct"""
        return self.api_spec.model_to_json(object)


    def json_to_model(self, model_name, j, validate=False):
        """Take a json strust and a model name, and return a model instance"""
        if validate:
            self.api_spec.validate(model_name, j)
        return self.api_spec.json_to_model(model_name, j)
    def test_call_on_each_endpoint(self):
        yaml_str = """
swagger: '2.0'
info:
  title: test
  version: '0.0.1'
  description: Just a test
host: pnt-login.elasticbeanstalk.com
schemes:
  - http
basePath: /v1
produces:
  - application/json
paths:
  /v1/auth/login:
    post:
      summary: blabla
      description: blabla
      parameters:
        - in: body
          name: whatever
          description: User login credentials.
          required: true
          schema:
            $ref: '#/definitions/Credentials'
      produces:
        - application/json
      x-bind-server: pnt_login.handlers.do_login
      x-bind-client: login
      x-auth-required: false
      responses:
        200:
          description: A session token
          schema:
            $ref: '#/definitions/SessionToken'

  /v1/auth/logout:
    get:
      summary: blabla
      description: blabla
      parameters:
        - in: query
          name: baboom
          description: foooo
          required: true
          type: string
      produces:
        - application/json
      x-bind-server: pnt_login.babar
      x-decorate-request: foo.bar.baz
      responses:
        200:
          description: A session token
          schema:
            $ref: '#/definitions/SessionToken'

  /v1/version:
    get:
      summary: blabla
      description: blabla
      produces:
        - application/json
      x-bind-server: do_version
      x-decorate-server: foo.bar.baz
      responses:
        200:
          description: A session token
          schema:
            $ref: '#/definitions/SessionToken'

  /v1/versionhtml:
    get:
      summary: blabla
      description: blabla
      produces:
        - text/html
      x-bind-server: do_version_html
      x-decorate-server: foo.bar.baz
      responses:
        200:
          description: A session token
          schema:
            $ref: '#/definitions/SessionToken'

  /v1/hybrid/{foo}:
    get:
      summary: blabla
      description: blabla
      produces:
        - application/json
      parameters:
        - in: body
          name: whatever
          description: User login credentials.
          required: true
          schema:
            $ref: '#/definitions/Credentials'
        - in: path
          name: foo
          description: foooo
          required: true
          type: string
      x-bind-server: do_version
      x-decorate-server: foo.bar.baz
      responses:
        200:
          description: A session token
          schema:
            $ref: '#/definitions/SessionToken'

  /v1/ignoreme:
    get:
      summary: blabla
      description: blabla
      produces:
        - application/json
      x-no-bind-server: true
      responses:
        200:
          description: A session token
          schema:
            $ref: '#/definitions/SessionToken'

definitions:

  SessionToken:
    type: object
    description: An authenticated user''s session token
    properties:
      token:
        type: string
        description: Session token.

  Credentials:
    type: object
    description: A user''s login credentials.
    properties:
      email:
        type: string
        description: User''s email.
      password_hash:
        type: string
        description: MD5 of user''s password, truncated to 16 first hexadecimal characters.
"""

        Tests.call_count = 0

        swagger_dict = yaml.load(yaml_str)
        spec = ApiSpec(swagger_dict)

        def test_callback(data):
            print("Looking at %s %s" % (data.method, data.path))

            Tests.call_count = Tests.call_count + 1

            self.assertEqual(type(data.operation).__name__, 'Operation')

            if data.path == '/v1/auth/logout/':
                self.assertEqual(data.method, 'GET')
                self.assertEqual(data.handler_server, 'pnt_login.babar')
                self.assertIsNone(data.handler_client)
                self.assertIsNone(data.decorate_server)
                self.assertEqual(data.decorate_request, 'foo.bar.baz')
                self.assertFalse(data.param_in_body)
                self.assertTrue(data.param_in_query)
                self.assertFalse(data.no_params)
                self.assertTrue(data.produces_json)
                self.assertFalse(data.produces_html)

            elif data.path == '/v1/auth/login':
                self.assertEqual(data.method, 'POST')
                self.assertEqual(data.handler_server, 'pnt_login.handlers.do_login')
                self.assertEqual(data.handler_client, 'login')
                self.assertIsNone(data.decorate_server)
                self.assertIsNone(data.decorate_request)
                self.assertTrue(data.param_in_body)
                self.assertFalse(data.param_in_query)
                self.assertFalse(data.param_in_path)
                self.assertFalse(data.no_params)
                self.assertTrue(data.produces_json)
                self.assertFalse(data.produces_html)

            elif data.path == '/v1/version':
                self.assertEqual(data.method, 'GET')
                self.assertEqual(data.handler_server, 'do_version')
                self.assertIsNone(data.handler_client)
                self.assertIsNone(data.decorate_request)
                self.assertEqual(data.decorate_server, 'foo.bar.baz')
                self.assertFalse(data.param_in_body)
                self.assertFalse(data.param_in_query)
                self.assertFalse(data.param_in_path)
                self.assertTrue(data.no_params)
                self.assertTrue(data.produces_json)
                self.assertFalse(data.produces_html)

            elif data.path == '/v1/versionhtml':
                self.assertEqual(data.method, 'GET')
                self.assertEqual(data.handler_server, 'do_version_html')
                self.assertIsNone(data.handler_client)
                self.assertIsNone(data.decorate_request)
                self.assertEqual(data.decorate_server, 'foo.bar.baz')
                self.assertFalse(data.param_in_body)
                self.assertFalse(data.param_in_query)
                self.assertFalse(data.param_in_path)
                self.assertTrue(data.no_params)
                self.assertFalse(data.produces_json)
                self.assertTrue(data.produces_html)

            elif data.path == '/v1/hybrib':
                self.assertEqual(data.method, 'GET')
                self.assertEqual(data.handler_server, 'do_version')
                self.assertIsNone(data.handler_client)
                self.assertIsNone(data.decorate_request)
                self.assertEqual(data.decorate_server, 'foo.bar.baz')
                self.assertTrue(data.param_in_body)
                self.assertFalse(data.param_in_query)
                self.assertTrue(data.param_in_path)
                self.assertTrue(data.no_params)
                self.assertTrue(data.produces_json)
                self.assertFalse(data.produces_html)

        spec.call_on_each_endpoint(test_callback)

        self.assertEqual(Tests.call_count, 5)