Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion flask_combo_jsonapi/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from flask_combo_jsonapi.schema import compute_schema, get_relationships, get_model_field
from flask_combo_jsonapi.data_layers.base import BaseDataLayer
from flask_combo_jsonapi.data_layers.alchemy import SqlalchemyDataLayer
from flask_combo_jsonapi.utils import JSONEncoder
from flask_combo_jsonapi.utils import JSONEncoder, validate_model_init_params


class ResourceMeta(MethodViewType):
Expand All @@ -39,6 +39,14 @@ def __new__(cls, name, bases, d):
data_layer_kwargs = d["data_layer"]
rv._data_layer = data_layer_cls(data_layer_kwargs)

if "schema" in d and "model" in data_layer_kwargs:
model = data_layer_kwargs["model"]
schema_fields = [get_model_field(d["schema"], key) for key in d["schema"]._declared_fields.keys()]
invalid_params = validate_model_init_params(model=model, params_names=schema_fields)
if invalid_params:
raise Exception(f"Construction of {name} failed. Schema '{d['schema'].__name__}' has "
f"fields={invalid_params} are not declare in {model.__name__} init parameters")

rv.decorators = (check_headers,)
if "decorators" in d:
rv.decorators += d["decorators"]
Expand Down
29 changes: 29 additions & 0 deletions flask_combo_jsonapi/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import inspect

import simplejson as json
from uuid import UUID
from datetime import datetime
Expand All @@ -17,3 +19,30 @@ def default(self, obj):
elif isinstance(obj, UUID):
return str(obj)
return json.JSONEncoder.default(self, obj)


def get_model_init_params_names(model):
"""Retrieve all params of model init method

:param DeclarativeMeta model: an object from sqlalchemy
:return tuple: list of init method fields names and boolean flag that init method has kwargs
"""
argnames, _, varkw = inspect.getfullargspec(model.__init__)[:3]
if argnames:
argnames.remove('self')
return argnames, bool(varkw)


def validate_model_init_params(model, params_names):
"""Retrieve invalid params of model init method if it exists
:param DeclarativeMeta model: an object from sqlalchemy
:param list params_names: parameters names to check
:return list: list of invalid fields or None
"""
init_args, has_kwargs = get_model_init_params_names(model)
if has_kwargs:
return

invalid_params = [name for name in params_names if name not in init_args]
if invalid_params:
return invalid_params
61 changes: 61 additions & 0 deletions tests/test_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from unittest.mock import Mock

import pytest
from marshmallow_jsonapi import fields, Schema
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

from flask_combo_jsonapi import ResourceList


@pytest.fixture(scope='module')
def base():
yield declarative_base()


@pytest.fixture(scope='module')
def model(base):
class SampleModel(base):
__tablename__ = 'model_sample'

id = Column(Integer, primary_key=True, index=True)
key1 = Column(String)
key2 = Column(String)

def __init__(self, key1):
pass

yield SampleModel


@pytest.fixture(scope='module')
def schema_for_model(model):
class SampleSchema(Schema):
class Meta:
model = model

id = fields.Integer()
key1 = fields.String()
key2 = fields.String()

yield SampleSchema


def test_resource_meta_init(model, schema_for_model):
expected_fields = ['id', 'key2']
raised_ex = None
try:
class SampleResourceList(ResourceList):
schema = schema_for_model
methods = ['GET', 'POST']
data_layer = {
'session': Mock(),
'model': model,
}
except Exception as ex:
raised_ex = ex

assert raised_ex
message = f"Construction of SampleResourceList failed. Schema '{schema_for_model.__name__}' " \
f"has fields={expected_fields}"
assert message in str(raised_ex)
73 changes: 73 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import pytest
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

from flask_combo_jsonapi.utils import get_model_init_params_names, validate_model_init_params


@pytest.fixture(scope='module')
def base():
yield declarative_base()


@pytest.fixture(scope='module')
def model_without_init(base):
class ModelWithoutInit(base):
__tablename__ = 'model_without_init'

id = Column(Integer, primary_key=True, index=True)
key1 = Column(String)
key2 = Column(String)

yield ModelWithoutInit


@pytest.fixture(scope='module')
def model_with_kwargs_in_init(base):
class ModelWithKwargsInit(base):
__tablename__ = 'model_with_kwargs_init'

id = Column(Integer, primary_key=True, index=True)
key1 = Column(String)
key2 = Column(String)

def __init__(self, key1=0, **kwargs):
pass

yield ModelWithKwargsInit


@pytest.fixture(scope='module')
def model_with_positional_args_init(base):
class ModelWithPositionalArgsInit(base):
__tablename__ = 'model_with_positional_args_init'

id = Column(Integer, primary_key=True, index=True)
key1 = Column(String)
key2 = Column(String)

def __init__(self, key1, key2=0):
pass

yield ModelWithPositionalArgsInit


def test_get_model_init_params_names(model_without_init, model_with_kwargs_in_init,
model_with_positional_args_init):
args, has_kwargs = get_model_init_params_names(model_without_init)
assert ([], True) == (args, has_kwargs)

args, has_kwargs = get_model_init_params_names(model_with_kwargs_in_init)
assert (['key1'], True) == (args, has_kwargs)

args, has_kwargs = get_model_init_params_names(model_with_positional_args_init)
assert (['key1', 'key2'], False) == (args, has_kwargs)


def test_validate_model_init_params(model_with_kwargs_in_init, model_with_positional_args_init):
schema_attrs = ['id', 'key1', 'key2']
invalid_params = validate_model_init_params(model_with_kwargs_in_init, schema_attrs)
assert invalid_params is None

invalid_params = validate_model_init_params(model_with_positional_args_init, schema_attrs)
assert invalid_params == ['id']