adcombo / combojsonapi Goto Github PK
View Code? Open in Web Editor NEWPlugins to improve functionality of the Flask-COMBO-JSONAPI
License: MIT License
Plugins to improve functionality of the Flask-COMBO-JSONAPI
License: MIT License
Here ApiSpec class from apispec is used, but here register_field
method of combojsonapi.spec.apispec.APISpec
is called (and register converter
below too).
Seems like it doesn't make any problems now because _fields
and _converters
attributes of ApiSpecPlugin are always empty lists.
We should either 1) remove combojsonapi.spec.apispec.APISpec
, usages of its methods and _fields
and _converters
attributes of ApiSpecPlugin
or 2) change ApiSpec
class in ApiSpecPlugin.__init__
if there are some reasons for its existence.
At least create hook for running black for formatting (#10)
This issue is blocked by #7
The name Flask-REST-JSONAPI
is going to be changed to Flask-COMBO-JSONAPI
Relates to AdCombo/flask-combo-jsonapi#10
Happens with apispec 4.0.0 (was installed automatically)
Fastest: pin/freeze apispec version (works with 2.0.2)
Best: update compatibility
Full traceback:
Traceback (most recent call last):
File "app.py", line 105, in <module>
json_api.route(views.PersonList, "person_list", "/api/person/", tag="Person")
File "/usr/local/lib/python3.7/site-packages/flask_combo_jsonapi/api.py", line 111, in route
i_plugin.after_route(resource=resource, view=view, urls=urls, self_json_api=self, **kwargs)
File "/usr/local/lib/python3.7/site-packages/combojsonapi/spec/plugin.py", line 114, in after_route
self._add_definitions_in_spec(resource.schema)
File "/usr/local/lib/python3.7/site-packages/combojsonapi/spec/plugin.py", line 444, in _add_definitions_in_spec
self.spec.components.schema(name_schema, schema=schema)
File "/usr/local/lib/python3.7/site-packages/apispec/core.py", line 82, in schema
ret.update(plugin.schema_helper(name, component, **kwargs) or {})
File "/usr/local/lib/python3.7/site-packages/apispec/ext/marshmallow/__init__.py", line 164, in schema_helper
json_schema = self.converter.schema2jsonschema(schema_instance)
File "/usr/local/lib/python3.7/site-packages/apispec/ext/marshmallow/openapi.py", line 175, in schema2jsonschema
jsonschema = self.fields2jsonschema(fields, partial=partial, ordered=ordered)
File "/usr/local/lib/python3.7/site-packages/apispec/ext/marshmallow/openapi.py", line 199, in fields2jsonschema
prop = self.field2property(field_obj)
File "/usr/local/lib/python3.7/site-packages/apispec/ext/marshmallow/field_converter.py", line 161, in field2property
ret.update(attr_func(field, ret=ret))
File "/usr/local/lib/python3.7/site-packages/apispec/ext/marshmallow/field_converter.py", line 417, in nested2properties
schema_dict = self.resolve_nested_schema(field.schema)
File "/usr/local/lib/python3.7/site-packages/combojsonapi/spec/plugin.py", line 478, in resolve_nested_schema
schema_cls = self.resolve_schema_class(schema)
AttributeError: 'OpenAPIConverter' object has no attribute 'resolve_schema_class'
There's already a sphinx config. It requires minor refactoring and then we can deploy to read-the-docs
If you define a custom field for the PostgreSQL JSONB schema and then try to filter it, filtering fails with KeyError
, because custom schema is not present in the mapping
combojsonapi/combojsonapi/postgresql_jsonb/plugin.py
Lines 131 to 137 in 4eda75c
I think that we need to implement one of these solutions:
I think that it's not a good idea to store permissions for ORM-mapper inside a class like PermissionToMapper, because it implicitly mixes permissions for different apps if they are started inside one process (e.g. for testing purposes).
May be it would be better to make own PermissionToMapper instance for each PermissionPlugin instance or use additional nesting level to somehow identify, which app is accessing permissions now.
Seems like the many
kwarg is set to True
in the permission plugin
No matter if the passed resource
is for List, or for Detail
Now id field is hardcoded in API spec
combojsonapi/combojsonapi/spec/plugin.py
Lines 136 to 144 in 9816284
we can allow defining primary key field on the ResourceDetail
subclass and check it
(for get here, and other references for param_id
:)
combojsonapi/combojsonapi/spec/plugin.py
Line 307 in 9816284
get field from schema by name, create spec for it
support such paths as /api/person/<string:public_id>/
where public_id
is unique/pk
There we can configure:
We need to implement SchemaHSTORE
like SchemaJSONB
. Maybe SchemaJSONB
has to be inherited from SchemaHSTORE
because in python world it's just a one-level dict
. So, smth like this:
class SchemaHSTORE(Schema):
class Meta:
filtering = True
class SchemaJSONB(SchemaHSTORE):
class Meta:
filtering = True
OR create smth like base schema:
from marshmallow import Schema
class JSONSchemaBase(Schema):
class Meta:
filtering = True
class SchemaHSTORE(JSONSchemaBase):
"""
Doc for HSTORE
"""
class SchemaJSONB(JSONSchemaBase):
"""
Doc for JSONB
"""
Search by JSON field is limited to nesting up to 2. Search with nesting of 3 or more is often required, for example extra.lvl1.lvl2
If you define List resource like this:
class PersonList(ResourceList):
schema = schemas.PersonSchema
methods = ["POST"]
data_layer = {
"session": db.session,
"model": models.Person,
"permission_post": [permissions.PersonPermission],
}
POST permission doesn't work ๐จ . You can create objects without any permissions
but if you add GET permission
class PersonList(ResourceList):
schema = schemas.PersonSchema
methods = ["POST"]
data_layer = {
"session": db.session,
"model": models.Person,
"permission_get": [permissions.PersonPermission],
"permission_post": [permissions.PersonPermission],
}
then it works. before creating it checks get permission
Here's a vulnerable place:
For example you have a schema
class UserSchema(Schema):
class Meta:
model = User
type_ = "user"
self_view = "user_detail"
self_view_kwargs = {"id": "<id>"}
self_view_many = "user_list"
ordered = True
group = Relationship(
nested="GroupSchema",
attribute="_relationship_group_id_",
related_view="group_detail",
related_view_kwargs={"id": "<group_id>"},
schema="GroupSchema",
type_="group",
)
And try to filter it using invalid filter:
[
{
"name": "group",
"op": "eq",
"val": 42
}
]
It raises this:
File "/.../src/combojsonapi/combojsonapi/utils/marshmallow_fields.py", line 56, in deserialize
if value and "links" in value:
TypeError: argument of type 'int' is not iterable
And a valid shorthand for it (which works well) is
[
{
"name": "group.id",
"op": "eq",
"val": 42
}
]
I think that this variant has to be working too, but it makes invalid filtering -- returns objects, that should not be here
https://flask-rest-jsonapi.readthedocs.io/en/latest/filtering.html#
[
{
"name": "group",
"op": "any",
"val": {
"name": "id",
"op": "eq",
"val": 42
}
}
]
I think that we have to add proper checks for data
and raise InvalidFilters
Freeze apispec <5 or update to make compatible
In latest marshmallow new fields are declared, and apispec uses them
Traceback:
Traceback (most recent call last):
File "/projects/flask-jsonapi-aug/app.py", line 29, in <module>
api.route(PersonList, "person_list", "/persons", tag="Person")
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/flask_combo_jsonapi/api.py", line 111, in route
i_plugin.after_route(resource=resource, view=view, urls=urls, self_json_api=self, **kwargs)
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/combojsonapi/spec/plugin.py", line 113, in after_route
self._add_definitions_in_spec(resource.schema)
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/combojsonapi/spec/plugin.py", line 459, in _add_definitions_in_spec
self.spec.components.schema(name_schema, schema=schema)
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/apispec/core.py", line 132, in schema
ret.update(plugin.schema_helper(component_id, ret, **kwargs) or {})
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/apispec/ext/marshmallow/__init__.py", line 166, in schema_helper
json_schema = self.converter.schema2jsonschema(schema_instance)
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/apispec/ext/marshmallow/openapi.py", line 182, in schema2jsonschema
jsonschema = self.fields2jsonschema(fields, partial=partial, ordered=ordered)
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/apispec/ext/marshmallow/openapi.py", line 208, in fields2jsonschema
prop = self.field2property(field_obj)
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/apispec/ext/marshmallow/field_converter.py", line 172, in field2property
ret.update(attr_func(field, ret=ret))
File "/projects/flask-jsonapi-aug/venv/lib/python3.9/site-packages/apispec/ext/marshmallow/field_converter.py", line 219, in field2default
default = field.load_default
AttributeError: 'String' object has no attribute 'load_default'
new feature #34 requires docs
I've faced an issue when there's a Column in a model and another column uses the same name for a column, passed in the Column object, the PermissionPlugin fails processing it. Here's an example:
from flask import Flask
from flask_rest_jsonapi import Api, ResourceList
from combojsonapi.spec import ApiSpecPlugin
from combojsonapi.permission.permission_plugin import PermissionPlugin
from marshmallow_jsonapi import fields, Schema
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from logging import getLogger as get_logger
logger = get_logger()
engine = create_engine("sqlite:///:memory:")
Session = scoped_session(sessionmaker())
Base = declarative_base(bind=engine)
class MyModel(Base):
__tablename__ = "model"
id = Column(Integer, primary_key=True)
# creating a column
model_type = Column(Integer)
# for no reason creating a column
# which takes the same name for no reason
model_entity = Column("model_type", Integer)
class MyModelSchema(Schema):
class Meta:
model = MyModel
type_ = "model"
self_view = "model_detail"
self_view_kwargs = {"id": "<id>"}
self_view_many = "model_list"
id = fields.Integer(as_string=True)
class MyModelResourceList(ResourceList):
schema = MyModelSchema
methods = ["GET"]
data_layer = {
"session": Session,
"model": MyModel,
}
def setup_openapi(app: Flask):
app.config["OPENAPI_URL_PREFIX"] = "/api/swagger"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/"
app.config["OPENAPI_SWAGGER_UI_VERSION"] = "3.22.0"
def main():
app = Flask(__name__)
@app.errorhandler(Exception)
def handle_bad_request(e):
logger.error("here it is:", exc_info=e)
return "eroro", 500
setup_openapi(app)
api_spec = ApiSpecPlugin(
app=app,
tags={"MyModels": "API MyModels"},
)
api_json = Api(
app,
plugins=(
api_spec,
PermissionPlugin(strict=False),
),
)
api_json.route(MyModelResourceList, "model_list", "/api/model/", tag="MyModels")
app.run(debug=True)
if __name__ == "__main__":
main()
When trying to access "/api/model/"
I get the following error:
here it is:
Traceback (most recent call last):
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/sqlalchemy/lib/sqlalchemy/sql/elements.py", line 717, in __getattr__
return getattr(self.comparator, key)
AttributeError: 'Comparator' object has no attribute 'prop'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/decorators.py", line 45, in wrapper
return func(*args, **kwargs)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/lib/python3.6/site-packages/flask/views.py", line 88, in view
return self.dispatch_request(*args, **kwargs)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/decorators.py", line 87, in wrapper
raise e
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/decorators.py", line 74, in wrapper
return func(*args, **kwargs)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/resource.py", line 71, in dispatch_request
response = method(*args, **kwargs)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/decorators.py", line 45, in wrapper
return func(*args, **kwargs)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/combojsonapi/combojsonapi/permission/permission_plugin.py", line 63, in wrapper
return method(*args, **kwargs, _permission_user=permission_user)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/decorators.py", line 65, in wrapper
return func(*args, **kwargs)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/resource.py", line 118, in get
objects_count, objects = self.get_collection(qs, kwargs)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/resource.py", line 220, in get_collection
return self._data_layer.get_collection(qs, kwargs)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/flask-rest-jsonapi/flask_rest_jsonapi/data_layers/alchemy.py", line 150, in get_collection
self_json_api=self)
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/combojsonapi/combojsonapi/permission/permission_plugin.py", line 372, in data_layer_get_collection_update_query
name_columns = list(set(name_columns) & set(get_columns_for_query(self_json_api.model)))
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/combojsonapi/combojsonapi/permission/permission_plugin.py", line 36, in get_columns_for_query
and isinstance(getattr(value, 'prop'), ColumnProperty):
File "/home/khorenyan/.local/share/virtualenvs/core-C3GrfXXb/src/sqlalchemy/lib/sqlalchemy/sql/elements.py", line 721, in __getattr__
% (type(self).__name__, type(self.comparator).__name__, key)
AttributeError: Neither 'Column' object nor 'Comparator' object has an attribute 'prop'
127.0.0.1 - - [10/Apr/2020 01:07:12] "GET /api/model/?page%5Bnumber%5D=1&page%5Bsize%5D=10 HTTP/1.1" 500 -
So, the error is AttributeError: Neither 'Column' object nor 'Comparator' object has an attribute 'prop'
.
I'll create a PR for proper attributes checks ๐
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.