Git Product home page Git Product logo

flask-combo-jsonapi's People

Contributors

akira-dev avatar chenl avatar dennyche avatar dmitrymishanov avatar evgg avatar fiends-grip avatar flotsan avatar greengiant avatar hellupline avatar holgerpeters avatar iamareebjamal avatar igieon avatar jcampbell avatar karec avatar kumy avatar mahenzon avatar miks avatar natureshadow avatar photous avatar psy-q avatar pysalt avatar raveline avatar rgant avatar roman-adcombo avatar rubenghio avatar samuelfirst avatar sodre avatar tarasovdg1 avatar vilyus avatar znbiz 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-combo-jsonapi's Issues

Filter results by search within an array attribute?

Hello all,

I am trying to filter results by searching for a word within an array. For instance this simple model:


class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    words = db.Column(db.ARRAY(db.TEXT))

class PersonSchema(Schema):
    class Meta:
        type_ = 'person'
        self_view = 'person_detail'
        self_view_kwargs = {'id': '<id>'}
        self_view_many = 'person_list'

    id = fields.Integer(as_string=True, dump_only=True)
    words = fields.List(fields.String())

# skipping some DB code here

session.add(Person(id=1, words=["foo", "nobar", "arbitraryWord"]))
session.add(Person(id=2, words=["bar", "nofoo"]))

In this example I try to find all persons with the word "arbitraryWord" in their list of words. I hoped using the operator "in_" would work like this:

       {"name":"arbitraryWord",
        "op":"in_",
        "field":"words"}]

The result is a "Bad filter value". I tried several variations with no luck. Hope someone can give me a hint. Find full example code here: https://github.com/vi0815/exampleArraySearch

Issue with `id` field on PATCH request

dump_only=True on id field as given in examples results in error

curl -X PATCH -d '{ "data":{ "type": "email", "id": "100","attributes":{"status": "S"} } }' -H 'Content-Type: application/vnd.api+json' 'localhost:5000/v1/emails/100' | jq .
{
  "errors": [
    {
      "detail": "Unknown field.",
      "source": {
        "pointer": "/data/id"
      },
      "status": "422",
      "title": "Validation error"
    }
  ],
  "jsonapi": {
    "version": "1.0"
  }
}

It can be reproduced by using your example (in /examples/api.py) and performing the patch call:

PATCH /computers/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "computer",
    "id": "1",
    "attributes": {
      "serial": "Amstrad 2"
    }
  }
}

It seems that the only way to avoid this is to remove the dump_only=True on the id field from the computer schema, but in that case, we can update the id with a patch call... which we obviously don't want. ;-)

Original issue: miLibris/flask-rest-jsonapi#193

Error dumping objects with JSONB fields (with already dumped date/datetime data)

If you have a model with JSONB field, and there's a dumped date / datetime, marshmallow won't be able to serialize it due to it being serialized already (date and datetime strings will cause an error when tried to serialize)
smth like TypeError: descriptor 'isoformat' requires a 'datetime.date' object but received a 'str'

possible solution: load all JSONB fields when loading object, or just before serializing

from marshmallow import Schema, fields
from sqlalchemy.dialects.postgresql import JSONB


def load_jsonb_columns(instance, schema: Schema) -> None:
    """
    :param instance:
    :param schema:
    :return:
    """
    for attr in type(instance)._sa_class_manager.attributes:
        if not isinstance(attr.expression.type, JSONB):
            continue

        if hasattr(instance, attr.name) and attr.name in schema.declared_fields:
            fld: fields.Nested = schema.declared_fields[attr.name]
            if not isinstance(fld, fields.Nested):
                continue
            deserialized = fld.schema.load(getattr(instance, attr.name))
            setattr(instance, attr.name, deserialized)

also there can be other types which require to be converted

Sparse fieldsets for invalid type results in 500

Description
When attempting to use sparse fieldsets for an invalid type, a 500 is raised. Is this the correct functionality? I would think this is more of a 400 level error. To demonstrate, if one were to make a request for GET /cars?fields[dealership]=name using the code below, they would get the documented 500 response. I would be happy to open a PR to change this functionality if there is agreement that there is a better way to handle these errors (i.e. raise 4XX error).

Problematic Code

raise Exception("Couldn't find schema for type: {}".format(resource_type))

Example

# Example
class Car(Base):
    id = Column(Int, primary_key=True)
    make = Column(Str)
    model = Column(Str)

class CarSchema
    class Meta:
        type_ = "car"

    make = fields.Str()
    model = fields.Str()

class CarListResource(ResourceList):
    methods = ["GET"]
    schema = CarSchema
    data_layer = {"session": db.session, "model": Car}

app = Flask(__name__)
api = Api(app)
api.route(
    CarListResource, "car_resource", "/cars",
)

Example Response

// GET /cars?fields[dealership]=name`
// status code: 500

{
  "errors": [
    {
      "status": "500",
      "title": "Unknown error",
      "detail": "Couldn't find schema for type: dealership"
    }
  ],
  "jsonapi": {
    "version": "1.0"
  }
}

Cleanup of branches

I have question regarding branches can we delete branches which are not used or not have any significance.

SqlalchemyDataLayer fails on models with __init__ statement

Sometimes in projects we use models with __init__ method. something like this:

class Cube(Model):
    x = Column()
    y = Column()
    z = Column()

    def __init__():
        self.x = 10
        self.y = 10

However, a resource with a post method will not work with this model. The create_object method throws a TypeError. This is due to such initialization of the model in this method:

obj = self.model(**{key: value
                            for (key, value) in data.items() if key not in join_fields})

That is, there is no validation of the arguments of the __init__ method.

I suggest using inspect.getfullargspec to determine that a model can only accept a limited list of arguments.

Add compatibility with http-errors to jsonapi_exception_formatter

Currently the jsonapi_exception_formatter decorator normally handles JsonApiException exceptions only. Source:

        try:
            return func(*args, **kwargs)
        except JsonApiException as e:
            return make_response(jsonify(jsonapi_errors([e.to_dict()])),
                                 e.status,
                                 headers)
        except Exception as e:
             ...

However, plugins can implement their own errors, which can be correlated with JsonApiException errors. This is necessary to avoid handling errors such as critical.
moreover, this library integrates with flask and should support its standard http-errors.
I suggest in the block except Exception to check the attribute code (or status_code) of the error and try to correlate it with the JSONAPI error. If a match is found, then do not log this error, but return the corresponding JSONAPI error.
Example:

except Exception as e:
    http_ex = format_http_exception(e)
    if http_ex:
        return make_response(jsonify(jsonapi_errors([http_ex.to_dict()])),
                                 http_ex.status,
                                 headers)

installation from pypi source package failing due to missing requirements.txt

Installation using the source package downloaded from pypi (https://pypi.org/project/Flask-COMBO-JSONAPI/#files) would fail because "requirements.txt" used by setup.py wasn't included in the archive.

Steps to reproduce:

$ pip install Flask-COMBO-JSONAPI-1.1.0.tar.gz
Processing /tmp/Flask-COMBO-JSONAPI-1.1.0.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/t7/t04m89ss3cj2x1bqv4gmqj5r000t9r/T/pip-req-build-6neyzad5/setup.py", line 8, in <module>
        with open(requirements_filepath) as fp:
    FileNotFoundError: [Errno 2] No such file or directory: 'requirements.txt'

    ----------------------------------------

It can be resolved if it is declared to be included in MANIFEST.in

Allow custom primary key

There's an option to set custom url_field, but no way to do patch, because id is hardcoded

if "id" not in json_data["data"]:
raise BadRequest('Missing id in "data" node', source={"pointer": "/data/id"})
if str(json_data["data"]["id"]) != str(kwargs[getattr(self._data_layer, "url_field", "id")]):
raise BadRequest(
"Value of id does not match the resource identifier in url", source={"pointer": "/data/id"}
)

I know that another pk field is violation of JSON:API spec, but this feature would be neat

Error handling exception in format_http_exception

Sometimes an error with invalid value can be passed to the `format_http_exception. And it fails here:

code = getattr(ex, 'code', None)
try:
status = int(code)
except TypeError:
return

  File "/.../flask-combo-jsonapi/flask_combo_jsonapi/errors.py", line 32, in format_http_exception
    status = int(code)
ValueError: invalid literal for int() with base 10: 'f405'

Maybe changing to except (TypeError, ValueError): will help

How to reinitialize the query string manager filter and sort

I am creating a plugin which will dasherize the calls from API in filter and sort .

Example: starts-at will be converted to starts_at

But how do I initalize the filter and sort in query string manager to the new dict which I created(which has dasherized parameters).

def _update_query(
        self,
        *args,
        query: Query = None,
        qs: QueryStringManager = None,
        view_kwargs=None,
        self_json_api=None,
        **kwargs,
    ) -> Query:

Rename this repo and python package

Rename repo from flask-rest-jsonapi so that it's easier to search and/or allows it to be added to the JSON:API site as a separate project? In addition it makes it clearer to users that it's not the exact same project, especially in case this API diverges from upstream.

UPD: new name will be flask-combo-jsonapi

Flow:

  • create a separate branch for renaming all imports and other names

  • do the same in combojsonapi

  • refactor our production to use new name

  • run tests

  • merge that branch:

    1. merge to master, tag 1.0.0
    2. rename repo
    3. update requirement in combojsonapi
    4. release combojsonapi @1.0.0
  • deploy to pypi #11

Update marshmallow version to the latest

Right now marshmallow 3.2.1 (2019-09-30) is used
Latest version is 3.12.1 (2021-05-10)

The only change I could find is this:

Passing the string "self" to fields.Nested is deprecated. Use a callable instead.
3.3.0 (2019-12-05)
https://marshmallow.readthedocs.io/en/stable/changelog.html#id17

Otherwise only additions / fixes / improvements which don't affect our usage.

Can't find anything breaking, but better test it before releasing (probably adding coverage would help)

Planning for v1.1.0

Faulty way to get relationship schema

This way to get schema works only if using apispec plugin - it resolves schema on start

return schema._declared_fields[field].__dict__['_Relationship__schema']

but __schema MAY and IS not resolved yet

It's resolved in Relationship.schema
https://github.com/AdCombo/combojsonapi/blob/98162841423acf20414b52c96ec2dffdda9f7131/combojsonapi/utils/marshmallow_fields.py#L22

so this way it works:

def get_related_schema(schema, field):
    return schema._declared_fields[field].schema

POST: AttributeError: Model object has no attribute 'items'

I cannot seem to get around this issue. The documentation isn't helpful to me in this case, but it suggests that my post-data isn't being received.

(forgive me if this isnt the proper location for this kind of report, but I'm having a difficult time finding support)

I've followed the docs on defining my models, schemas and resources - yet I cannot successfully POST.

class MyModelSchema(BaseSchema):
    """MyModel model"""
    class Meta:
        type_ = "my_model"
        self_view = "my_model_detail"
        self_view_kwargs = {"id": "<id>"}
        self_view_many = "my_models_list"
        model = MyModel
        include_relationships = False
        load_instance = True
        sqla_session = Session

    id = fields.Integer(as_string=True, dump_only=True)
    name = fields.String()
    display_name = fields.String()

    

class MyModelList(ResourceList):
    schema = MyModelSchema
    data_layer = {"session": session,
                 "model": MyModel,
                  }



class MyModelDetail(ResourceDetail):
    schema = MyModelSchema
    data_layer = {"session": session,
                 "model": MyModel,
                 }

Separate method in the alchemy layer for collection count fetching

Sometimes we just don't need the collection count for the model/resource or the query and/or the table is so huge, that it would take a lot of time to fetch the collection count.
It happens inside the get_collection here: https://github.com/AdCombo/flask-rest-jsonapi/blob/725cf11286a1ea06aa2ca62182a212d03fceb1c8/flask_rest_jsonapi/data_layers/alchemy.py#L160

So, the decision is:

  • create a separate method in the SqlalchemyDataLayer for getting the collection count. For example get_collection_count. So it can be easily overridden and being replaced with anything else
  • create disable_collection_count and default_collection_count, get it from the resource

Custom data layer, class object requirements

Hi

When using SQLAlchemy, the class of the created object is a child of flask_sqlalchemy().Model.
Ex

db = flask_sqlalchemy(app)
class Person(db.Model): pass
class PersonDetail(ResourceDetail):
    data_layer = { 'model': Person }

But what if we don't use SQLAlchemy? What are the required parent class or interface that our custom class must implement?

Can you clarify this in the doc?

Thank you!
Camille

Using ToastedMarshmallow

Original question on Flapison: multimeric/flapison#22

Has this library been tested with ToastedMarshmallow?
We have been using this library to great effect recently in a project (for about 8 months), but we're now seeing some performance hits during the Marshmallow serialization (sometimes 100's of ms) when dealing with many objects (100's)

I've seen people talk about ToastedMarshmallow and notice that the compute_schema recreates schema instances everytime, according to the ToastedMarshmallow documentation this will reduce the gain heavily.

Is there a reason we recreate schema objects every time? If not, what would be the best way to change the code to allow one schema object per resource?

I tried changing the code and using an in-memory object dict as an object cache, but toastedmarshmallow still made no performance impact.
Anybody tried this before?

Also posted here: miLibris#192

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

Make sqlalchemy an optional dependency

Hi guys

Would you know distutils enough to make sqlalchemy an optional dependency?

Since we have only custom DataLayers, we don't use SQLAlchemy and it pulls too many dependencies (like greenlet which does not work on Alpine without a recompilation... so the full pip install is failing). It's a mess. Anyway SQLAlchemy is just a default implementation and should not be mandatory to use it.

I you can supply a simple and nice way to remove sqlalchemy from the packages pulled by pip, it would be awesome.

Best regards
Camille

test_get_list_with_simple_filter_relationship_custom_qs_api test failure

Hi,

The following test fails:

==================================== ERRORS ====================================
_ ERROR at setup of test_get_list_with_simple_filter_relationship_custom_qs_api _

client = <FlaskClient <Flask 'tests.conftest'>>, app = <Flask 'tests.conftest'>
api_blueprint = <Blueprint 'api'>
custom_query_string_manager = <class 'tests.test_sqlalchemy_data_layer.custom_query_string_manager.<locals>.QS'>
person_list_2 = <class 'tests.test_sqlalchemy_data_layer.person_list_2.<locals>.PersonList'>

    @pytest.fixture(scope="module")
    def register_routes_custom_qs(
            client,
            app,
            api_blueprint,
            custom_query_string_manager,
            person_list_2,
    ):
        api = Api(blueprint=api_blueprint, qs_manager_class=custom_query_string_manager)
        api.route(person_list_2, "person_list_qs", "/qs/persons")
>       api.init_app(app)

tests/test_sqlalchemy_data_layer.py:655: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
flask_combo_jsonapi/api.py:59: in init_app
    self.app.register_blueprint(self.blueprint)
/gnu/store/4rlh2lfp2p6hdpsdsvcd8b05hm97vw86-python-flask-2.1.1/lib/python3.9/site-packages/flask/scaffold.py:56: in wrapper_func
    return f(self, *args, **kwargs)
/gnu/store/4rlh2lfp2p6hdpsdsvcd8b05hm97vw86-python-flask-2.1.1/lib/python3.9/site-packages/flask/app.py:1028: in register_blueprint
    blueprint.register(self, options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Blueprint 'api'>, app = <Flask 'tests.conftest'>, options = {}

    def register(self, app: "Flask", options: dict) -> None:
        """Called by :meth:`Flask.register_blueprint` to register all
        views and callbacks registered on the blueprint with the
        application. Creates a :class:`.BlueprintSetupState` and calls
        each :meth:`record` callback with it.
    
        :param app: The application this blueprint is being registered
            with.
        :param options: Keyword arguments forwarded from
            :meth:`~Flask.register_blueprint`.
    
        .. versionchanged:: 2.0.1
            Nested blueprints are registered with their dotted name.
            This allows different blueprints with the same name to be
            nested at different locations.
    
        .. versionchanged:: 2.0.1
            The ``name`` option can be used to change the (pre-dotted)
            name the blueprint is registered with. This allows the same
            blueprint to be registered multiple times with unique names
            for ``url_for``.
    
        .. versionchanged:: 2.0.1
            Registering the same blueprint with the same name multiple
            times is deprecated and will become an error in Flask 2.1.
        """
        name_prefix = options.get("name_prefix", "")
        self_name = options.get("name", self.name)
        name = f"{name_prefix}.{self_name}".lstrip(".")
    
        if name in app.blueprints:
            bp_desc = "this" if app.blueprints[name] is self else "a different"
            existing_at = f" '{name}'" if self_name != name else ""
    
>           raise ValueError(
                f"The name '{self_name}' is already registered for"
                f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
                f" provide a unique name."
            )
E           ValueError: The name 'api' is already registered for this blueprint. Use 'name=' to provide a unique name.

/gnu/store/4rlh2lfp2p6hdpsdsvcd8b05hm97vw86-python-flask-2.1.1/lib/python3.9/site-packages/flask/blueprints.py:305: ValueError
=============================== warnings summary ===============================
tests/conftest.py:12
  /tmp/guix-build-python-flask-combo-jsonapi-1.1.0.drv-0/source/tests/conftest.py:12: PytestDeprecationWarning: @pytest.yield_fixture is deprecated.
  Use @pytest.fixture instead; they are the same.
    @pytest.yield_fixture(scope="session")

tests/test_sqlalchemy_data_layer.py::test_get_list
  /tmp/guix-build-python-flask-combo-jsonapi-1.1.0.drv-0/source/tests/test_sqlalchemy_data_layer.py:181: SAWarning: relationship 'Person.computers_owned' will copy column person.person_id to column computer.person_id, which conflicts with relationship(s): 'Computer.person' (copies person.person_id to computer.person_id), 'Person.computers' (copies person.person_id to computer.person_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards.   To silence this warning, add the parameter 'overlaps="computers,person"' to the 'Person.computers_owned' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
    person_ = person_model(name="test")

tests/test_sqlalchemy_data_layer.py::test_delete_detail
  /tmp/guix-build-python-flask-combo-jsonapi-1.1.0.drv-0/source/tests/test_sqlalchemy_data_layer.py:187: SAWarning: DELETE statement on table 'person' expected to delete 1 row(s); 0 were matched.  Please set confirm_deleted_rows=False within the mapper configuration to prevent this warning.
    session_.commit()

-- Docs: https://docs.pytest.org/en/stable/warnings.html
=========================== short test summary info ============================
ERROR tests/test_sqlalchemy_data_layer.py::test_get_list_with_simple_filter_relationship_custom_qs_api
=================== 118 passed, 3 warnings, 1 error in 1.29s ===================

Tested on GNU Guix with Python 3.9.9 and the following dependencies:

Allow fetching multiple relationships from same included resource

For example, let's say, we have this resource heirarchy event -> session -> track, location

And I want to fetch them all, my query would be

/v1/events/123?include=session.track,session.location

This should be possible, as evident here http://jsonapiplayground.reyesoft.com/v2/stores/1?include=books.author,books.chapters

But the library implementation overrides the previous include by latest include, so it will only include session.location, and not session.track

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.