Giter Club home page Giter Club logo

flask-restplus-server-example's Introduction

Build Status Coverage Status Codacy Coverage Status Codacy Quality Status Heroku

RESTful API Server Example

This project showcases my vision on how the RESTful API server should be implemented.

Author's vision update!

I used to use RESTful style APIs for quite a number of projects and this example was the finest foundation I ended up with, but I always felt limited by HTTP request-response nature and RESTful resources. Thus, I was looking for a new solution to the API problem space. I am currently happy with WAMP-proto specification (here is my barebones demo), so I can recommend it. I have also switched to Rust programming language. I am currently working on async/await-powered implementation of WAMP-proto in Rust. Stay tuned!

The goals that were achived in this example:

  • RESTful API server should be self-documented using OpenAPI (fka Swagger) specifications, so interactive documentation UI is in place;
  • Authentication is handled with OAuth2 and using Resource Owner Password Credentials Grant (Password Flow) for first-party clients makes it usable not only for third-party "external" apps;
  • Permissions are handled (and automaticaly documented);
  • PATCH method can be handled accordingly to RFC 6902;
  • Extensive testing with good code coverage.

I had to patch Flask-RESTplus (see flask_restplus_patched folder), so it can handle Marshmallow schemas and Webargs arguments.

Here is how it looks at this point of time (live demo):

Flask RESTplus Example API

Single File Example

This example should give you a basic understanding of what you can get with Flask, SQLAlchemy, Marshmallow, Flask-RESTplus (+ my patched extension), and OpenAPI.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_restplus_patched import Api, Namespace, Resource, ModelSchema

# Extensions initialization
# =========================
app = Flask(__name__)
db = SQLAlchemy(app)
api = Api(app)


# Database table definition (SQLAlchemy)
# ======================================
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(), nullable=False)


# Serialization/Deserialization schema definition
# ===============================================
class UserSchema(ModelSchema):
    class Meta:
        model = User


# "Users" resource RESTful API definitions
# ========================================
users_api = Namespace('users')
api.add_namespace(users_api)

@users_api.route('/')
class UsersList(Resource):

    @users_api.response(UserSchema(many=True))
    def get(self):
        return User.query.all()


@users_api.route('/<int:user_id>')
@users_api.resolve_object('user', lambda kwargs: User.query.get_or_404(kwargs.pop('user_id')))
class UserByID(Resource):

    @users_api.response(UserSchema())
    def get(self, user):
        return user


# Run the RESTful API server
# ==========================
if __name__ == '__main__':
    db.create_all()
    with db.session.begin(nested=True):
        db.session.add(User(name='user1'))
        db.session.add(User(name='user2'))
    app.run()

Save it, install the dependencies, and run it:

$ pip install -r app/requirements.txt
$ python server.py

Open http://127.0.0.1:5000 and examine the interactive documentation for your new RESTful API server! You can use any HTTP tools (e.g. cURL, wget, Python requests, or just a web browser) to communicate with it, or generate specialized API client libraries for many programming languages using Swagger Codegen (learn more in the API Integration section).

Note, this whole repo features much more than that; it demonstrates how I would organize a production-ready RESTful API server project, so stay tunned.

Project Structure

Root folder

Folders:

  • app - This RESTful API Server example implementation is here.
  • flask_restplus_patched - There are some patches for Flask-RESTPlus (read more in Patched Dependencies section).
  • migrations - Database migrations are stored here (see invoke --list to learn available commands, and learn more about PyInvoke usage below).
  • tasks - PyInvoke commands are implemented here.
  • tests - These are pytest tests for this RESTful API Server example implementation.
  • docs - It contains just images for the README, so you can safely ignore it.
  • deploy - It contains some application stack examples.

Files:

  • README.md
  • config.py - This is a config file of this RESTful API Server example.
  • conftest.py - A top-most pytest config file (it is empty, but it helps to have a proper PYTHON PATH).
  • .coveragerc - Coverage.py (code coverage) config for code coverage reports.
  • .travis.yml - Travis CI (automated continuous integration) config for automated testing.
  • .pylintrc - Pylint config for code quality checking.
  • Dockerfile - Docker config file which is used to build a Docker image running this RESTful API Server example.
  • .dockerignore - Lists files and file masks of the files which should be ignored while Docker build process.
  • .gitignore - Lists files and file masks of the files which should not be added to git repository.
  • LICENSE - MIT License, i.e. you are free to do whatever is needed with the given code with no limits.

Application Structure

app/
├── requirements.txt
├── __init__.py
├── extensions
│   └── __init__.py
└── modules
    ├── __init__.py
    ├── api
    │   └── __init__.py
    ├── auth
    │   ├── __init__.py
    │   ├── models.py
    │   ├── parameters.py
    │   └── views.py
    ├── users
    │   ├── __init__.py
    │   ├── models.py
    │   ├── parameters.py
    │   ├── permissions.py
    │   ├── resources.py
    │   └── schemas.py
    └── teams
        ├── __init__.py
        ├── models.py
        ├── parameters.py
        ├── resources.py
        └── schemas.py
  • app/requirements.txt - The list of Python (PyPi) requirements.
  • app/__init__.py - The entrypoint to this RESTful API Server example application (Flask application is created here).
  • app/extensions - All extensions (e.g. SQLAlchemy, OAuth2) are initialized here and can be used in the application by importing as, for example, from app.extensions import db.
  • app/modules - All endpoints are expected to be implemented here in logicaly separated modules. It is up to you how to draw the line to separate concerns (e.g. you can implement a monolith blog module, or split it into topics+comments modules).

Module Structure

Once you added a module name into config.ENABLED_MODULES, it is required to have your_module.init_app(app, **kwargs) function. Everything else is completely optional. Thus, here is the required minimum:

your_module/
└── __init__.py

, where __init__.py will look like this:

def init_app(app, **kwargs):
    pass

In this example, however, init_app imports resources and registeres api (an instance of (patched) flask_restplus.Namespace). Learn more about the "big picture" in the next section.

Where to start reading the code?

The easiest way to start the application is by using PyInvoke command app.run implemented in tasks/app/run.py:

$ invoke app.run

The command creates an application by running app/__init__.py:create_app() function, which in its turn:

  1. loads an application config;
  2. initializes extensions: app/extensions/__init__.py:init_app();
  3. initializes modules: app/modules/__init__.py:init_app().

Modules initialization calls init_app() in every enabled module (listed in config.ENABLED_MODULES).

Let's take teams module as an example to look further. app/modules/teams/__init__.py:init_app() imports and registers api instance of (patched) flask_restplus.Namespace from .resources. Flask-RESTPlus Namespace is designed to provide similar functionality as Flask Blueprint.

api.route() is used to bind a resource (classes inherited from flask_restplus.Resource) to a specific route.

Lastly, every Resource should have methods which are lowercased HTTP method names (i.e. .get(), .post(), etc). This is where users' requests end up.

Dependencies

Project Dependencies

Build Dependencies

I use pyinvoke with custom tasks to maintain easy and nice command-line interface. Thus, it is required to have invoke Python package installed, and optionally you may want to install colorlog, so your life become colorful.

Patched Dependencies

  • flask-restplus is patched to handle marshmallow schemas and webargs input parameters (GH #9).
  • swagger-ui (the bundle is automatically downloaded on the first run) just includes a pull-request to support Resource Owner Password Credentials Grant OAuth2 (aka Password Flow) (PR #1853).

Installation

Using Docker

It is very easy to start exploring the example using Docker:

$ docker run -it --rm --publish 5000:5000 frolvlad/flask-restplus-server-example

From sources

Clone the Project

$ git clone https://github.com/frol/flask-restplus-server-example.git

Setup Environment

It is recommended to use virtualenv or Anaconda/Miniconda to manage Python dependencies. Please, learn details yourself.

You will need invoke package to work with everything related to this project.

$ pip install -r tasks/requirements.txt

Run Server

NOTE: All dependencies and database migrations will be automatically handled, so go ahead and turn the server ON! (Read more details on this in Tips section)

$ invoke app.run

Deploy Server

In general, you deploy this app as any other Flask/WSGI application. There are a few basic deployment strategies documented in the ./deploy/ folder.

Quickstart

Open online interactive API documentation: http://127.0.0.1:5000/api/v1/

Autogenerated swagger config is always available from http://127.0.0.1:5000/api/v1/swagger.json

example.db (SQLite) includes 2 users:

  • Admin user root with password q
  • Regular user user with password w

NOTE: Use On/Off switch in documentation to sign in.

Authentication Details

This example server features OAuth2 Authentication protocol support, but don't be afraid of it! If you learn it, OAuth2 will save you from a lot of troubles.

Authentication with Login and Password (Resource Owner Password Credentials Grant)

Here is how you authenticate with user login and password credentials using cURL:

$ curl 'http://127.0.0.1:5000/auth/oauth2/token?grant_type=password&client_id=documentation&username=root&password=q'
{
    "token_type": "Bearer",
    "access_token": "oqvUpO4aKg5KgYK2EUY2HPsbOlAyEZ",
    "refresh_token": "3UTjLPlnomJPx5FvgsC2wS7GfVNrfH",
    "expires_in": 3600,
    "scope": "auth:read auth:write users:read users:write teams:read teams:write"
}

That is it!

Well, the above request uses query parameters to pass client ID, user login and password which is not recommended (even discouraged) for production use since most of the web servers logs the requested URLs in plain text and we don't want to leak sensitive data this way. Thus, in practice you would use form parameters to pass credentials:

$ curl 'http://127.0.0.1:5000/auth/oauth2/token?grant_type=password' -F 'client_id=documentation' -F 'username=root' -F 'password=q'

, or even pass client_id as Basic HTTP Auth:

$ curl 'http://127.0.0.1:5000/auth/oauth2/token?grant_type=password' --user 'documentation:' -F 'username=root' -F 'password=q'

You grab the access_token and put it into Authorization header to request "protected" resources:

$ curl --header 'Authorization: Bearer oqvUpO4aKg5KgYK2EUY2HPsbOlAyEZ' 'http://127.0.0.1:5000/api/v1/users/me'
{
    "id": 1,
    "username": "root",
    "email": "root@localhost",
    "first_name": "",
    "middle_name": "",
    "last_name": "",
    "is_active": true,
    "is_regular_user": true,
    "is_admin": true,
    "created": "2016-10-20T14:00:35.912576+00:00",
    "updated": "2016-10-20T14:00:35.912602+00:00"
}

Once the access token expires, you can refresh it with refresh_token. To do that, OAuth2 RFC defines Refresh Token Flow (notice that there is no need to store user credentials to do the refresh procedure):

$ curl 'http://127.0.0.1:5000/auth/oauth2/token?grant_type=refresh_token' --user 'documentation:' -F 'refresh_token=3UTjLPlnomJPx5FvgsC2wS7GfVNrfH'
{
    "token_type": "Bearer",
    "access_token": "FwaS90XWwBpM1sLeAytaGGTubhHaok",
    "refresh_token": "YD5Rc1FojKX1ZY9vltMSnFxhm9qpbb",
    "expires_in": 3600,
    "scope": "auth:read auth:write users:read users:write teams:read teams:write"
}

Authentication with Client ID and Secret (Client Credentials Grant)

Here is how you authenticate with user login and password credentials using cURL:

$ curl 'http://127.0.0.1:5000/auth/oauth2/token?grant_type=client_credentials' --user 'documentation:KQ()SWK)SQK)QWSKQW(SKQ)S(QWSQW(SJ*HQ&HQW*SQ*^SSQWSGQSG'
{
    "token_type": "Bearer",
    "access_token": "oqvUpO4aKg5KgYK2EUY2HPsbOlAyEZ",
    "expires_in": 3600,
    "scope": "teams:read users:read users:write teams:write"
}

The same way as in the previous section, you can grab the access_token and access protected resources.

API Integration

One of the key point of using OpenAPI (Swagger) specification is that it enables automatic code generation. Swagger Codegen project implements client library and server stub generators for over 18 programming languages! There are also many other projects with OpenAPI specification support, so if you lack anything in the official tooling, search for third-party solutions.

I have had a need to work with my API servers from Python and JavaScript, so I started with Swagger Codegen Python and JavaScript generators. Very soon I realized that most (if not all) Swagger Codegen generators lack OAuth2 support, but other than that, the client libraries look fine (I have contributed a few changes to Python and JavaScript generators over the time, and the nice thing all the clients benefit from contributions into a single project). Thus, @khorolets and I implemented hacky OAuth2 support for Python client and even more hacky out-of-client helpers for JavaScript (hopefully, one day OAuth2 support will be contributed into the Swagger Codegen upstream).

To use Swagger Codegen, you only need a swagger.json file describing your API server. You can get one by accessing http://127.0.0.1:5000/api/v1/swagger.json, or by running an Invoke task:

$ invoke app.swagger

NOTE: Use stdout rediction to save the output into a file.

To further simplify the codegeneration, there is another Invoke task:

$ invoke app.swagger.codegen --language python --version 1.0.0

To run that, however, you will need Docker installed on your machine since we use Swagger Codegen as a Docker image. Once that is completed, you will have a Python client in the clients/python/dist/ folder. The javascript client can be generated just the same way. Read the generated clients/*/dist/README.md to learn how to use those clients.

NOTE: As mentioned above, a slightly modified Swagger Codegen version is used to enable OAuth2 support in Python client.

Integrations with Flask-* Projects

Since this project is only an extension to Flask, most (if not all) Flask plugins should work.

Verified compatible projects:

  • flask-sqlalchemy
  • flask-login
  • flask-marshmallow
  • flask-oauthlib
  • flask-cors
  • flask-limiter

Example integration steps

flask-limiter

  1. Add flask-limiter to end of the app/requirements.txt file, so it gets installed when the application is deployed.

  2. Apply the relevant changes to app/extensions/__init__.py:

    # ... other imports.
    
    from flask_limiter import Limiter
    from flask_limiter.util import get_remote_address
    
    # change limiter configs per your project needs.
    limiter = Limiter(key_func=get_remote_address, default_limits=["1 per minute"])
    
    from . import api
    
    def init_app(app):
        """
        Application extensions initialization.
        """
        for extension in (
                # ... other extensions.
                limiter,  # Add this
            ):
            extension.init_app(app)
  3. (Optional) Set endpoint-specific limits:

    from app.extensions import limiter
    
    @api.route('/account/verify')
    class IdentityVerify(Resource):
        """
        Handle identity verification.
        """
        # Notice this is different from the simple example at the top of flask-limiter doc page.
        # The reason is explained here: https://flask-limiter.readthedocs.io/en/stable/#using-flask-pluggable-views
        decorators = [limiter.limit("10/second")] # config as you need. 
    
        @api.parameters(parameters.SomeParameters())
        @api.response(schemas.SomeSchema())
        def post(self, args):
            return {"verified": True}

Tips

Once you have invoke, you can learn all available commands related to this project from:

$ invoke --list

Learn more about each command with the following syntax:

$ invoke --help <task>

For example:

$ invoke --help app.run
Usage: inv[oke] [--core-opts] app.run [--options] [other tasks here ...]

Docstring:
  Run DDOTS RESTful API Server.

Options:
  -d, --[no-]development
  -h STRING, --host=STRING
  -i, --[no-]install-dependencies
  -p, --port
  -u, --[no-]upgrade-db

Use the following command to enter ipython shell (ipython must be installed):

$ invoke app.env.enter

app.run and app.env.enter tasks automatically prepare all dependencies (using pip install) and migrate database schema to the latest version.

Database schema migration is handled via app.db.* tasks group. The most common migration commands are app.db.upgrade (it is automatically run on app.run), and app.db.migrate (creates a new migration).

You can use better_exceptions package to enable detailed tracebacks. Just add better_exceptions to the app/requirements.txt and import better_exceptions in the app/__init__.py.

Marshmallow Tricks

There are a few helpers already available in the flask_restplus_patched:

  • flask_restplus_patched.parameters.Parameters - base class, which is a thin wrapper on top of Marshmallow Schema.
  • flask_restplus_patched.parameters.PostFormParameters - a helper class, which automatically mark all the fields that has no explicitly defined location to be form data parameters.
  • flask_restplus_patched.parameters.PatchJSONParameters - a helper class for the common use-case of RFC 6902 describing JSON PATCH.
  • flask_restplus_patched.namespace.Namespace.parameters - a helper decorator, which automatically handles and documents the passed Parameters.

You can find the examples of the usage throughout the code base (in /app/modules/*).

JSON Parameters

While there is an implementation for JSON PATCH Parameters, there are other use-cases, where you may need to handle JSON as input parameters. Naturally, JSON input is just a form data body text which is treated as JSON on a server side, so you only need define a single variable called body with location='json':

class UserSchema(Schema):
    id = base_fields.Integer(required=False)
    username = base_fields.String(required=True)


class MyObjectSchema(Schema):
    id = base_fields.Integer(required=True)
    priority = base_fields.String(required=True)
    owner = base_fields.Nested(UserSchema, required=False)


class CreateMyObjectParameters(Parameters):
    body = base_fields.Nested(MyObjectSchema, location='json', required=True)


api = Namespace('my-objects-controller', description="My Objects Controller", path='/my-objects')


@api.route('/')
class MyObjects(Resource):
    """
    Manipulations with My Objects.
    """

    @api.parameters(CreateMyObjectParameters())
    @api.response(MyObjectSchema())
    @api.response(code=HTTPStatus.CONFLICT)
    @api.doc(id='create_my_object')
    def post(self, args):
        """
        Create a new My Object.
        """
        return create_my_object(args)

Useful Links

flask-restplus-server-example's People

Contributors

10000tb avatar askz avatar belkka avatar donggangcj avatar duranda avatar frol avatar jaza avatar khorolets avatar magicjohnjang avatar modgahead avatar patlachance avatar sloria avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-restplus-server-example's Issues

Support for JSON parameters when many=False

When using api.parameters decorator, the locations parameter is not taken into account to generate the documentation (@api.parameters(parameters.MyParameters(many=False), locations=('json',) ).
My current workaround is to use the namespace.expect decorator with a flask_restplus model but it is not very consistent.

I would happily submit a pull request if someone point me in the right direction on how to add this.

Add more tests

  • tests to auth module
  • tests to users resources
  • tests to teams resources (not 100%, but good coverage enough for now)
  • tests to extensions

Upgrade Flask-RESTplus to 0.9.0

0.9.0 release included a lot of breaking changes, including dropping Flask-RESTful dependency and improving namespaces support.

Make patched Flask-RESTplus standalone

Hi @frol,

I was wondering if there were any plans about merging this patched version of Flask-RESTplus into the Flask-RESTplus repository some day (or making it a stand-alone package) ? So that it can be used independently from the rest of this example. Also would there be a very simple example somewhere (similar to the flask-restplus quickstart example) on how to use this patched version with marshmallow and webargs? Thank you!

how to enable debugger

Hi there,
How can I enable debugger in this kit? I assumed it's already active but i get Internal Server Error and nothing shows up on console or browser.

Trouble distinguishing flask-marshmallow problems from embedded hack

Hi -
Thanks for a GREAT example! I am trying use it as the starting point for a small service I am writing. I do not seem to be able to get the custom validators and or pre and post request processing to work. However, I am not well versed in Marshmallow, so do not know if they are my problems or problems with the integration hack.

Would you consider adding a field to teams with a custom validator (I think this is the marshmallow way) The one I am trying to add is a custom date field, simplest case - existing or string 'now'.

The other things I am looking at integrating (once I have a bit more understanding about 'schemas' are:
guid instead of or in addition to id fields
jwt authentication (then I can separate out the oauth part)

Improve `Namespace.response` decorator to handle HTTP/204 responses with no need to provide a Schema

Currently, schema (model) is required for all non-error HTTP codes. There is no need to bother with models when HTTP/204 (No content) is going to be responded, so no model might be valid option in the Namespace.response decorator.

I would try to implement it as missing model, something along these lines, but you will need to change the latter code do expect None model:

- if model is None:
+ if model is None and code != 204:

As a subtask, it would be nice to find a place where HTTP/204 code is defined as constant (Flask or Werkzeug might have it).

/cc @khorolets

Namespace.parameters(locations=('json', 'files')) needs testing

It is reported that locations=('json', 'files') in @Namespace.parameters decorator breaks Swagger config:

@api.parameters(
        parameters.CreateTeamParameters(),
        locations=('json', 'files')
)
def post(self, ...):
...

The Swagger config results in:

"schema": {
                            "properties": {
                                "source_file": {
                                    "type": "file"
                                },
                                ...
                            },
                            "type": "object"
                        }

Instead of

"parameters": [
                    {
                        "in": "formData",
                        "name": "source_file",
                        "required": false,
                        "type": "file"
                    },
                    ...
                ]

This might be related to #20.

/cc @DurandA

[Python 2.7] add_namespace() got an unexpected keyword argument 'path'

When I run py.test -x I've got the error from subject.

I've checked no error with python 3, only with 2.7

Trace:


    def test_create_app():
        try:
>           create_app()

tests/test_app_creation.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
app/__init__.py:61: in create_app
    modules.init_app(app)
app/modules/__init__.py:17: in init_app
    import_module('.%s' % module_name, package=__name__).init_app(app, **kwargs)
app/modules/auth/__init__.py:40: in init_app
    api_v1.add_namespace(resources.api)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <app.extensions.api.api.Api object at 0x10bef97d0>, ns = <app.extensions.api.namespace.Namespace object at 0x10c1d2dd0>, path = None

    def add_namespace(self, ns, path=None):
        # Rewrite security rules for OAuth scopes since Namespaces don't have
        # enough information about authorization methods.
        for resource, _, _ in ns.resources:
            for method in resource.methods:
                method_func = getattr(resource, method.lower())

                if (
                        hasattr(method_func, '__apidoc__')
                        and
                        'security' in method_func.__apidoc__
                        and
                        '__oauth__' in method_func.__apidoc__['security']
                ):
                    oauth_scopes = method_func.__apidoc__['security']['__oauth__']['scopes']
                    method_func.__apidoc__['security'] = {
                        auth_name: oauth_scopes
                        for auth_name, auth_settings in iteritems(self.authorizations)
                        if auth_settings['type'].startswith('oauth')
                    }

>       super(Api, self).add_namespace(ns, path=path)
E       TypeError: add_namespace() got an unexpected keyword argument 'path'

app/extensions/api/api.py:52: TypeError

Multi-tentant architecture

Hi,

I would like to know what do you think about using this example for multi-tenant application.
Is it necessary to add some tenant table, or could I extend the Team model and use it as tenant model ? (because I don't need Team model anyway)
Let me know, thanks !

Running invoke app.run breaks in argspec

Hey,

Thanks again but I'm running into further issues:

>> FLASK_CONFIG=development invoke app.run                                                                      [flask_restplus_example]ilaif@Ilai
Traceback (most recent call last):
  File "/Users/ilaif/.virtualenvs/flask_restplus_example/bin/inv", line 11, in <module>
    sys.exit(program.run())
  File "/Users/ilaif/.virtualenvs/flask_restplus_example/lib/python2.7/site-packages/invoke/program.py", line 269, in run
    self._parse(argv)
  File "/Users/ilaif/.virtualenvs/flask_restplus_example/lib/python2.7/site-packages/invoke/program.py", line 327, in _parse
    self.parse_tasks()
  File "/Users/ilaif/.virtualenvs/flask_restplus_example/lib/python2.7/site-packages/invoke/program.py", line 488, in parse_tasks
    self.parser = Parser(contexts=self.collection.to_contexts())
  File "/Users/ilaif/.virtualenvs/flask_restplus_example/lib/python2.7/site-packages/invoke/collection.py", line 325, in to_contexts
    name=primary, aliases=aliases, args=task.get_arguments()
  File "/Users/ilaif/.virtualenvs/flask_restplus_example/lib/python2.7/site-packages/invoke/tasks.py", line 193, in get_arguments
    arg_names, spec_dict = self.argspec(self.body)
  File "/Users/ilaif/Desktop/flask-restplus-server-example/tasks/app/_utils.py", line 26, in argspec
    return super(Task, self).argspec(body)
  File "/Users/ilaif/.virtualenvs/flask_restplus_example/lib/python2.7/site-packages/invoke/tasks.py", line 143, in argspec
    context_arg = arg_names.pop(0)
IndexError: pop from empty list

Any thoughts?

Regular user is forbidden to access team he just created

If create team by regular user and then try to GET /teams/:id we get

{
  "message": "You don't have the permission to access the requested resource.",
  "status": 403
}

It seems to happen because team is created without any member. I think the creator should become the first member of team.

How do you deal with circular imports ?

For example I have models like this :

# modules/patients/models.py
class Patient(db.Model, OwnerMixin):	
	__owner_backref_name__ = "patients"

	id = db.Column(db.Integer, primary_key=True)

	phone = db.Column(db.String)
	email = db.Column(db.String)
	first_name = db.Column(db.String)
	last_name = db.Column(db.String)
	birthday = db.Column(db.DateTime)
	zip_code = db.Column(db.String)
	address = db.Column(db.String)

        # I would like to do this, but there's circular import....
       @aggregated('claims', db.Column(db.Integer))
	def unpaid_amount(self):
		return db.func.count('1')

# modules/accounting/models.py
class Claim(db.Model, OwnerMixin):
	__owner_backref_name__ = 'claims'

	id = db.Column(db.Integer, primary_key=True)

	date = db.Column(db.DateTime, nullable=False)
	label = db.Column(db.String, nullable=False)
	amount = db.Column(db.Float, nullable=False)
	ref = db.Column(db.String)	

	patient_id = db.Column(db.Integer, db.ForeignKey('patient.id'), nullable=False)
	patient = db.relationship(
				Patient,
				backref=db.backref('claims', cascade="delete, delete-orphan")
	)

Patient is in dedicated module "patient"
and claim model is in 'accounting' modules with other models related to accounting.
The problem is I can't import claim model and I can't access to claim model in patient model in order to, for example, make the sum of claims amount.
I think this is some design issue, but I want to be sure if there's any workaround before rewriting....

Unable to use validators with PatchJSONParameters

I am not able to use the validates decorator using PatchJSONParameters.

For example, using the following code raise a ValueError (field does not exist) while there are fields in PATH_CHOICES.

@validates('name')
def validate_name(self, data):
    if len(data)<3:
        raise ValidationError('Too short!')

I am not even sure that this is an issue as I don't know how I am supposed to do it. I can see there is already some code to map validators with patched fields in PatchJSONParameters.__init__:

self.fields['op'].validators = \
    self.fields['op'].validators + [validate.OneOf(self.OPERATION_CHOICES)]
self.fields['path'].validators = \
    self.fields['path'].validators + [validate.OneOf(self.PATH_CHOICES)]

Splitting Resources out into Multiple Files

Consider the folowing:

api = Namespace('users', description="Users")

........

@api.route('/<int:user_id>')
@api.resolve_object_by_model(User, 'user')
class UserByID(Resource):
...

@api.route('/<int:user_id>/accounts/')

Pretty straightforward, nesting "accounts" under the users namespace. I would like to split my resource into multiple files, seems simple enough:

resources/users

api = Namespace('users', description="Users")
@api.route('/<int:user_id>')

resources/accounts

api = Namespace('users', description="Users")
@api.route('/<int:user_id>/accounts/')

resources/init.py

from .users import api as userApi
from .accounts import api as userAccountApi
from .logins import api as userLoginApi

init.py

    from .resources import userApi, userLoginApi, userAccountApi

    api_v1.add_namespace(userApi)
    api_v1.add_namespace(userLoginApi)
    api_v1.add_namespace(userAccountApi)

This does work, but gives some oddities in the swagger related test cases:

Failed validating 'uniqueItems' in schema['properties']['tags']:
E               {'items': {'$ref': '#/definitions/tag'},
E                'type': 'array',
E                'uniqueItems': True}
E           
E           On instance['tags']:
E               [{'description': 'Default namespace', 'name': 'default'},
E                {'description': 'Authentication', 'name': 'auth'},
E                {'description': 'Teams', 'name': 'teams'},
E                {'description': 'Users', 'name': 'users'},
E                {'description': 'Users', 'name': 'users'},
E                {'description': 'Users', 'name': 'users'}]

How would one properly set this up?

Running example "invoke app.run" fails with "Type Error:Tasks must have an initial Context argument"

Hey,

Sounds like a great project. However I'm trying to run it on my local machine (OSX El-Capitan, Python 2.7.10) and I receive an error:

>> invoke app.run
2016-11-18 12:15:27,067 [WARNING] [tasks.app.db] Alembic cannot be imported, so some app.db.* tasks won't be available!
Traceback (most recent call last):
  File "/usr/local/bin/invoke", line 11, in <module>
    sys.exit(program.run())
  File "/usr/local/lib/python2.7/site-packages/invoke/program.py", line 269, in run
    self._parse(argv)
  File "/usr/local/lib/python2.7/site-packages/invoke/program.py", line 325, in _parse
    self.load_collection()
  File "/usr/local/lib/python2.7/site-packages/invoke/program.py", line 473, in load_collection
    coll = loader.load(coll_name) if coll_name else loader.load()
  File "/usr/local/lib/python2.7/site-packages/invoke/loader.py", line 53, in load
    module = imp.load_module(name, fd, path, desc)
  File "/Users/ilaif/Desktop/flask-restplus-server-example/tasks/__init__.py", line 61, in <module>
    from . import app
  File "/Users/ilaif/Desktop/flask-restplus-server-example/tasks/app/__init__.py", line 8, in <module>
    from . import dependencies, env, db, run, users
  File "/Users/ilaif/Desktop/flask-restplus-server-example/tasks/app/db.py", line 272, in <module>
    def init_development_data(context, upgrade_db=True, skip_on_failure=False):
  File "/Users/ilaif/Desktop/flask-restplus-server-example/tasks/app/_utils.py", line 58, in app_context_task
    return Task(wrapper, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/invoke/tasks.py", line 58, in __init__
    self.positional = self.fill_implicit_positionals(positional)
  File "/usr/local/lib/python2.7/site-packages/invoke/tasks.py", line 149, in fill_implicit_positionals
    args, spec_dict = self.argspec(self.body)
  File "/Users/ilaif/Desktop/flask-restplus-server-example/tasks/app/_utils.py", line 21, in argspec
    return super(Task, self).argspec(body)
  File "/usr/local/lib/python2.7/site-packages/invoke/tasks.py", line 144, in argspec
    raise TypeError("Tasks must have an initial Context argument!")
TypeError: Tasks must have an initial Context argument!

I must be missing something very trivial, I event tried to manually pip install the requirements to make sure I have all the dependencies.

Thanks,
Ilai.

Nested path for PATCH operations ?

imagine I have a profile linked to my user and I want to expose only one route (/users/) to patch the twos, is it actually possible ?
Seems like the actual implementation isn't compatible ? is it ?

I created two tuples and merged them with :
PATH_CHOICES = USER_CHOICES (as /%s) + PROFILE_CHOICES (as /profile/%s)

But I get this error :

marshmallow.exceptions.ValidationError: Field 'parts_number' does not exist, so it cannot be patched

[Invoke task] New module boilerplate

I'd like to have something like

invoke app.newmodule <name_of_my_module>

Which create an empty module with necessary stuff to create write a module.

Now we need to copy-paste some existing module then clean it and rewrite to have a new one. Just want to simplify this process.

@frol what do you think? What stuff if necessary for a new module.

BTW we can have similar task for new extension (it'd be better to create separate issue for that if you are interested)

UPDATE

And I don't like app.newmodule it is first that came to my head, ready to discuss any variants :)

no os.path.commonpath support

It seems that commonpath module is not supported in some python versions.
I'm using Python 2.7.6 and it's not supported.
Instead, I used not os.path.commonprefix.

ImportError: cannot import name 'force_auto_coercion'

On centos 6 and python 3.4 when executing invoke app.run following error occurs

2017-01-11 13:04:17,614 [INFO] [tasks.app.dependencies] Project dependencies are installed. 2017-01-11 13:04:17,615 [INFO] [tasks.app.dependencies] Installing Swagger UI assets... 2017-01-11 13:04:17,616 [INFO] [tasks.app.dependencies] Downloading Swagger UI assets... 2017-01-11 13:04:17,909 [INFO] [tasks.app.dependencies] Unpacking Swagger UI assets... 2017-01-11 13:04:18,069 [INFO] [tasks.app.dependencies] Swagger UI is installed. Traceback (most recent call last): File "/usr/bin/invoke", line 11, in <module> sys.exit(program.run()) File "/usr/lib/python3.4/site-packages/invoke/program.py", line 274, in run self.execute() File "/usr/lib/python3.4/site-packages/invoke/program.py", line 389, in execute executor.execute(*self.tasks) File "/usr/lib/python3.4/site-packages/invoke/executor.py", line 113, in execute result = call.task(*args, **call.kwargs) File "/usr/lib/python3.4/site-packages/invoke/tasks.py", line 111, in __call__ result = self.body(*args, **kwargs) File "/root/flask-restplus-server-example-master/tasks/app/run.py", line 40, in run app = create_app() File "/root/flask-restplus-server-example-master/app/__init__.py", line 62, in create_app from . import extensions File "/root/flask-restplus-server-example-master/app/extensions/__init__.py", line 15, in <module> from sqlalchemy_utils import force_auto_coercion, force_instant_defaults ImportError: cannot import name 'force_auto_coercion'

_invalid_response (potential) misusage

Here, you're trying to override _invalid_response in OAuthProvider object.

Do you really need this trick? AFAIU, the proper way to do this would be to register your custom function using invalid_response.

Example using it as a decorator (as seen here):

    @oauth.invalid_response
    def require_oauth_invalid(req):
        api.abort(code=http_exceptions.Unauthorized.code)

How are required properties bubbling up to Swagger.json definition?

Looking at the example the swagger.json file it appears to be generating fields that are required.

For example: https://gist.github.com/xamox/7bf2fafedb303ec25f397dfb72fcaeb5#file-swagger-json-L1463

It says username required.

When I look at the schema, it appears to be using the model:
https://github.com/frol/flask-restplus-server-example/blob/master/app/modules/teams/schemas.py#L15

using this model:
https://github.com/frol/flask-restplus-server-example/blob/master/app/modules/teams/models.py#L21

Which seems to have a sqlalchemy relationship to the User model.
https://github.com/frol/flask-restplus-server-example/blob/master/app/modules/users/models.py#L41

The only place that I can see anywhere that it maybe required is in the users parameters.py file.
https://github.com/frol/flask-restplus-server-example/blob/master/app/modules/users/parameters.py#L25

But those other fields (email, password, recaptcha) are listed as required, but don't appear to be a required field in the swagger.yaml. Is there somewhere that is stripping them from being required? Or is it actually setup/propagated somewhere that I may not know about?

@api.header not working

Hello Frol, how are you?
As you do to specify that it is necessary to use a token in the header, once your patch has made the @api.header notation stop working.

../Scripts/invoke No Such File or Directory

Hi,
I have cloned the repository and trying to run the project. I am using command invoke app.run. All dependencies are installing successfully. Migrations are running good. I have checked the db and all tables are created. However i am getting error:
C:\Users\Administrator\AppData\Local\Programs\Python\Python35\Scripts\invoke [Errorno 2] No Such File or Directory.
I have checked that invoke.py is present in above mentioned path.
Any workaround for this problem? I am running it on windows 7 64 bit with python 3.5.2

Restrict responses with only documented types (@api.response(code=...))

#26 showed that there might be a situation when all tests pass, the response is expected, but it was not explicitly covered with @api.response() decorator. I think we will need to override Api.output from flask_restplus/api.py (note, there is already customization on Api class in flask_restplus_patched, so you can just add this patch there), so we can check what responses are defined on the resource, and the current response is expected (don't forget about special care around HTTP/500 internal error, this can happen anywhere and shouldn't be shadowed).

Swagger: TypeEror: request[method] is not a function

If we add 'options' to supportedSubmitMethods here https://github.com/frol/flask-restplus-server-example/blob/master/app/templates/swagger-ui.html#L13

Then button 'Try it out' appears in Swagger under OPTIONS block, but if we try to do a request we got an error in console.

Found a thread with issue that looks similar to current, tried to add code from the link to commit into swagger-ui.js -- didn't help. Link to thread: https://groups.google.com/forum/#!topic/swagger-swaggersocket/K021AxKqXVM

token request does not return expiration time

I'm using the Resource Owner Password Flow. It works successfully, but it leaves out the expiration information. Here is the debug logging info:

2017-06-19 18:22:17,940 [INFO] [werkzeug] 127.0.0.1 - - [19/Jun/2017 18:22:17] "OPTIONS /auth/oauth2/token HTTP/1.1" 200 -
2017-06-19 18:22:17,952 [DEBUG] [flask_oauthlib] Fetched extra credentials, {}.
2017-06-19 18:22:17,953 [DEBUG] [oauthlib.oauth2.rfc6749.endpoints.token] Dispatching grant_type password request to <oauthlib.oauth2.rfc6749.grant_types.resource_owner_password_credentials.ResourceOwnerPasswordCredentialsGrant object at 0x105809358>.
2017-06-19 18:22:17,974 [DEBUG] [flask_oauthlib] Authenticate client 'documentation'.
2017-06-19 18:22:17,978 [DEBUG] [oauthlib.oauth2.rfc6749.grant_types.resource_owner_password_credentials] Validating access token request, <oauthlib.Request url="http://localhost:5000/auth/oauth2/token", http_method="POST", headers="{'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 'Content-Length': '172', 'Accept-Language': 'en-US,en;q=0.8,af;q=0.6', 'Accept': 'application/json, application/x-www-form-urlencoded', 'Referer': 'http://localhost:3000/', 'Connection': 'keep-alive', 'Origin': 'http://localhost:3000', 'Host': 'localhost:5000', 'Authorization': '<SANITIZED>', 'Accept-Encoding': 'gzip, deflate, br'}", body="{'password': 'q', 'scope': 'videos:read auth:read users:write auth:write users:read teams:read teams:write videos:write', 'username': 'root', 'grant_type': 'password'}">.
2017-06-19 18:22:17,979 [DEBUG] [oauthlib.oauth2.rfc6749.grant_types.resource_owner_password_credentials] Validating username root.
2017-06-19 18:22:17,979 [DEBUG] [flask_oauthlib] Validating username 'root' and its password
2017-06-19 18:22:18,307 [DEBUG] [oauthlib.oauth2.rfc6749.grant_types.resource_owner_password_credentials] Authorizing access to user <User(id=1, username="root", email="root@localhost", is_internal=False, is_admin=True, is_regular_user=True, is_active=True, is_enterprise=False, is_staff=False, is_employer=False, is_applicant=False, )>.
2017-06-19 18:22:18,307 [DEBUG] [oauthlib.oauth2.rfc6749.grant_types.base] Validating access to scopes ['videos:read', 'auth:read', 'users:write', 'auth:write', 'users:read', 'teams:read', 'teams:write', 'videos:write'] for client 'documentation' (<app.modules.auth.models.OAuth2Client object at 0x105889da0>).
2017-06-19 18:22:18,309 [DEBUG] [flask_oauthlib] Save bearer token {'refresh_token': 'PkAau0F3OubMMNxRtHtADPDcBSibHX', 'token_type': 'Bearer', 'expires_in': 3600, 'scope': 'videos:read auth:read users:write auth:write users:read teams:read teams:write videos:write', 'access_token': 'tYMLyLO6RE87H2w99HJaRs1qNBVY0x'}
2017-06-19 18:22:18,323 [DEBUG] [oauthlib.oauth2.rfc6749.grant_types.resource_owner_password_credentials] Issuing token {'refresh_token': 'PkAau0F3OubMMNxRtHtADPDcBSibHX', 'token_type': 'Bearer', 'scope': 'videos:read auth:read users:write auth:write users:read teams:read teams:write videos:write', 'access_token': 'tYMLyLO6RE87H2w99HJaRs1qNBVY0x'} to client id 'documentation' (<app.modules.auth.models.OAuth2Client object at 0x105889da0>) and username root.
2017-06-19 18:22:18,324 [INFO] [werkzeug] 127.0.0.1 - - [19/Jun/2017 18:22:18] "POST /auth/oauth2/token HTTP/1.1" 200 -

Here is the raw JSON returned:

{"refresh_token": "PkAau0F3OubMMNxRtHtADPDcBSibHX", "token_type": "Bearer", "scope": "videos:read auth:read users:write auth:write users:read teams:read teams:write videos:write", "access_token": "tYMLyLO6RE87H2w99HJaRs1qNBVY0x"}

It seems that it gets left out somewhere between flask-oauthlib and oauthlib logging output.

Is this expected behavior?

Add X-Total-Count header to the listing resources to enable pagination

I would expect to return a lazy queryset from the resource method handler, and some smart (explicit) magic will take care of OpenAPI/Swagger documentation, offsetting and limiting the data, and setting X-Total-Count header to the response.

There are a few solutions I have in mind, but none of them is perfect to me:

  • Wrap the @api.response() decorator, so we have the input parameters available, the original response, and the control over serialization:

    @api.route('/my_resources/')
    class MyResources(Resource):
        @parameters.MyParametersInheritedFromPaginationParameters().paginate(
            api.response(schemas.MySchema)
        )
        def get(self, args):
             return models.MyResource.query.all()

    or

    @api.route('/my_resources/')
    class MyResources(Resource):
        @api.parameters(parameters.MyParametersInheritedFromPaginationParameters())
        @api.paginate(
            api.response(schemas.MySchema)
        )
        def get(self, args):
             return models.MyResource.query.all()
  • Extend the @api.response decorator to have hooks before the object gets dumped with a schema (so we can limit and offset the queryset), and after the Response is generated (so we can inject the header)

References:

P.S. To document the headers in Swagger.json, we need the latest master of flask-restplus (the latest 0.10.1 release doesn't include the fix for noirbizarre/flask-restplus#119).

Implements tests for tasks

Hi,

This is obviously related to #70;

I have no idea how to implement these tests (and haven't searched yet) but would really want to help!

problem install with PostgreSQL database

Hello

I'm trying using PostgreSQL database to make your example work. I have problem with logs error like this

Traceback (most recent call last):
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
context)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
cursor.execute(statement, parameters)
psycopg2.ProgrammingError: column "password" cannot be cast automatically to type bytea
HINT: You might need to specify "USING password::bytea".

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/home/me/.virtualenvs/flask-noota-api/bin/invoke", line 11, in
sys.exit(program.run())
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/invoke/program.py", line 293, in run
self.execute()
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/invoke/program.py", line 408, in execute
executor.execute(*self.tasks)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/invoke/executor.py", line 114, in execute
result = call.task(*args, **call.kwargs)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/invoke/tasks.py", line 114, in call
result = self.body(*args, **kwargs)
File "/home/me/CODER/python/flask-noota-api/tasks/app/_utils.py", line 61, in wrapper
return func(*args, **kwargs)
File "/home/me/CODER/python/flask-noota-api/tasks/app/db.py", line 277, in init_development_data
context.invoke_execute(context, 'app.db.upgrade')
File "/home/me/CODER/python/flask-noota-api/tasks/init.py", line 73, in invoke_execute
results = Executor(namespace, config=context.config).execute((command_name, kwargs))
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/invoke/executor.py", line 114, in execute
result = call.task(*args, **call.kwargs)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/invoke/tasks.py", line 114, in call
result = self.body(*args, **kwargs)
File "/home/me/CODER/python/flask-noota-api/tasks/app/_utils.py", line 61, in wrapper
return func(*args, **kwargs)
File "/home/me/CODER/python/flask-noota-api/tasks/app/db.py", line 163, in upgrade
command.upgrade(config, revision, sql=sql, tag=tag)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/command.py", line 174, in upgrade
script.run_env()
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/script/base.py", line 416, in run_env
util.load_python_file(self.dir, 'env.py')
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/util/pyfiles.py", line 93, in load_python_file
module = load_module_py(module_id, path)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/util/compat.py", line 68, in load_module_py
module_id, path).load_module(module_id)
File "", line 388, in _check_name_wrapper
File "", line 809, in load_module
File "", line 668, in load_module
File "", line 268, in _load_module_shim
File "", line 693, in _load
File "", line 673, in _load_unlocked
File "", line 665, in exec_module
File "", line 222, in call_with_frames_removed
File "migrations/env.py", line 93, in
run_migrations_online()
File "migrations/env.py", line 86, in run_migrations_online
context.run_migrations()
File "", line 8, in run_migrations
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/runtime/environment.py", line 807, in run_migrations
self.get_context().run_migrations(**kw)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/runtime/migration.py", line 321, in run_migrations
step.migration_fn(**kw)
File "/home/me/CODER/python/flask-noota-api/migrations/versions/36954739c63
.py", line 28, in upgrade
existing_nullable=False)
File "/usr/lib/python3.5/contextlib.py", line 66, in exit
next(self.gen)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/operations/base.py", line 299, in batch_alter_table
impl.flush()
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/operations/batch.py", line 57, in flush
fn(*arg, **kw)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/ddl/postgresql.py", line 91, in alter_column
existing_nullable=existing_nullable,
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/alembic/ddl/impl.py", line 118, in _exec
return conn.execute(construct, *multiparams, **params)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 945, in execute
return meth(self, multiparams, params)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/sql/ddl.py", line 68, in _execute_on_connection
return connection._execute_ddl(self, multiparams, params)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1002, in _execute_ddl
compiled
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1189, in _execute_context
context)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1393, in _handle_dbapi_exception
exc_info
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 203, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb, cause=cause)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/util/compat.py", line 186, in reraise
raise value.with_traceback(tb)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
context)
File "/home/me/.virtualenvs/flask-noota-api/lib/python3.5/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) column "password" cannot be cast automatically to type bytea
HINT: You might need to specify "USING password::bytea".
[SQL: 'ALTER TABLE "user" ALTER COLUMN password TYPE BYTEA ']


tested with

  • Python 3.5.2
  • (PostgreSQL) 9.4.10

installed dependencies

alembic==0.8.10
aniso8601==1.2.0
apispec==0.19.0
appdirs==1.4.2
arrow==0.8.0
bcrypt==3.1.3
cffi==1.9.1
click==6.7
colorlog==2.10.0
Flask==0.12
Flask-Cors==3.0.2
Flask-Login==0.4.0
flask-marshmallow==0.7.0
Flask-OAuthlib==0.9.3
flask-restplus==0.10.1
Flask-SQLAlchemy==2.2
invoke==0.15.0
itsdangerous==0.24
Jinja2==2.9.5
jsonschema==2.6.0
lockfile==0.12.2
Mako==1.0.6
MarkupSafe==0.23
marshmallow==2.13.1
marshmallow-sqlalchemy==0.12.0
oauthlib==2.0.1
packaging==16.8
passlib==1.7.1
permission==0.4.1
psycopg2==2.7
pycparser==2.17
pyparsing==2.1.10
python-dateutil==2.6.0
python-editor==1.0.3
pytz==2016.10
PyYAML==3.12
requests==2.13.0
requests-oauthlib==0.8.0
six==1.10.0
SQLAlchemy==1.1.5
SQLAlchemy-Utils==0.32.12
webargs==1.5.3
Werkzeug==0.11.15

any idea ?

Decorate with multiple/chained parameters

I would like to add multiple parameters to an endpoint, essentially chaining them as so:

 @api.parameters(SomeParameters())
 @api.parameters(MoreParameters())
 @api.parameters(EvenMoreParameters())
 def get(self, args):
    ...

However, this results in a separate dict for each set of parameters and does not concatenate the arguments as I had expected. Also, Swagger does not seem to parse all of the argument options as only the topmost decorator appears in the docs.

image

I could simply create another Parameters class that combines all of the fields from the others, but I'd like to avoid having to create one for each desired combination of parameters.

Another idea I had was to create a method that would accept any number of Parameter objects and return a new dynamic Schema object with all of the combined fields. Something like:

 @api.parameters(many_params(SomeParameters(), MoreParameters(), EvenMoreParameters()))
 def get(self, args):
    ...

What is the recommended approach to accomplish this? I feel like I must be missing an easier way...

locations='json' breaks validation

I've looked over the other two json related issues and didn't see anything relating to my problem.
I'm setting my parameter to json:

@api.parameters(parameters.AddAnnouncementParameters(), locations=('json',))

configuring my parameters:

     title = base_fields.String(required=True, location='json')
    content = base_fields.String(required=True, location='json')

Then validation stops working, I'm allowed to submit blank strings for these fields.

`invoke app.dependencies.install` produces the error on Python 2.7

invoke app.run --no-install-dependencies or invoke app.dependencies.install_swagger_ui works fine.

invoke app.dependencies.install fails with error.

2017-03-07 17:21:01,359 [INFO] [tasks.app.dependencies] Installing project dependencies...
Traceback (most recent call last):
  File "/Users/dog/.virtualenvs/flask_restplus_env/bin/invoke", line 11, in <module>
    sys.exit(program.run())
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/program.py", line 270, in run
    self.execute()
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/program.py", line 381, in execute
    executor.execute(*self.tasks)
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/executor.py", line 113, in execute
    result = call.task(*args, **call.kwargs)
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/tasks.py", line 111, in __call__
    result = self.body(*args, **kwargs)
  File "/Users/dog/projects/forks/flask-restplus-server-example/tasks/app/run.py", line 37, in run
    context.invoke_execute(context, 'app.dependencies.install')
  File "/Users/dog/projects/forks/flask-restplus-server-example/tasks/__init__.py", line 73, in invoke_execute
    results = Executor(namespace, config=context.config).execute((command_name, kwargs))
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/executor.py", line 113, in execute
    result = call.task(*args, **call.kwargs)
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/tasks.py", line 111, in __call__
    result = self.body(*args, **kwargs)
  File "/Users/dog/projects/forks/flask-restplus-server-example/tasks/app/dependencies.py", line 102, in install
    install_python_dependencies(context)
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/tasks.py", line 111, in __call__
    result = self.body(*args, **kwargs)
  File "/Users/dog/projects/forks/flask-restplus-server-example/tasks/app/dependencies.py", line 27, in install_python_dependencies
    context.run("pip install -r requirements.txt %s" % ('--upgrade' if force else ''))
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/context.py", line 53, in run
    return runner_class(context=self).run(command, **kwargs)
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/runners.py", line 261, in run
    self.encoding = opts['encoding'] or self.default_encoding()
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/site-packages/invoke/runners.py", line 842, in default_encoding
    default = locale.getdefaultlocale()[1]
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/locale.py", line 543, in getdefaultlocale
    return _parse_localename(localename)
  File "/Users/dog/.virtualenvs/flask_restplus_env/lib/python2.7/locale.py", line 475, in _parse_localename
    raise ValueError, 'unknown locale: %s' % localename
ValueError: unknown locale: UTF-8

@frol Can you propose the place where to start finding the reason? I was looking on the line 27 but didn't see anything to produce the error.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.