title: Routing error when testing API made with Blueprint in Flask
body: <p>I'm having troubles when testing an API made with Flask-Restplus, integrating it into an existing application. </p>
  
  <p>The structure (not complete, the relevant pieces) of the project is as follows (root folder is called portal)</p>
  
  <pre><code>|
  |-api -&gt; Blueprint directory package
  |   |
  |   |-&gt; endpoints -&gt; package for endpoints
  |   |        |-&gt; __init__.py -&gt; Empty
  |   |        |-&gt; token.py -&gt; The token endpoint
  |   |-&gt; __init__.py -&gt; empty
  |   |-&gt; business.py -&gt; logic to create tokens
  |   |-&gt; restplus.py -&gt; API initialization
  |   |-&gt; serializer.py -&gt; token serializer
  |-tests -&gt; Tests folder
  |   |-&gt; helpers.py -&gt; Base classes for testing, setup, etc
  |   |-&gt; tests_api.py -&gt; API tests
  |-&gt; app.py -&gt; app initialization and config 
  |-&gt; main.py -&gt; Entry point
  </code></pre>
  
  <p><strong>main.py</strong></p>
  
  <pre><code>import sys
  
  from flask import Blueprint
  
  from portal.app import app
  from portal import libs
  
  from portal.api.restplus import api
  from portal.api.endpoints.token import ns as tokens_namespace
  
  
  if __name__ == '__main__':
      if len(sys.argv) == 2:
          port = int(sys.argv[1])
      else:
          port = 5000
  
      host = app.config.get('HOST', '127.0.0.1')
  
      # Configure the Blueprint for API
      blueprint = Blueprint('api', __name__, url_prefix='/api')
      api.init_app(blueprint)
      api.add_namespace(tokens_namespace)
      app.register_blueprint(blueprint)
      # For Dev environment we set the app root path to the current working directory
      import os
      app.root_path = os.getcwd()
      print "Running in", app.root_path, " with DEBUG=", app.config.get('DEBUG', False)
      app.run(host,
              port,
              app.config.get('DEBUG', False),
              use_reloader=True
      )
  </code></pre>
  
  <p><strong>app.py</strong></p>
  
  <pre><code>import sys
  import os
  import datetime
  import logging
  from logging import Formatter
  from logging.handlers import RotatingFileHandler
  
  from jinja2 import Environment, PackageLoader
  
  from flask import Flask, url_for, render_template, abort, request
  
  from utils.mail import CustomMail as Mail
  
  
  app = Flask(__name__)
  
  app.secret_key = "crackthis"
  app.config.from_pyfile('settings.py')
  
  if os.path.exists(os.path.join(app.root_path, 'local_settings.py')):
      app.config.from_pyfile('local_settings.py')
  
  
  app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://%s@%s/%s' % (
      app.config['DB_USER'], app.config['DB_HOST'], app.config['DB_NAME'])
  
  def in_test():
      return "".join(sys.argv).find('nosetests') &gt;= 0
  
  Environment(
      loader=PackageLoader('portal', 'templates')).globals['url_for'] = url_for
  
  mail = Mail(app)
  
  from filters import CUSTOM_FILTERS
  app.jinja_env.filters.update(CUSTOM_FILTERS)
  app.jinja_env.globals.update(zip=zip, now=datetime.datetime.now())
  
  
  # Initialize event handling
  from blinker import Namespace
  signals = Namespace()
  from portal.events import handlers
  
  def log_exception(exc_info, app_ctx):
      app.logger.exception(
          """
          Request:   {method} {path}
          IP:        {ip}
          Agent:     {agent_platform} | {agent_browser} {agent_browser_version}
          Raw Agent: {agent}
          user:      {user}
          """.format(
              method=request.method,
              path=request.path,
              ip=request.remote_addr,
              agent_platform=request.user_agent.platform,
              agent_browser=request.user_agent.browser,
              agent_browser_version=request.user_agent.version,
              agent=request.user_agent.string,
              user=app_ctx.logged_in
          )
      )
  
  
  @app.errorhandler(500)
  def server_error_page(error):
      from portal.utils.app_ctx import AppContext
      app_ctx = AppContext('connect', 'error')
      log_exception(error, app_ctx)
      return render_template("errors/server_error_new.html", app_ctx=app_ctx), 500
  
  @app.errorhandler(404)
  def server_page_not_found(error):
      from portal.utils.app_ctx import AppContext
      app_ctx = AppContext('connect', 'error')
      log_exception(error, app_ctx)
      return render_template("errors/page_not_found_new.html", app_ctx=app_ctx), 404
  </code></pre>
  
  <p>Now for the Blueprint part</p>
  
  <p><strong>token.py</strong></p>
  
  <pre><code>from flask import request
  from flask_restplus import Resource
  from portal.api.business import create_token, update_token, delete_token
  from portal.api.serializers import token
  from portal.api.restplus import api
  from portal.utils.api_helpers import api_authenticate, is_administrator, is_assistant
  from portal.models.api import ApiToken
  
  
  ns = api.namespace('tokens', description='Operations related to deal with tokens')
  
  
  @ns.route('/')
  class TokenCollection(Resource):
  
      parser = api.parser()
      parser.add_argument('email', type=str, help='Member username', location='form')
      parser.add_argument('password', type=str, help='Member password', location='form')
  
      @api.response(201, 'Token successfully created.')
      @api.response(404, 'Not user with provided credentials')
      @api.response(403, 'Provided password or username are invalid')
      @api.expect(parser)
      def post(self):
          """
          Creates a new token.
          """
          data = request.form
          response, code = create_token(data)
          return response, code
  
  
  @ns.route('/&lt;string:id&gt;')
  @api.response(404, 'Token not found.')
  class TokenItem(Resource):
  
      method_decorators = [is_administrator, api_authenticate]
  
      @api.marshal_with(token, code=200)
      @api.header('Auth-Token', 'Required field for accessing most API endpoints', required=True)
      def get(self, id):
          """
          Returns a Token.
          """
          token = ApiToken.query.filter(ApiToken.id == id).one()
          return token
  
  
      @api.response(204, 'Token successfully deleted.')
      @api.header('Auth-Token', 'Required field for accessing most API endpoints', required=True)
      def delete(self, id):
          """
          Deletes a Token.
          """
          delete_token(id)
          return None, 204
  </code></pre>
  
  <p><strong>business.py</strong></p>
  
  <pre><code>from datetime import datetime
  
  from portal.db import DB
  from portal.models import Program, Member
  from portal.models.api import ApiToken
  
  def create_token(data):
      """
      Creates a token to be used in furhter API calls
      :param data: A Dict containing username and password
      :return:
      """
      member = Member()
      member.from_dict(data)
  
      password = member.password
      username = member.email
  
      try:
          member = Member.objects.get(username__iexact=username, skip_auth=True)
      except Member.DoesNotExist:
          result = {'error': 'Not user with provided credentials'}
          code = 404
          return result, code
  
      if not member.check_password(password):
          result = {'error': 'Provided password or username are invalid'}
          code = 403
          return result, code
  
      try:
          token = ApiToken.objects.get(member=member)
      except ApiToken.DoesNotExist:
          token = ApiToken(member=member)
          token.save()
  
      code = 201
      return {'id': token.id,
          'token': token.token,
          'member_id': member.id,
          'date': str(token.date)}, code
  
  
  def update_token(token_id, data):
      """
      Updates date and deleted flag of specified token
      :param token_id: The token id
      :param data: The dict with data
      :return: The Token modifed
      """
      try:
          token = ApiToken.objects.get(id=token_id)
          token.date = datetime.utcnow
          token.is_deleted = data['is_deleted']
          token.save()
      except ApiToken.DoesNotExist:
          result = {'error': 'Could not find Auth Token with provided ID'}
          code = 404
          return result, code
  
      code = 204
      return {'id': token.id,
          'token': token.token,
          'member_id': token.member_id,
          'date': str(token.date)}, code
  
  
  def delete_token(token_id):
      """
      Deletes specified token if it exists
      :param token_id: The token ID
      :return:
      """
      try:
          ApiToken.objects.delete(id=token_id)
      except ApiToken.DoesNotExist:
          result = {'error': 'Could not find Auth Token with provided ID'}
          code = 404
          return result, code
  
      code = 204
      return 'Token deleted successfully', code
  </code></pre>
  
  <p><strong>restplus.py</strong></p>
  
  <pre><code>from flask_restplus import Api
  from sqlalchemy.orm.exc import NoResultFound
  
  api = Api(version='1.0', title='Wizbots API',
            description='Wizbots site Restful API')
  
  @api.errorhandler
  def default_error_handler(e):
      message = 'An unhandled exception occurred.'
      return {'message': message}, 500
  
  @api.errorhandler(NoResultFound)
  def database_not_found_error_handler(e):
      return {'message': 'A database result was required but none was found.'}, 404
  </code></pre>
  
  <p><strong>serializers.py</strong></p>
  
  <pre><code>from flask_restplus import fields
  from portal.api.restplus import api
  
  token = api.model('ApiToken', {
  'id': fields.String(readOnly=True, description='The unique identifier of a Token'),
  'token': fields.String(required=True, description='The token value'),
  'member_id': fields.String(required=True, description='The Member ID associated with token'),
  'date': fields.DateTime(required=True, description='Token creation time'),
  })
  </code></pre>
  
  <p>And now the test itself:</p>
  
  <p><strong>tests_api.py</strong></p>
  
  <pre><code>"""
  Test API endpoints
  """
  import unittest
  
  from portal.tests import helpers
  from portal.app import app
  from portal.views import (
  views, account, admin, enrollment,
  programs, lab, public)
  
  
  class ApiTests(helpers.ViewBase):
  
      ############################
      #### setup and teardown ####
      ############################
  
      def setUp(self):
          super(ApiTests, self).setUp()
          app.config['TESTING'] = True
          app.config['WTF_CSRF_ENABLED'] = False
          app.config['DEBUG'] = False
          self.assertEquals(app.debug, False)
  
      # executed after to each test
      def tearDown(self):
          pass
  
      ########################
      #### helper methods ####
      ########################
  
      ###############
      #### tests ####
      ###############
      def test_can_obtain_token(self):
          # Make a call to api endpoint to create a token
          form_data = dict()
          form_data['email'] = 'admin@wizbots.test.com'
          form_data['password'] = 'password'
          response = self.client.post('/api/tokens', data=form_data)
          # Make sure response is ok and contains and Auth-token
          self.assertEqual(response.status_code, 200)
          self.assertIn('token', response.data)
  
      def test_wrong_credentials_token(self):
          pass
  
      if __name__ == "__main__":
          unittest.main()
  </code></pre>
  
  <p>When running tests with nosetests:</p>
  
  <pre><code>nosetests -s tests/test_api.py:ApiTests.test_can_obtain_token
  </code></pre>
  
  <p>I keep on getting 404:</p>
  
  <pre><code>F
  ======================================================================
  FAIL: test_can_obtain_token (portal.tests.test_api.ApiTests)
  ----------------------------------------------------------------------
  Traceback (most recent call last):
    File "/home/internetmosquito/git/wizbots/portal/src/portal/tests/test_api.py", line 42, in test_can_obtain_token
      form_data = dict()
  AssertionError: 404 != 200
  -------------------- &gt;&gt; begin captured logging &lt;&lt; --------------------
  portal.app: ERROR: 
  Request:   POST /api/tokens
  IP:        None
  Agent:     None | None None
  Raw Agent: 
  user:      &lt;portal.models.member.AnonymousMember instance at 0x7f73b4654560&gt;
  
  Traceback (most recent call last):
    File "/home/internetmosquito/python_envs/wizbots/local/lib/python2.7/site-packages/flask/app.py", line 1639, in full_dispatch_request
  rv = self.dispatch_request()
    File "/home/internetmosquito/python_envs/wizbots/local/lib/python2.7/site-packages/flask/app.py", line 1617, in dispatch_request
  self.raise_routing_exception(req)
    File "/home/internetmosquito/python_envs/wizbots/local/lib/python2.7/site-packages/flask/app.py", line 1600, in raise_routing_exception
  raise request.routing_exception
  NotFound: 404: Not Found
  --------------------- &gt;&gt; end captured logging &lt;&lt; ---------------------
  
  ----------------------------------------------------------------------
  Ran 1 test in 3.967s
  </code></pre>
  
  <p>It just looks as if the /api/tokens endpoint is unreachable...needless to say this works fine if I fire up the server and use the endpoint either with curl or swaggerUI...any idea what I'm doing wrong? Thanks!</p>