Skip to content

Commit 943282a

Browse files
Merge pull request #2 from AdCombo/master
upd
2 parents fa191e6 + 33aede1 commit 943282a

File tree

11 files changed

+158
-196
lines changed

11 files changed

+158
-196
lines changed

.github/workflows/run_tests.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Python tests and coverage
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- develop
8+
pull_request:
9+
branches:
10+
- master
11+
- develop
12+
13+
jobs:
14+
unit-tests:
15+
16+
runs-on: ubuntu-latest
17+
strategy:
18+
matrix:
19+
python-version: [3.6, 3.7, 3.8, 3.9]
20+
21+
steps:
22+
- uses: actions/checkout@v2
23+
- name: Set up Python ${{ matrix.python-version }}
24+
uses: actions/setup-python@v2
25+
with:
26+
python-version: ${{ matrix.python-version }}
27+
- name: Install the package and dependencies
28+
run: python setup.py install
29+
- name: Lint with flake8
30+
run: |
31+
pip install flake8
32+
# stop the build if there are Python syntax errors or undefined names
33+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
34+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
35+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
36+
- name: Install testing dependencies
37+
run: pip install pytest --upgrade
38+
- name: Test with pytest and run coverage
39+
run: pytest -s -vv --color=yes
40+
41+
coverage:
42+
43+
runs-on: ubuntu-latest
44+
45+
steps:
46+
- uses: actions/checkout@v2
47+
- name: Set up Python 3.8
48+
uses: actions/setup-python@v1
49+
with:
50+
python-version: '3.8'
51+
- name: Install the package and dependencies
52+
run: python setup.py install
53+
- name: Lint with flake8
54+
run: |
55+
pip install flake8
56+
# stop the build if there are Python syntax errors or undefined names
57+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
58+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
59+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
60+
- name: Install testing and coverage dependencies
61+
run: |
62+
pip install coveralls coverage
63+
pip install -U setuptools
64+
pip install pytest --upgrade
65+
- name: Test with pytest with coverage
66+
run: coverage run --source flask_combo_jsonapi -m pytest
67+
- name: Trigger Coveralls
68+
run: coveralls --service=github
69+
env:
70+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71+
72+
73+
# TODO later: add support for parallel builds
74+
# (not needed right now, now python-version-specific code yet)
75+
# https://coveralls-python.readthedocs.io/en/latest/usage/configuration.html#github-actions-support

.travis.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
.. image:: https://travis-ci.org/AdCombo/flask-combo-jsonapi.svg
2-
:target: https://travis-ci.org/AdCombo/flask-combo-jsonapi
1+
.. image:: https://github.com/AdCombo/flask-combo-jsonapi/workflows/Python%20tests%20and%20coverage/badge.svg
2+
:target: https://github.com/AdCombo/flask-combo-jsonapi/actions
33
.. image:: https://coveralls.io/repos/github/AdCombo/flask-combo-jsonapi/badge.svg
44
:target: https://coveralls.io/github/AdCombo/flask-combo-jsonapi
55

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
# The short X.Y version.
6464
version = "1.0"
6565
# The full version, including alpha/beta/rc tags.
66-
release = "1.0.4"
66+
release = "1.0.6"
6767

6868
# The language for content autogenerated by Sphinx. Refer to documentation
6969
# for a list of supported languages.

flask_combo_jsonapi/resource.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from flask_combo_jsonapi.schema import compute_schema, get_relationships, get_model_field
1818
from flask_combo_jsonapi.data_layers.base import BaseDataLayer
1919
from flask_combo_jsonapi.data_layers.alchemy import SqlalchemyDataLayer
20-
from flask_combo_jsonapi.utils import JSONEncoder, validate_model_init_params
20+
from flask_combo_jsonapi.utils import JSONEncoder
2121

2222

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

42-
if "schema" in d and "model" in data_layer_kwargs:
43-
model = data_layer_kwargs["model"]
44-
schema_fields = [get_model_field(d["schema"], key) for key in d["schema"]._declared_fields.keys()]
45-
invalid_params = validate_model_init_params(model=model, params_names=schema_fields)
46-
if invalid_params:
47-
raise Exception(f"Construction of {name} failed. Schema '{d['schema'].__name__}' has "
48-
f"fields={invalid_params} are not declare in {model.__name__} init parameters")
49-
5042
rv.decorators = (check_headers,)
5143
if "decorators" in d:
5244
rv.decorators += d["decorators"]

flask_combo_jsonapi/schema.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def compute_schema(schema_cls, default_kwargs, qs, include):
2121
"""
2222
# manage include_data parameter of the schema
2323
schema_kwargs = default_kwargs
24-
schema_kwargs['include_data'] = tuple()
24+
schema_kwargs['include_data'] = schema_kwargs.get('include_data', tuple())
2525

2626
# collect sub-related_includes
2727
related_includes = {}
@@ -72,6 +72,7 @@ def compute_schema(schema_cls, default_kwargs, qs, include):
7272
related_schema_kwargs['context'] = default_kwargs['context']
7373
if isinstance(related_schema_cls, SchemaABC):
7474
related_schema_kwargs['many'] = related_schema_cls.many
75+
related_schema_kwargs['include_data'] = related_schema_cls.__dict__.get('include_data')
7576
related_schema_cls = related_schema_cls.__class__
7677
if isinstance(related_schema_cls, str):
7778
related_schema_cls = class_registry.get_class(related_schema_cls)

flask_combo_jsonapi/utils.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import inspect
2-
31
import simplejson as json
42
from uuid import UUID
53
from datetime import datetime
@@ -19,30 +17,3 @@ def default(self, obj):
1917
elif isinstance(obj, UUID):
2018
return str(obj)
2119
return json.JSONEncoder.default(self, obj)
22-
23-
24-
def get_model_init_params_names(model):
25-
"""Retrieve all params of model init method
26-
27-
:param DeclarativeMeta model: an object from sqlalchemy
28-
:return tuple: list of init method fields names and boolean flag that init method has kwargs
29-
"""
30-
argnames, _, varkw = inspect.getfullargspec(model.__init__)[:3]
31-
if argnames:
32-
argnames.remove('self')
33-
return argnames, bool(varkw)
34-
35-
36-
def validate_model_init_params(model, params_names):
37-
"""Retrieve invalid params of model init method if it exists
38-
:param DeclarativeMeta model: an object from sqlalchemy
39-
:param list params_names: parameters names to check
40-
:return list: list of invalid fields or None
41-
"""
42-
init_args, has_kwargs = get_model_init_params_names(model)
43-
if has_kwargs:
44-
return
45-
46-
invalid_params = [name for name in params_names if name not in init_args]
47-
if invalid_params:
48-
return invalid_params

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
from setuptools import setup, find_packages
33

4-
__version__ = "1.0.4"
4+
__version__ = "1.0.6"
55

66

77
requirements_filepath = os.path.join(os.path.dirname(__name__), "requirements.txt")

tests/test_resource.py

Lines changed: 0 additions & 61 deletions
This file was deleted.

tests/test_sqlalchemy_data_layer.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class Person(base):
119119
)
120120

121121
computers_owned = relationship("Computer")
122+
address = relationship("Address", backref="person", uselist=False)
122123

123124
yield Person
124125

@@ -136,17 +137,35 @@ class Computer(base):
136137
yield Computer
137138

138139

140+
@pytest.fixture(scope="module")
141+
def address_model(base):
142+
class Address(base):
143+
144+
__tablename__ = "address"
145+
146+
id = Column(Integer, primary_key=True)
147+
street = Column(String)
148+
city = Column(String)
149+
state = Column(String)
150+
zip = Column(String)
151+
person_id = Column(Integer, ForeignKey("person.person_id"))
152+
153+
yield Address
154+
155+
139156
@pytest.fixture(scope="module")
140157
def engine(
141158
person_tag_model, person_single_tag_model, person_model,
142-
computer_model, string_json_attribute_person_model
159+
computer_model, string_json_attribute_person_model,
160+
address_model
143161
):
144162
engine = create_engine("sqlite:///:memory:")
145163
person_tag_model.metadata.create_all(engine)
146164
person_single_tag_model.metadata.create_all(engine)
147165
person_model.metadata.create_all(engine)
148166
computer_model.metadata.create_all(engine)
149167
string_json_attribute_person_model.metadata.create_all(engine)
168+
address_model.metadata.create_all(engine)
150169
return engine
151170

152171

@@ -189,6 +208,17 @@ def computer(session, computer_model):
189208
session_.commit()
190209

191210

211+
@pytest.fixture()
212+
def address(session, address_model):
213+
address_ = address_model(state='NYC')
214+
session_ = session
215+
session_.add(address_)
216+
session_.commit()
217+
yield address_
218+
session_.delete(address_)
219+
session_.commit()
220+
221+
192222
@pytest.fixture(scope="module")
193223
def dummy_decorator():
194224
def deco(f):
@@ -237,6 +267,29 @@ class AddressSchema(MarshmallowSchema):
237267
yield AddressSchema
238268

239269

270+
@pytest.fixture(scope="module")
271+
def person_address_schema():
272+
class PersonAddressSchema(Schema):
273+
class Meta:
274+
type_ = "address"
275+
276+
id = fields.Str(dump_only=True)
277+
street = fields.String()
278+
city = fields.String()
279+
state = fields.String()
280+
zip = fields.String()
281+
282+
person = Relationship(
283+
related_view="api.person_detail",
284+
related_view_kwargs={"person_id": "<person.person_id>"},
285+
schema="PersonSchema",
286+
id_field="person_id",
287+
type_="person",
288+
)
289+
290+
yield PersonAddressSchema
291+
292+
240293
@pytest.fixture(scope="module")
241294
def string_json_attribute_person_schema(address_schema):
242295
class StringJsonAttributePersonSchema(Schema):
@@ -255,7 +308,7 @@ class Meta:
255308

256309

257310
@pytest.fixture(scope="module")
258-
def person_schema(person_tag_schema, person_single_tag_schema):
311+
def person_schema(person_tag_schema, person_single_tag_schema, person_address_schema):
259312
class PersonSchema(Schema):
260313
class Meta:
261314
type_ = "person"
@@ -284,6 +337,11 @@ class Meta:
284337
many=True,
285338
)
286339

340+
address = Relationship(
341+
schema="PersonAddressSchema",
342+
type_="address",
343+
)
344+
287345
yield PersonSchema
288346

289347

@@ -920,6 +978,22 @@ def test_get_relationship_single_empty(session, client, register_routes, compute
920978
assert response.status_code == 200
921979

922980

981+
def test_get_include_multiple(session, client, register_routes, computer, person, address):
982+
session_ = session
983+
person.address = address
984+
computer.person = person
985+
session_.commit()
986+
987+
with client:
988+
response = client.get(
989+
"/computers/" + str(computer.id) + "?include=owner.computers,owner.address", content_type="application/vnd.api+json"
990+
)
991+
included = json.loads(response.data)['included']
992+
types = {item['type'] for item in included}
993+
assert {'person', 'computer', 'address'} == types
994+
assert response.status_code == 200
995+
996+
923997
def test_issue_49(session, client, register_routes, person, person_2):
924998
with client:
925999
for p in [person, person_2]:

0 commit comments

Comments
 (0)