4catalyzer / flask-resty Goto Github PK
View Code? Open in Web Editor NEWBuilding blocks for REST APIs for Flask
Home Page: https://flask-resty.readthedocs.io/en/latest/
License: MIT License
Building blocks for REST APIs for Flask
Home Page: https://flask-resty.readthedocs.io/en/latest/
License: MIT License
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?
Hi!
I'm unable to import the JwtAuthentication class.
(ImportError: cannot import name 'JwtAuthentication' from 'flask_resty'
)
maybe the import is failing here?
Python 3.7 no longer allows trailing commas in generator expressions (see similar issue for Django).
A server generating a 401 (Unauthorized) response MUST send a WWW-Authenticate header field containing at least one challenge.
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)
.
Currently request_args
are foo=bar&foo=baz
where filters are foo=bar,baz
.
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
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
This call looks like it won't work properly if a OneOfSchema is used unless I'm mistaken:
flask-resty/flask_resty/pagination.py
Line 504 in 33471c9
That function will try to pull fields that are from the concrete schema but that reference will be the root schema, I think
Suggestion where docs are most needed, what the various methods to on Authorization
classes.
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"]
.
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.
flask-apispec,I found that the author of fastapi recomended it,but it has a simple document.
do you recomend using id in production enviroment?
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?
https://github.com/philipn/django-rest-framework-filters#filtering-across-relationships
if support:
how?
else:
any advice?
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?
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
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
The time is near: https://pythonclock.org/
Return a 404 in JSON formatting.
Now that you have gone 1.0 I assume the api should be stable enough to start filling holes in the documentation.
I'd be happy to assist.
Consider enabling: landscape.io or similar.
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.
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'
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
The default behavior here is confusing. This isn't really a create semantically, and most of the time we want the same semantics as we have for reads.
Currently it is hardcoded as Bearer
.
In many places, we have to explicitly pass the view into helper classes, e.g.
flask-resty/flask_resty/view.py
Line 295 in 438f439
It would be better if we could bind the view object to helper class instances, or else have some flask_resty.current_view
proxy.
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
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.
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
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?
flask-resty/flask_resty/api.py
Lines 154 to 169 in 4c167f3
Schema().load and Schema().dump don’t return a (data, errors) tuple any more. Only data is returned.
..\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.
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
.
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!
Right now assert_response
first checks the status_code
:
flask-resty/flask_resty/testing.py
Line 106 in 289e4ff
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
...
Passing a schema instance makes it impossible to use schema context
safely.
Currently (version 0.21.1) the pagination is returning only has_next_page
field in the meta
{
"data": [],
"meta": {
"has_next_page": true
}
}
But this is useless in terms of displaying a pagination control in the frontend. (https://getbootstrap.com/docs/4.3/components/pagination/)
The frontend has to know to total number of pages or items, in order to render properly.
Jeffery
The example from https://flask-resty.readthedocs.io/en/latest/guide.html fails when running "flask run" with the following error:
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.
There is logic to skip setting the Location
header if get_location
returns None
, however that is not documented on get_location
.
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
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.
You might find the marshmallow-jsonapi package useful: https://github.com/marshmallow-code/marshmallow-jsonapi .
Nice work on this project; I definitely see a need for something like this in the Flask ecosystem.
Right now ApiError
just allows setting body and status code.
CONTRIBUTING.md
specifies [testing]
while setup.py
names tests
:
Line 41 in c20e499
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?
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.