Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

route use “/” return 404 #712

Open
tank2737 opened this issue Sep 15, 2019 · 5 comments
Open

route use “/” return 404 #712

tank2737 opened this issue Sep 15, 2019 · 5 comments
Labels

Comments

@tank2737
Copy link

tank2737 commented Sep 15, 2019

***** BEFORE LOGGING AN ISSUE *****

  • Is this something you can debug and fix? Send a pull request! Bug fixes and documentation fixes are welcome.
  • Please check if a similar issue already exists or has been closed before. Seriously, nobody here is getting paid. Help us out and take five minutes to make sure you aren't submitting a duplicate.
  • Please review the guidelines for contributing

Code

api = Api(app, doc='/doc'
          )
Bootstrap(app)


@app.route('/',methods=['GET','POST'])
def root_page():
    return redirect('/home')```

Repro Steps (if applicable)

  1. ...
  2. ...
  3. Broken!

Expected Behavior

A description of what you expected to happen.

Actual Behavior

A description of the unexpected, buggy behavior.

Error Messages/Stack Trace

If applicable, add the stack trace produced by the error

Environment

  • Python 3.7.3
  • Flask 1.0.3
  • Flask-RESTPlus 0.13
  • Other installed Flask extensions

Additional Context

This is your last chance to provide any pertinent details, don't let this opportunity pass you by!

@tank2737 tank2737 added the bug label Sep 15, 2019
@j5awry
Copy link
Collaborator

j5awry commented Sep 15, 2019

The code above looks like pure Flask and not flask-restplus. There a few things to consider:

  1. / by default with flask-restplus servers the swagger.json. by using flask instead of flask-restplus to set the endpoint, there very well could be something very odd happening.
  2. Removing flask-restplus code entirely from the above example, we know that flask will work
from flask import Flask
from flask_restplus import Resource, Api

app = Flask(__name__)


@app.route("/")
def get_home():
    return "I'm home"

if __name__ == '__main__':
    app.run(debug=True)

returns

I'm home

Now, adding in all the flask-restplus info and trying

from flask import Flask
from flask_restplus import Resource, Api

app = Flask(__name__)
api = Api(app, doc="/doc/")

ns = api.namespace('', description="base")


api.add_namespace(ns)


@ns.route('/')
class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}


if __name__ == '__main__':
    app.run(debug=True)

Couple big points above:

  1. I've moved the swagger endpoint to /doc/
  2. I'm using a namespace with empty string (meaning it sits at base, which is /)
  3. I'm adding the class, inheriting from Resource, and setting its route to /
  4. I'm ensuring the the namespace is added.

swagger.json and /doc shows the appropriate

{
    "swagger": "2.0",
    "basePath": "/",
    "paths": {
        "/ ": {
            "get": {
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                },
                "operationId": "get_hello_world",
                "tags": [
                    ""
                ]
            }
        }
    },
    "info": {
        "title": "API",
        "version": "1.0"
    },
    "produces": [
        "application/json"
    ],
    "consumes": [
        "application/json"
    ],
    "tags": [
        {
            "name": "",
            "description": "base"
        }
    ],
    "responses": {
        "ParseError": {
            "description": "When a mask can't be parsed"
        },
        "MaskError": {
            "description": "When any error occurs on mask"
        }
    }
}

however when hitting / I get a 404 (snippet from log)

127.0.0.1 - - [15/Sep/2019 12:59:55] "GET / HTTP/1.1" 404 -

I'll need to dig around a bit. I don't recall any issues with / being valid or not before, but I'm still fairly new to the maintenance of flask-restplus. I do know that the code provided in the description isn't exactly valid, but there still might be something there.

@SteadBytes
Copy link
Collaborator

I've also done some digging and have found the source of the problem (still thinking of the best solution though) in Api._register_doc. It will register the Swagger documentation before the root view:

app_or_blueprint.add_url_rule(self._doc, 'doc', self.render_doc)

This is to allow the documentation to be served at the default '/' path instead of the root view. When '/' is requested, the documentation view is called not the root view. The root view is never called.

In this case,self._doc == '/doc/' so '/' doesn't match the documentation view and the root view is called instead of the resource defined on the namespace because the URLs are matched in order. The root view simply aborts with a 404:

self.abort(HTTPStatus.NOT_FOUND)

@j5awry if you change your example to remove the doc='/doc/' parameter from the Api` constructor a 200 will be returned.

As for a solution, I'm going to have a think and hopefully come up with something over the weekend. Suggestions are welcomed! 😄

@SteadBytes
Copy link
Collaborator

The root view is needed, even if it by default it just 404s. It is used throughout Flask-RESTPlus when building URLs via route name. Therefore simply removing

app_or_blueprint.add_url_rule(self.prefix or '/', 'root', self.render_root)
isn't sufficient. The problem arises from the fact that routes can be added at any point prior to the Flask app being run i.e. not all routes are known when Api._init_app() is called (where the root route is currently registered from). Lazily registering the root route using Flask's before_first_request signal works:

# Api._init_app
app.before_first_request(partial(self._register_root, self.blueprint or app))

However, this means the root endpoint is technically not available until after a request has been made. Attempting to build a URL for it using url_for('root') will fail - not only does this break a large number of tests, I'm also not sure that it's behaviour we would want.

Still thinking on this one 🤔

@r2ewj
Copy link

r2ewj commented Dec 8, 2020

Hi did anyone find a solution to this? Thanks!

@niqzart
Copy link

niqzart commented Nov 2, 2021

While writting this, found the same solution in a comment on another issue

But why will I just erase my comment, right? Here it is:

from flask import Flask
from flask_restx import Api

app: Flask = Flask(__name__)


@app.route("/")
def welcome():
    return "Welcome!"


api = Api(app, doc="/doc/")

if __name__ == "__main__":
    app.run()

Important steps were:

  • add doc="/doc/" to Api, as others suggested
  • place the root route above the api creation
  • use the standard @app.route just for this one endpoint

Note, that docs do not display that endpoint, but you can fool them:

# Can be placed anywhere
# Even in a different file and then imported to the main one
from flask_restx import Namespace, Resource

base_namespace = Namespace("base", path="/")


@base_namespace.route("/")
class Test(Resource):
    @base_namespace.response(200, "A welcome response")
    def get(self):
        pass  # whatever you put here won't work
# after api initialization
api.add_namespace(base_namespace)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants