Coder Social home page Coder Social logo

combojsonapi's People

Contributors

bykov25 avatar dmitrymishanov avatar fiends-grip avatar mahenzon avatar photovirus avatar pysalt avatar roman-adcombo avatar tarasovdg1 avatar znbiz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

combojsonapi's Issues

Wrong ApiSpec class used

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.

Deploy to pypi

This issue is blocked by #7

  • start with version 1.0
  • Setup Github Action for this task.

'OpenAPIConverter' object has no attribute 'resolve_schema_class'

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'

Support for custom marshmallow fields for PostgreSQL filtering

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

mapping_ma_field_to_type = {v: k for k, v in self_nested.schema.TYPE_MAPPING.items()}
mapping_ma_field_to_type[ma_fields.Email] = str
mapping_ma_field_to_type[ma_fields.Dict] = dict
mapping_ma_field_to_type[ma_fields.List] = list
mapping_ma_field_to_type[ma_fields.Decimal] = Decimal
mapping_ma_field_to_type[ma_fields.Url] = str
mapping_ma_field_to_type[ma_fields.DateTime] = datetime.datetime

I think that we need to implement one of these solutions:

  1. try to find parent schema for the custom schema (guessing is not good)
  2. โœ… create a method to define/register custom schema with mapping (seems ok). We need to create an interface to add custom Fields to the mapping (and remove too)
  3. make this mapping available from the outer scope, so it can be easily changed (not a good idea to fully expose it)

PermissionToMapper storage

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.

Custom primary key reference in path (API spec plugin)

Now id field is hardcoded in API spec

@property
def param_id(self) -> dict:
return {
"in": "path",
"name": "id",
"required": True,
"type": "integer",
"format": "int32",
}

we can allow defining primary key field on the ResourceDetail subclass and check it
(for get here, and other references for param_id:)

if issubclass(resource, ResourceDetail):

get field from schema by name, create spec for it

why:

support such paths as /api/person/<string:public_id>/ where public_id is unique/pk

Create SchemaHSTORE

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 JSONB fields

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

POST permission requires GET permission to be present, otherwise it doesn't work

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

Unhandled exception on invalid filter value for relationship

Here's a vulnerable place:

if value and "links" in value:

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

Error using apispec >= 5

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'

Accessing 'prop' of a Column

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 ๐Ÿ˜„

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.