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:
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?
@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:
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:
where I explicitly map to function methods even if only a function name is provided as an
operationId.