Skip to content

Commit

Permalink
Enhance Protean Flask to be compatible with Protean 0.0.11 (#18)
Browse files Browse the repository at this point in the history
* Remove Pylint and retain Flake8 as linter
* Clean up test cases and use pytest fixtures
* Upgrade package versions where possible
* Upgrade to support Python 3.7 alone
  • Loading branch information
subhashb authored Apr 30, 2019
1 parent dc85d43 commit 6a6c1f8
Show file tree
Hide file tree
Showing 25 changed files with 122 additions and 602 deletions.
426 changes: 0 additions & 426 deletions .pylintrc

This file was deleted.

1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: python
dist: xenial
python:
- '3.6'
- '3.7'
install:
- pip install -r requirements/test.txt
Expand Down
1 change: 0 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ include .bumpversion.cfg
include .coveragerc
include .cookiecutterrc
include .editorconfig
include .pylintrc

include AUTHORS.rst
include CHANGELOG.rst
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Development

::

pyenv virtualenv -p python3.6 3.6.5 protean-flask-dev
pyenv virtualenv -p python3.7 3.7.2 protean-flask-dev

To run the all tests run::

Expand Down
8 changes: 1 addition & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
werkzeug==0.14.1
flask==1.0.2
click==7.0
inflect==1.0.1
marshmallow==2.16.1
protean==0.0.9
-r requirements/dev.txt
-r requirements/prod.txt
10 changes: 4 additions & 6 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
check-manifest==0.37
coverage==4.5.1
-r prod.txt

check-manifest==0.38
coverage==4.5.3
docutils==0.14
flake8==3.5.0
pylint==2.0.0
tox==3.1.2
twine==1.11.0
6 changes: 6 additions & 0 deletions requirements/prod.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
click==7.0
flask==1.0.2
inflect==1.0.1
marshmallow==2.19.2
protean==0.0.11
werkzeug==0.15.2
8 changes: 4 additions & 4 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-r dev.txt

mock==2.0.0
pytest==3.6.3
pytest-cov==2.5.1
pytest-travis-fold==1.3.0
pluggy==0.9.0
pytest-cov==2.6.1
pytest-flake8==1.0.4
pluggy==0.6.0
pytest-travis-fold==1.3.0
pytest==4.4.1
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[bdist_wheel]
universal = 1


[flake8]
max-line-length = 140
exclude = */migrations/*
extend-ignore = E731

[tool:pytest]
testpaths = tests
Expand Down
13 changes: 3 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def read(*names, **kwargs):
'Operating System :: POSIX :: Linux',
'Operating System :: OS/2',
'Programming Language :: Python',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Database',
'Topic :: Software Development :: Libraries',
Expand All @@ -65,20 +65,13 @@ def read(*names, **kwargs):
],
install_requires=[
'click==7.0',
'werkzeug==0.14.1',
'werkzeug==0.15.2',
'flask==1.0.2',
'inflect==1.0.1',
'protean==0.0.9',
'protean==0.0.11',
'marshmallow==2.16.1',
# eg: 'aspectlib==1.1.1', 'six>=1.7',
],
test_requires=[
'mock==2.0.0',
'pytest==3.6.3',
'pytest-cov==2.5.1',
'pytest-travis-fold==1.3.0',
'pluggy==0.6.0'
],
extras_require={
# eg:
# 'rst': ['docutils>=0.11'],
Expand Down
3 changes: 1 addition & 2 deletions src/protean_flask/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from protean.conf import active_config
from protean.context import context
from protean.core.exceptions import UsecaseExecutionError
from protean.core.repository import repo_factory
from protean.utils.importlib import perform_import

from ..utils import derive_tenant
Expand Down Expand Up @@ -129,7 +128,7 @@ def _load_protean():
def _cleanup_protean(response):
""" Cleanup the context and connections on end of request"""
context.cleanup()
repo_factory.close_connections()
# FIXME Close Open Connections
return response

def _handle_exception(self, e):
Expand Down
2 changes: 1 addition & 1 deletion src/protean_flask/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __init__(self, *args, **kwargs):

entity_fields = OrderedDict()
for field_name, field_obj in \
self.opts.entity_cls.declared_fields.items():
self.opts.entity_cls.meta_.declared_fields.items():
if self.opts.fields and field_name not in self.opts.fields:
continue
elif self.opts.exclude and field_name in self.opts.exclude:
Expand Down
5 changes: 3 additions & 2 deletions src/protean_flask/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from protean.core.usecase import UpdateUseCase
from protean.utils import inflection
from protean.utils.importlib import perform_import
from werkzeug.wrappers import parse_options_header
from werkzeug.http import parse_options_header

from protean_flask.utils import immutable_dict_2_dict

Expand Down Expand Up @@ -208,10 +208,11 @@ def _process_request(self, usecase_cls, request_object_cls, payload,
# Serialize the results and return the response
if many:
items = serializer.dump(response_object.value.items)
page = int(response_object.value.offset / response_object.value.limit) + 1
result = {
INFLECTOR.plural(resource): items.data,
'total': response_object.value.total,
'page': response_object.value.page
'page': page
}
return result, response_object.code.value

Expand Down
49 changes: 43 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,47 @@
os.environ['PROTEAN_CONFIG'] = 'tests.support.sample_config'


@pytest.fixture(scope='module', autouse=True)
@mock.patch.dict(os.environ, {'PROTEAN_CONFIG': 'tests.support.sample_config'})
def config():
"""Global Config fixture for all tests"""
def pytest_addoption(parser):
"""Additional options for running tests with pytest"""
parser.addoption(
"--slow", action="store_true", default=False, help="run slow tests"
)

from protean.conf import active_config
return active_config

def pytest_collection_modifyitems(config, items):
"""Configure special markers on tests, so as to control execution"""
if config.getoption("--slow"):
# --slow given in cli: do not skip slow tests
return
skip_slow = pytest.mark.skip(reason="need --slow option to run")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)


@pytest.fixture(scope="session", autouse=True)
def register_models():
"""Register Test Models with Dict Repo
Run only once for the entire test suite
"""
from protean.core.repository import repo_factory
from tests.support.sample_app.entities import Dog, RelatedDog, Human

repo_factory.register(Dog)
repo_factory.register(RelatedDog)
repo_factory.register(Human)


@pytest.fixture(autouse=True)
def run_around_tests():
"""Cleanup Database after each test run"""
from protean.core.repository import repo_factory
from tests.support.sample_app.entities import Dog, RelatedDog, Human

# A test function will be run at this point
yield

repo_factory.get_repository(Dog).delete_all()
repo_factory.get_repository(RelatedDog).delete_all()
repo_factory.get_repository(Human).delete_all()
9 changes: 0 additions & 9 deletions tests/core/test_blueprint.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Module to test View functionality and features"""

from protean.core.repository import repo_factory
from tests.support.sample_app import app
from tests.support.sample_app.entities import Dog
from tests.support.sample_app.entities import Human
Expand All @@ -16,14 +15,6 @@ def setup_class(cls):
# Create the test client
cls.client = app.test_client()

@classmethod
def teardown_class(cls):
""" Teardown for this test case"""

# Delete all dog objects
repo_factory.Dog.delete_all()
repo_factory.Human.delete_all()

def test_show(self):
""" Test retrieving an entity using blueprint ShowAPIResource"""

Expand Down
9 changes: 1 addition & 8 deletions tests/core/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import marshmallow as ma
import pytest
from protean.core.exceptions import ConfigurationError
from protean.core.repository import repo_factory

from protean_flask.core.serializers import EntitySerializer

Expand Down Expand Up @@ -113,13 +112,6 @@ def setup_class(cls):
""" Setup the test case """
cls.human = Human.create(id=1, name='John')

@classmethod
def teardown_class(cls):
""" Teardown for this test case"""

# Delete all dog objects
repo_factory.Human.delete_all()

def test_reference_field(self):
""" Test that the reference field gets serialized """

Expand All @@ -138,6 +130,7 @@ def test_reference_field(self):

def test_hasmany_association(self):
""" Test the has many association gets serialized """
RelatedDog.create(id=5, name='Johnny', owner=self.human)

s = HumanDetailSerializer()
s_result = s.dump(self.human)
Expand Down
49 changes: 18 additions & 31 deletions tests/core/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,26 @@

import pytest
from protean.core.exceptions import ObjectNotFoundError
from protean.core.repository import repo_factory
from tests.support.sample_app import app
from tests.support.sample_app.entities import Dog


class TestGenericAPIResource:
"""Class to test Generic API Resource functionality and methods"""

@classmethod
def setup_class(cls):
""" Setup for this test case"""
@pytest.fixture(scope="function")
def client(self):
""" Setup client for test cases """
yield app.test_client()

# Create the test client
cls.client = app.test_client()

@classmethod
def teardown_class(cls):
""" Teardown for this test case"""

# Delete all dog objects
repo_factory.Dog.delete_all()

def test_show(self):
def test_show(self, client):
""" Test retrieving an entity using ShowAPIResource"""

# Create a dog object
Dog.create(id=5, name='Johnny', owner='John')

# Fetch this dog by ID
rv = self.client.get('/dogs/5')
rv = client.get('/dogs/5')
assert rv.status_code == 200

expected_resp = {
Expand All @@ -42,14 +32,14 @@ def test_show(self):
assert rv.json == expected_resp

# Test search by invalid id
rv = self.client.get('/dogs/6')
rv = client.get('/dogs/6')
assert rv.status_code == 404

# Delete the dog now
dog = Dog.get(5)
dog.delete()

def test_list(self):
def test_list(self, client):
""" Test listing an entity using ListAPIResource """
# Create a dog objects
Dog.create(id=1, name='Johnny', owner='John')
Expand All @@ -58,24 +48,22 @@ def test_list(self):
Dog.create(id=4, name='Brawny', owner='John', age=2)

# Get the list of dogs
rv = self.client.get('/dogs?order_by[]=age')
rv = client.get('/dogs?order_by[]=age')
assert rv.status_code == 200
assert rv.json['total'] == 4
assert rv.json['dogs'][0] == {'age': 2, 'id': 4, 'name': 'Brawny', 'owner': 'John'}

# Test with filters
rv = self.client.get('/dogs?owner=Jane')
rv = client.get('/dogs?owner=Jane')
assert rv.status_code == 200
assert rv.json['total'] == 1

def test_create(self):
def test_create(self, client):
""" Test creating an entity using CreateAPIResource """

# Create a dog object
rv = self.client.post('/dogs',
data=json.dumps(
dict(id=5, name='Johnny', owner='John')),
content_type='application/json')
rv = client.post('/dogs', data=json.dumps(dict(id=5, name='Johnny', owner='John')),
content_type='application/json')
assert rv.status_code == 201

expected_resp = {
Expand All @@ -92,16 +80,15 @@ def test_create(self):
dog = Dog.get(5)
dog.delete()

def test_update(self):
def test_update(self, client):
""" Test updating an entity using UpdateAPIResource """

# Create a dog object
Dog.create(id=5, name='Johnny', owner='John')

# Update the dog object
rv = self.client.put('/dogs/5',
data=json.dumps(dict(age=3)),
content_type='application/json')
rv = client.put('/dogs/5', data=json.dumps(dict(age=3)),
content_type='application/json')
assert rv.status_code == 200

expected_resp = {
Expand All @@ -118,14 +105,14 @@ def test_update(self):
dog = Dog.get(5)
dog.delete()

def test_delete(self):
def test_delete(self, client):
""" Test deleting an entity using DeleteAPIResource """

# Create a dog object
Dog.create(id=5, name='Johnny', owner='John')

# Delete the dog object
rv = self.client.delete('/dogs/5')
rv = client.delete('/dogs/5')
assert rv.status_code == 204
assert rv.data == b''

Expand Down
Loading

0 comments on commit 6a6c1f8

Please sign in to comment.