Connexion: Endpoint not found in main module

Created on 26 Nov 2015  路  12Comments  路  Source: zalando/connexion

With the current version of connexion I am getting messages such as the following:

Failed to add operation for GET /models/<model_id>
Traceback (most recent call last):
  File "/home/elezar/envs/connexion/lib/python3.4/site-packages/connexion/api.py", line 136, in add_paths
    self.add_operation(method, path, endpoint)
  File "/home/elezar/envs/connexion/lib/python3.4/site-packages/connexion/api.py", line 117, in add_operation
    validate_responses=self.validate_responses, resolver=self.resolver)
  File "/home/elezar/envs/connexion/lib/python3.4/site-packages/connexion/operation.py", line 90, in __init__
    self.__undecorated_function = resolver(self.operation_id)
  File "/home/elezar/envs/connexion/lib/python3.4/site-packages/connexion/utils.py", line 55, in get_function_from_name
    function = getattr(module, function_name)
AttributeError: 'module' object has no attribute 'get_model'

for each of the endpoints that I have defined.

I am starting the server locally using:
python app.py

I have noticed the following:

  1. The application still runs (and serves the pages), but this causes a lot of extra output at the start of the application.
  2. The error only occurs when the module in which the endpoint is defined is the main module (in this case app.py). If I have another module where the endpoint is defined (adjusting the operationId accordingle), the errors are not shown.

Any idea what could be the problem here?

enhancement in progress

Most helpful comment

Thanks, I will have a look at the new changes (and those of the resolver object that @kleijnweb is implementing for #107). For the time being, I was using the following:

class FdnaApi(connexion.App):
    def __init__(self, api_file, backend=None):
        super(FdnaApi, self).__init__(__name__,
                                      port=5000,
                                      specification_dir='swagger/',
                                      swagger_ui=True,
                                      debug=True)
        self._backend = backend
        self._api_file = api_file
        self.add_url_rule('/health', 'health', self.health, methods=['GET'])
        self.add_api(self._api_file, resolver=self.function_resolver)

    def function_resolver(self, operation_id):
        """Map the operation_id to the function in this class."""
        if '.' in operation_id:
            _, function_name = operation_id.rsplit('.', 1)
        else:
            function_name = operation_id
        function = getattr(self, function_name)
        return function

    def start_server(self):

        self.run()

    def health(self):
        """A health check endpoint for the api service."""
        return "OK"

where I explicitly map to function methods even if only a function name is provided as an operationId.

All 12 comments

@elezar can you post your Swagger YAML and app.py?

One idea: you are running Python 2 and therefore the module import will be relative, i.e. using "app" will actually import "connexion.app" (in connexion/utils.py).

It should work in Python 3 as it fixes this (module imports are absolute if they do not start with a dot).

I can confirm that I am running python3:

Python 3.4.3 (default, Oct 14 2015, 20:28:29) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

(running python3 app.py results in the same error messages).

The error also shows up in a docker container that I built for this project, so I don't think that the problems are configuration-specific.

The problem is also not limited to files named app.py. I have attached an example that reproduces the issue here:
dummy.yaml.txt
foo.py.txt

(please remove the .txt extensions in each case). I can simplify the example further if required.

@elezar ok, I see what your problem is: you are calling the connexion.App constructor during your module initialization, i.e. the functions are not yet defined at this point:

class FdnaApi(connexion.App):

    def __init__(self, api_file):
        super(FdnaApi, self).__init__(__name__,
                                      port=5000,
                                      specification_dir='.',
                                      debug=True)
        self.add_api(api_file)

# Setup the API server.
api = FdnaApi('dummy.yaml')

# ... Swagger functions are defined here AFTER running App.__init__

def get_model():
    # ...

You can see that the module import itself fails already:

$ python3 -c "import foo"

You need to ensure that importing your module does not create the app yet, or you create the app below your functions.

I added the "invalid" label, as this is not a bug in Connexion.

Thanks @hjacobs. I will move the construction to the start_server function instead.

As a general question, what is the best practice in terms of having a class handle the requests? I assume that a custom resolver would be the way to go?

@elezar I was thinking about making the lookup work for class instances too.

I think I ended up adding a resolver as a class method, which added "self"
to the functions. When I get to the office on Monday, I will add an example
or a snippet.

@elezar operationId can now point to instance, class or static methods (https://github.com/zalando/connexion/releases/tag/1.0.22)

Examples:

operationId: mymodule.MyClass.my_static_method
operationId: mymodule.MyClass.my_class_method
operationId: mymodule.myobj.my_method

Thanks, I will have a look at the new changes (and those of the resolver object that @kleijnweb is implementing for #107). For the time being, I was using the following:

class FdnaApi(connexion.App):
    def __init__(self, api_file, backend=None):
        super(FdnaApi, self).__init__(__name__,
                                      port=5000,
                                      specification_dir='swagger/',
                                      swagger_ui=True,
                                      debug=True)
        self._backend = backend
        self._api_file = api_file
        self.add_url_rule('/health', 'health', self.health, methods=['GET'])
        self.add_api(self._api_file, resolver=self.function_resolver)

    def function_resolver(self, operation_id):
        """Map the operation_id to the function in this class."""
        if '.' in operation_id:
            _, function_name = operation_id.rsplit('.', 1)
        else:
            function_name = operation_id
        function = getattr(self, function_name)
        return function

    def start_server(self):

        self.run()

    def health(self):
        """A health check endpoint for the api service."""
        return "OK"

where I explicitly map to function methods even if only a function name is provided as an operationId.

@elezar I release a new version https://github.com/zalando/connexion/releases/tag/1.0.25 --- can you check if it works for you (with the custom resolver)?

My custom resolver method still works as expected. Is this what you were asking, @hjacobs?

I am happy to close this issue as resolved.

:+1:

Was this page helpful?
0 / 5 - 0 ratings