Coder Social home page Coder Social logo

flask-resty's People

Contributors

aaronbuxbaum avatar c0state avatar cancan101 avatar d-wysocki avatar dependabot[bot] avatar emmilco avatar gnottobfly avatar hugovk avatar itajaja avatar jquense avatar manon-pilaud avatar peterschutt avatar r1b avatar renovate[bot] avatar sloria avatar stanley98yu avatar taion 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

Watchers

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

flask-resty's Issues

Allow registration of resources before app is available

Hi all,

I believe #280 is related to this.

Thanks for the library, I've been using for a little while now and as I was already using Flask/SQLAlchemy/Marshmallow, I've found using flask-resty to be a nice little productivity boost.

I was hoping to get some feedback on an implementation for deferring resource registration until an App is available.

Take the below project structure:

.
└── app
    ├── __init__.py
    ├── ext
    │   ├── api.py
    │   └── db.py
    ├── models
    │   ├── __init__.py
    │   └── country.py
    └── resources
        └── country
            ├── __init__.py
            └── country.py

With the current implementation of Api I do this:

# app/__init__.py

from flask import flask

from .ext import api, db
from .resources.country import CountryListView, CountryView

def create_app(config=None):
    app = app(__name__)
    ...
    api.init_app(app)
    api.add_resource(
        "/country",
        country.country_api.CountryListView,
        country.country_api.CountryView,
        app=app,
        id_rule="<int:id>",
    )
    ...

This is no problem, but I find that as the number of resources grows, my create_app() function is getting quite long. So I made this wrapper around Api:

# app/ext/api.py
from flask_resty.api import Api as Api_


class Api(Api_):
    def __init__(self, app=None, prefix=""):
        super().__init__(app, prefix)
        # add a container for holding deferred registrations
        self._deferred_registrations = []

    def _get_app(self, app):
        # this method no longer raises AssertionError
        app = app or self._app
        return app

    def init_app(self, app):
        super().init_app(app)

        # if there are deferred registrations, add them now that the app is available.
        for deferred in self._deferred_registrations:
            self.add_resource(*deferred[0], app=app, **deferred[1])

    def add_resource(self, *args, app=None, **kwargs):
        # first check if we have the app, and if not, defer the registration
        app = self._get_app(app)
        if app is None:
            self._deferred_registrations.append((args, kwargs))
        else:
            super().add_resource(*args, app=app, **kwargs)


api = Api(prefix="/api")

Which then allows me to register the resources closer to their implementation, e.g.:

# app/resources/country/__init__.py
from ...ext import api
from . import country 

api.add_resource(
    "/country",
    country.CountryListView,
    country.CountryView,
    id_rule="<int:id>",
)

And create_app() stays nice and simple:

# app/__init__.py
from flask import flask

from .ext import api, db

# import resources before calling `api.init_app()`
from .resources import country  # noqa: F401

def create_app(config=None):
    app = app(__name__)
    ...
    api.init_app(app)
    ...

This implementation of App should be backward compatible as the only change in existing behavior is that it no longer will raise AssertionError if the app is not available when the resource is added.

I was wondering if there are any pitfalls with this approach that I should consider, and if this or something related might be considered for merging upstream?

unable to import JwtAuthentication

Hi!

I'm unable to import the JwtAuthentication class.
(ImportError: cannot import name 'JwtAuthentication' from 'flask_resty')

maybe the import is failing here?

Clarify "SQLAlchemy model"

The README states "SQLAlchemy model", however in many cases the model used will be a subclass of db.Model where db = flask_sqlalchemy.SQLAlchemy(app).

init_app method did not save app reference

Hi,

I've been playing this library since yesterday, and using it with factory create_app mode.

def register_extensions(app):
    from .extensions import login_manager, migrate, cache, ma, api
    from .database import db

    db.init_app(app)
    ma.init_app(app)
    migrate.init_app(app, db)
    cache.init_app(app)
    api.init_app(app)

however pytest throws an error as below, from latest version (v0.21.1)

――――――――――――――――――――――――――――――――――――――――――― ERROR at setup of test_api_asset_get_bad_url_format ――――――――――――――――――――――――――――――――――――――――――――

request = <SubRequest 'app' for <Function test_api_asset_get_bad_url_format>>

>   ???

C:\dev\xx-data_2\tests\conftest.py:16:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
app\app.py:21: in create_app
    register_blueprints(app)
app\app.py:60: in register_blueprints
    from app.api import api_bp
app\api\__init__.py:4: in <module>
    from . import asset
app\api\asset.py:35: in <module>
    api.add_resource("/widgets", AssetListView, AssetView)
..\xx-data\venv\lib\site-packages\flask_resty\api.py:127: in add_resource
    app = self._get_app(app)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <flask_resty.api.Api object at 0x000001FFE7AAE5F8>, app = None

    def _get_app(self, app):
        app = app or self._app
>       assert app, "no application specified"
E       AssertionError: no application specified
E       assert None

..\xx-data\venv\lib\site-packages\flask_resty\api.py:79: AssertionError
----------------------------------------------------------- Captured stdout setup -----------------------------------------------------------
ktmw32
                                                                                                                                17% █▋

Apparently the init_app function forgot to save the app reference after digging into the source code.

    def init_app(self, app):
        """Initialize an application for use with Flask-RESTy.
        :param app: The Flask application object.
        :type app: :py:class:`flask.Flask`
        """
        app.extensions["resty"] = FlaskRestyState(self)

        app.register_error_handler(ApiError, handle_api_error)
        app.register_error_handler(HTTPException, handle_http_exception)

So I've been wondering... you guys don't use factory mode to create app? :)

Jeffery

Access query parameters in GET

Hello I am trying to access query parameter in a GET to perform the logic of my function. I am using request_args method but this fails. I defined an args schema as well. But still fails with TypeError: 'dict' object is not callable. If access as flask.request.args it works but it is failing when access with flask resty framework. Is there different approach where I can access query parameters ?

Method call to access query params:
self.request_args()

error: TypeError: 'dict' object is not callable

Add dev extras that includes tox

CONTRIBUTING.md uses pip install -e .[tests,docs] for installing dependencies, and then specifies tox ... commands while tox isn't a dependency of either of the tests or docs extras.

Similar to marshmallow, we could add a dev extras, for example:

EXTRAS_REQUIRE = {
    "docs": ("sphinx", "pallets-sphinx-themes"),
    "jwt": ("PyJWT>=1.4.0", "cryptography>=2.0.0"),
    "tests": ("coverage", "psycopg2-binary", "pytest"),
}
EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["docs"] + EXTRAS_REQUIRE["tests"] + ("tox",)

...

setup(
    ...,
    extras_require=EXTRAS_REQUIRE,
    ...,
)

Then change CONTRIBUTING.md to pip install -e .["dev"].

Enable better composition model

For larger APIs, the composition model in Flask-RESTy gets a bit messy. We often end up with multiple parallel complex inheritance trees in separate auth, schemas, and views modules, and a single view may compose in a half-dozen or more base classes and mixins. This is not ideal for understandability; the MRO gets complex enough that it's often not easy to figure out which method overrides which, or what super calls do.

For auth, many of these problems will be solved by #236. Outside of auth, though, we need to brainstorm further on how to make these work. One option is to move toward embedding class definitions for functionality. This would then allow patterns like:

class ChildView(ParentView):
    class Authorization(ParentView.Authorization):
        def authorize_delete_item(item):
            return False

    class Schema(ParentView.Schema):
        extra_field = fields.String()

Also, to limit the number of base classes in views, we could factor out classes to handle specific actions. For example, instead of something like:

class WidgetView(VersionedModelMixin, SoftDeleteMixin, GenericModelView):
    version_ignored_columns = ('version_id', 'version_committed_by')

    pass
class WidgetView(GenericModelView):
    class Updater(VersionedUpdater):
        ignored_columns = ('version_id', 'version_committed_by')

    class Deleter(SoftDeleteDeleter):
        pass

This could also enable patterns like:

class WidgetView(GenericModelView):
    class Model(SoftDeleteMixin.Model, db.Model):
        name = sa.Column(sa.String)

    Deleter = SoftDeleteMixin.Deleter

i.e. allow better co-location of mixin-type behavior across models and views.

This likely will require some version of #237.

Docs Question

Hey, i have been reading over the source trying to see if this lib handles creating/updating relationships of the main model being created/updated.

I am looking for something like that. Does this lib do anything if the incoming json object to a POST/PUT method has relationships also in json?

Trouble understanding how to filter

I'm trying to achieve something similar to

# Flask-SQLAlchemy
User.query.filter(User.is_active)

but using flask-resty filtering capability. How do I go about doing this?

api actions?

Hi,

flask-resty has done a good job with CRUD apis like

/users/
/users/<id>/

what about the action apis similar to following?

/users/<id>/follow
/users/<id>/posts

What's the best way to implement these apis along with flask-resty? putting blueprints and class view into same file isn't a good idea

BuildError

If try to create a view with only the post method then will be BuildError.

..\resources\profiles.py:40: in post
    resp = self.create()
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask_resty\view.py:1070: in create
    return self.make_created_response(item)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask_resty\view.py:188: in make_created_response
    location = self.get_location(item)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask_resty\view.py:234: in get_location
    return flask.url_for(flask.request.endpoint, _method="GET", **id_dict)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask\helpers.py:370: in url_for
    return appctx.app.handle_url_build_error(error, endpoint, values)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask\app.py:2215: in handle_url_build_error
    reraise(exc_type, exc_value, tb)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask\_compat.py:39: in reraise
    raise value
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask\helpers.py:358: in url_for
    endpoint, values, method=method, force_external=external
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <werkzeug.routing.MapAdapter object at 0x000002F1D11C8E88>
endpoint = 'ProfileCreateView', values = {'id': 1}, method = 'GET'
force_external = False, append_unknown = True

    def build(
        self,
        endpoint,
        values=None,
        method=None,
        force_external=False,
        append_unknown=True,
    ):
        """Building URLs works pretty much the other way round.  Instead of
        `match` you call `build` and pass it the endpoint and a dict of
        arguments for the placeholders.
    
        The `build` function also accepts an argument called `force_external`
        which, if you set it to `True` will force external URLs. Per default
        external URLs (include the server name) will only be used if the
        target URL is on a different subdomain.
    
        >>> m = Map([
        ...     Rule('/', endpoint='index'),
        ...     Rule('/downloads/', endpoint='downloads/index'),
        ...     Rule('/downloads/<int:id>', endpoint='downloads/show')
        ... ])
        >>> urls = m.bind("example.com", "/")
        >>> urls.build("index", {})
        '/'
        >>> urls.build("downloads/show", {'id': 42})
        '/downloads/42'
        >>> urls.build("downloads/show", {'id': 42}, force_external=True)
        'http://example.com/downloads/42'
    
        Because URLs cannot contain non ASCII data you will always get
        bytestrings back.  Non ASCII characters are urlencoded with the
        charset defined on the map instance.
    
        Additional values are converted to unicode and appended to the URL as
        URL querystring parameters:
    
        >>> urls.build("index", {'q': 'My Searchstring'})
        '/?q=My+Searchstring'
    
        When processing those additional values, lists are furthermore
        interpreted as multiple values (as per
        :py:class:`werkzeug.datastructures.MultiDict`):
    
        >>> urls.build("index", {'q': ['a', 'b', 'c']})
        '/?q=a&q=b&q=c'
    
        Passing a ``MultiDict`` will also add multiple values:
    
        >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b'))))
        '/?p=z&q=a&q=b'
    
        If a rule does not exist when building a `BuildError` exception is
        raised.
    
        The build method accepts an argument called `method` which allows you
        to specify the method you want to have an URL built for if you have
        different methods for the same endpoint specified.
    
        .. versionadded:: 0.6
           the `append_unknown` parameter was added.
    
        :param endpoint: the endpoint of the URL to build.
        :param values: the values for the URL to build.  Unhandled values are
                       appended to the URL as query parameters.
        :param method: the HTTP method for the rule if there are different
                       URLs for different methods on the same endpoint.
        :param force_external: enforce full canonical external URLs. If the URL
                               scheme is not provided, this will generate
                               a protocol-relative URL.
        :param append_unknown: unknown parameters are appended to the generated
                               URL as query string argument.  Disable this
                               if you want the builder to ignore those.
        """
        self.map.update()
    
        if values:
            if isinstance(values, MultiDict):
                temp_values = {}
                # iteritems(dict, values) is like `values.lists()`
                # without the call or `list()` coercion overhead.
                for key, value in iteritems(dict, values):
                    if not value:
                        continue
                    if len(value) == 1:  # flatten single item lists
                        value = value[0]
                        if value is None:  # drop None
                            continue
                    temp_values[key] = value
                values = temp_values
            else:
                # drop None
                values = dict(i for i in iteritems(values) if i[1] is not None)
        else:
            values = {}
    
        rv = self._partial_build(endpoint, values, method, append_unknown)
        if rv is None:
>           raise BuildError(endpoint, values, method, self)
E           werkzeug.routing.BuildError: Could not build url for endpoint 'ProfileCreateView' ('GET') with values ['id']. Did you mean to use methods ['OPTIONS', 'POST']?

C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\werkzeug\routing.py:2020: BuildError

Authorization before data validation

How to make that authorization was executed before the validation of data. This is necessary so that an unauthorized user does not recognize the data scheme since now the response has 422 code with a description of what is not correct in the request instead of 401.

Play nice with Flask factory pattern

Flask extensions can be initialized in two ways: having the app passed to them on creation, or by calling an init_app() method with the app later. This second way is useful when using the create_app factory pattern.

flask-resty throws an AttributeError with the second method:

  File "/lib/python3.6/site-packages/flask_resty/api.py", line 49, in add_resource
    app = self._get_app(app)
  File "/lib/python3.6/site-packages/flask_resty/api.py", line 31, in _get_app
    app = app or self._app
AttributeError: 'Api' object has no attribute '_app'

301 Redirect Broken

repost from: #213 (comment)

301 redirects (eg from extra /) are broken.

301 code sent however json content is attached (both in debug and not in debug).

This leads to crhome Resource interpreted as Document but transferred with MIME type application/json: "http://localhost:5000/admin". and then ignoring the redirect.

Content in debug.

Traceback (most recent call last):
  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/werkzeug/routing.py", line 1538, in match
    rv = rule.match(path, method)
  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/werkzeug/routing.py", line 776, in match
    raise RequestSlash()
werkzeug.routing.RequestSlash

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/flask/app.py", line 1791, in dispatch_request
    self.raise_routing_exception(req)
  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/flask/app.py", line 1774, in raise_routing_exception
    raise request.routing_exception
  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/flask/ctx.py", line 336, in match_request
    self.url_adapter.match(return_rule=True)
  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/werkzeug/routing.py", line 1542, in match
    safe='/:|+') + '/', query_args))
werkzeug.routing.RequestRedirect: 301 Moved Permanently: None

apispec required but not installed

cross post: #197 (comment)

flsk-resty is broken without apispec being installed (which is not listed in setup.py).

  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/flask_resty/spec/__init__.py", line 9, in <module>
    from .plugin import FlaskRestyPlugin
  File "/Users/alex/.pyenv/versions/app/lib/python3.7/site-packages/flask_resty/spec/plugin.py", line 3, in <module>
    from apispec.ext.flask import FlaskPlugin
ModuleNotFoundError: No module named 'apispec'  

/CC @taion

Prevent Registering Multiple Api to the Same App

The following should probably not be allowed:

api_v1 = Api(app, '/api/v1')
api_v2 = Api(app, '/api/v2')

even though it can be done now. The issue is that each Api registers itself with the app and error handlers.

Update POST request data

I would like to update the POST data, part of the data should be updated by the server. I see we do that for soft delete where we can override delete_item_raw and delete_item and it works. Similarly I want to update the POST request data before saving to the database. I am overriding the "create_item_raw" method but getting an error. How can I update post and not run in to the builtins.dict error.

venv/lib/python3.9/site-packages/sqlalchemy/util/compat.py", line 207, in raise_
raise exception
sqlalchemy.orm.exc.UnmappedInstanceError: Class 'builtins.dict' is not mapped

Ping should return valid JSON for 200 status code

Given we return 200 status code rather than 204 (no content), we should return valid json. eg. {} or {'status': 'ok'} Maybe take the return values as kwarg and default to empty dict?

def add_ping(self, rule, status_code=200, app=None):
"""Add a ping route.
:param str rule: The URL rule. This will not use the API prefix, as the
ping endpoint is not really part of the API.
:param int status_code: The ping response status code. The default is
200 rather than the more correct 204 because many health checks
look for 200s.
:param app: If specified, the application to which to add the route.
Otherwise, this will be the bound application, if present.
"""
app = self._get_app(app)
@app.route(rule)
def ping():
return '', status_code

TypeError: 'MyDataModel' object is not subscriptable

..\resources\profiles.py:44: in put
    return self.update(id, partial=True)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask_resty\view.py:1104: in update
    data_in = self.get_request_data(expected_id=id, partial=partial)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask_resty\view.py:246: in get_request_data
    return self.deserialize(data_raw, **kwargs)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask_resty\view.py:713: in deserialize
    data = super().deserialize(data_raw, **kwargs)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask_resty\view.py:292: in deserialize
    self.validate_request_id(data, expected_id)
C:\Users\Asus\.virtualenvs\corpteam-site-NCnneqC8\lib\site-packages\flask_resty\view.py:360: in validate_request_id
    id = self.get_data_id(data)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <resources.profiles.ProfileView object at 0x00000151FF735D88>
data = <ProfileModel (transient 1451689744328)>

    def get_data_id(self, data):
        if len(self.id_fields) == 1:
            try:
>               return data[self.id_fields[0]]
E               TypeError: 'ProfileModel' object is not subscriptable

It seems like a violation in logic. After data = schema_load(self.deserializer, data_raw, **kwargs) in ApiView.deserialize data is a ProfileModel istead of dict in marshmellow 3.

TypeError: argument of type 'Asset' is not iterable

Throws error when using POST method with code from example, with version 0.21.1, and marshmallow==3.0.0rc8

class AssetListView(AssetViewBase):
    def get(self):
        return self.list()

    def post(self):
        return self.create()
app\api\asset.py:21: in post
    return self.create()
..\xx-data\venv\lib\site-packages\flask_resty\view.py:1061: in create
    data_in = self.get_request_data(expected_id=expected_id)
..\xx-data\venv\lib\site-packages\flask_resty\view.py:245: in get_request_data
    return self.deserialize(data_raw, **kwargs)
..\xx-data\venv\lib\site-packages\flask_resty\view.py:709: in deserialize
    data = super().deserialize(data_raw, **kwargs)
..\xx-data\venv\lib\site-packages\flask_resty\view.py:291: in deserialize
    self.validate_request_id(data, expected_id)
self = <app.api.asset.AssetListView object at 0x0000020BFEAB3D68>, data = <Assert "hello" >, expected_id = False

    def validate_request_id(self, data, expected_id):
        """Check that the request data has the expected ID.
    
        This is generally used to assert that update operations include the
        correct item ID and that create operations do not include an ID.
    
        This works in one of three modes::
        - If `expected_id` is ``None``, do no checking
        - If `expected_id` is ``False``, check that no ID is provided
        - Otherwise, check that `data` has the expected ID
    
        :param data: The request data.
        :param expected_id: The ID or ID tuple, or False, or None.
        :raises ApiError: If the necessary IDs are not present and correct
        """
        if expected_id is None:
            return
    
        if expected_id is False:
            for id_field in self.id_fields:
>               if id_field in data:
E               TypeError: argument of type 'Asset' is not iterable

the data is an model object that loaded from super().deserialize method. So the id_field should be checked via hasattr.

Model filters and joins

I find that I often want to provide an arg filter to an endpoint that requires a join to another model, e.g. (somewhat contrived):

class Match:
    sport_id = ...

class Sport:
    id = ...
    name = ...

client.get("/match?sport=Football")

One way that I've been supplying the join is to override query_raw and have a look in the request args to see if the arg has been supplied, and then attach the join..

@model_filter(ma.fields.String())
def sport_name_filter(_, value):
    return Sport.name == value

class MatchView:

    filtering = Filtering(sport=sport_name_filter)

    @property
    def query_raw(self):
        qry = super().query_raw
        if "sport" in flask.request.args:
            qry = qry.join(Sport)
        return qry

However, I don't like having separate logic of whether to apply the join and the filter when I never want one without the other. So I've created a JoinedModelFilter, which is just a wrapper around ModelFilter that allows the join logic to be associated with the Filter and conditionally applied along with the filter depending on whether a value is present for the arg. In addition to the marshmallow field, the type requires a callback that applies the join to the query.

Here is my POC:

class JoinedModelFilter(flask_resty.filtering.ModelFilter):
    def __init__(self, join_cb, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.join_cb = join_cb

    def apply_join(self, query):
        return self.join_cb(query)

    def filter_query(self, query, view, arg_value):
        filter = self.get_filter(view, arg_value)
        if filter is None:
            return query

        query = self.apply_join(query)
        return query.filter(filter)

# decorator
def joined_model_filter(join_cb, field, **kwargs):
    def wrapper(func):
        filter_field = JoinedModelFilter(join_cb, field, func, **kwargs)
        functools.update_wrapper(filter_field, func)
        return filter_field

    return wrapper

Example use:

@joined_model_filter(lambda qry: qry.join(Sport, Match.sport_id == Sport.id), ma.fields.String())
def sport_name_filter(_, value):
    return Sport.name == value

I was curious whether you had a different recipe for this type of thing, and if not whether there might be any drawbacks that I haven't thought of, and finally, whether you'd consider to bring this in upstream.

Thanks!

Show more information on assert_response when status code fails

Right now assert_response first checks the status_code:

assert response.status_code == expected_status_code

however this doesn't print any information as to what the error was other than the status code. In many cases, seeing the contents of errors is helpful for diagnosing the problem. This is especially true when a 200 is expected but some other error happens yielding a 4XX error.

I suggest something like:

def assert_response(response, expected_status_code, expected_data=UNDEFINED):
    if 200 <= response.status_code < 300:
        response_data = get_data(response)
    else:
        response_data = get_errors(response)

    assert response.status_code == expected_status_code, response_data

    if expected_data is UNDEFINED:
        return

...

Tutorial example doesn't work

The example from https://flask-resty.readthedocs.io/en/latest/guide.html fails when running "flask run" with the following error:

  • Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.
    Use a production WSGI server instead.
  • Debug mode: off
    Usage: flask run [OPTIONS]
    Try 'flask run --help' for help.

Error: Could not locate a Flask application. You did not provide the "FLASK_APP" environment variable, and a "wsgi.py" or "app.py" module was not found in the current directory.

Werkzeug 1.0.0 breaks routing

New version of werkzeug causes import error in routing.py

  File "test.py", line 1, in <module>
    from flask_resty import Api
  File "/tmp/tmp.3SXcUAZNYT/_venv/lib/python3.7/site-packages/flask_resty/__init__.py", line 37, in <module>
    from .routing import StrictRule
  File "/tmp/tmp.3SXcUAZNYT/_venv/lib/python3.7/site-packages/flask_resty/routing.py", line 1, in <module>
    from werkzeug.routing import RequestSlash, Rule
ImportError: cannot import name 'RequestSlash' from 'werkzeug.routing' (/tmp/tmp.3SXcUAZNYT/_venv/lib/python3.7/site-packages/werkzeug/routing.py)

Environment:

Click==7.0
Flask==1.1.1
Flask-RESTy==0.22.1
Flask-SQLAlchemy==2.4.1
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1
marshmallow==3.4.0
pkg-resources==0.0.0
SQLAlchemy==1.3.13
Werkzeug==1.0.0

Allow boolean operations between authorization items

For combining authorization, in many cases it would be simpler to do something like:

class WidgetView(GenericModelView):
    authorization = WidgetAuthorization() | AdminAuthorization()

Instead of having to define a new authorization class that combines the two in a less declarative manner.

Inspired by the permission_classes functionality in DRF.

Additive Filtering

I have a base view that defines quite a few filters that is shared across a few different views. On one of the sub-class views I want to add another ColumnFilter, e.g.:

class BaseView(GenericModelView):
    ...
    filtering = Filtering(...)

class SubclassView(BaseView):
    ...
    filtering = ...  # I want to add a filter to the BaseView filtering without it affecting the other sub-classes.

I've done this:

from flask_resty.filtering import Filtering as Filtering_

class Filtering(Filtering_):
    def __add__(self, other):
        if not isinstance(other, Filtering):
            return NotImplemented
        new = Filtering()
        new._arg_filters = dict(**self._arg_filters)
        new._arg_filters.update(other._arg_filters)
        return new

So on the sub-class I then do:

class SubclassView(BaseView):
    ...
    filtering = BaseView.filtering + Filtering(another=ColumnFilter(...))

Is there a supported way to do this type of thing?

Failing tests

On my box, some tests are failing, when launched either manually (pytest) or via tox.

flask-resty ❯ pytest --tb line
==================================================================== test session starts ====================================================================
platform darwin -- Python 3.7.3, pytest-4.4.0, py-1.8.0, pluggy-0.9.0
rootdir: /Users/fermigier/git/flask/flask-resty
plugins: Flask-RESTy-0.20.10
collected 216 items

tests/test_args.py ..........                                                                                                                         [  4%]
tests/test_auth.py ...FF..F.........                                                                                                                  [ 12%]
tests/test_basic.py ...FF                                                                                                                             [ 14%]
tests/test_composite_id.py ...FF                                                                                                                      [ 17%]
tests/test_context.py ..                                                                                                                              [ 18%]
tests/test_decorators.py ....                                                                                                                         [ 19%]
tests/test_fields.py ........                                                                                                                         [ 23%]
tests/test_filtering.py .....................                                                                                                         [ 33%]
tests/test_jwt.py sssssssssssssssssssssssssssssssss                                                                                                   [ 48%]
tests/test_misc.py .........                                                                                                                          [ 52%]
tests/test_pagination.py .................................                                                                                            [ 68%]
tests/test_ping.py .                                                                                                                                  [ 68%]
tests/test_related.py .............                                                                                                                   [ 74%]
tests/test_sorting.py ......                                                                                                                          [ 77%]
tests/test_swagger.py ssssssssssssssssssss                                                                                                            [ 86%]
tests/test_testing.py .........                                                                                                                       [ 90%]
tests/test_utils.py .                                                                                                                                 [ 91%]
tests/test_view_errors.py .........xx.....F..                                                                                                         [100%]

========================================================================= FAILURES ==========================================================================
/Users/fermigier/git/flask/flask-resty/flask_resty/testing.py:87: AssertionError: assert 'text/html' == 'application/json'
/Users/fermigier/git/flask/flask-resty/flask_resty/testing.py:87: AssertionError: assert 'text/html' == 'application/json'
/Users/fermigier/git/flask/flask-resty/flask_resty/testing.py:87: AssertionError: assert 'text/html' == 'application/json'
/Users/fermigier/git/flask/flask-resty/flask_resty/testing.py:87: AssertionError: assert 'text/html' == 'application/json'
/Users/fermigier/git/flask/flask-resty/flask_resty/testing.py:87: AssertionError: assert 'text/html' == 'application/json'
/Users/fermigier/git/flask/flask-resty/flask_resty/testing.py:87: AssertionError: assert 'text/html' == 'application/json'
/Users/fermigier/git/flask/flask-resty/flask_resty/testing.py:87: AssertionError: assert 'text/html' == 'application/json'
/Users/fermigier/git/flask/flask-resty/tests/test_view_errors.py:80: RuntimeError

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.