diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index de9032d..0000000 --- a/.pylintrc +++ /dev/null @@ -1,426 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist=lxml - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS, manage.py - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call -disable=R,too-many-function-args,no-self-use,redefined-builtin,invalid-name,too-few-public-methods,broad-except,unexpected-keyword-arg,eval-used,arguments-differ,protected-access,bad-classmethod-argument,duplicate-code - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[BASIC] - -# Naming hint for argument names -argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for attribute names -attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming hint for function names -function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_,id,Base,faker,cv - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=yes - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for method names -method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Naming hint for variable names -variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1500 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=XXX,TODO - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members=fake.* - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local,Profile - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=15 - -# Maximum number of attributes for a class (see R0902). -max-attributes=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=20 - -# Maximum number of locals for function / method body -max-locals=35 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index fda96a8..40f9f91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python dist: xenial python: - - '3.6' - '3.7' install: - pip install -r requirements/test.txt diff --git a/MANIFEST.in b/MANIFEST.in index 1ee26d5..385ec10 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,6 @@ include .bumpversion.cfg include .coveragerc include .cookiecutterrc include .editorconfig -include .pylintrc include AUTHORS.rst include CHANGELOG.rst diff --git a/README.rst b/README.rst index 40cf066..c513a00 100644 --- a/README.rst +++ b/README.rst @@ -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:: diff --git a/requirements.txt b/requirements.txt index 8057607..5eaadb8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/requirements/dev.txt b/requirements/dev.txt index 48e722f..11c8378 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -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 diff --git a/requirements/prod.txt b/requirements/prod.txt new file mode 100644 index 0000000..c1cd30f --- /dev/null +++ b/requirements/prod.txt @@ -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 \ No newline at end of file diff --git a/requirements/test.txt b/requirements/test.txt index 81068cc..c694e2d 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -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 diff --git a/setup.cfg b/setup.cfg index d0ed737..04d6064 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,10 @@ [bdist_wheel] universal = 1 - [flake8] max-line-length = 140 exclude = */migrations/* +extend-ignore = E731 [tool:pytest] testpaths = tests diff --git a/setup.py b/setup.py index b4e308f..13bfa74 100644 --- a/setup.py +++ b/setup.py @@ -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', @@ -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'], diff --git a/src/protean_flask/core/base.py b/src/protean_flask/core/base.py index 57ae85a..cce72b6 100644 --- a/src/protean_flask/core/base.py +++ b/src/protean_flask/core/base.py @@ -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 @@ -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): diff --git a/src/protean_flask/core/serializers.py b/src/protean_flask/core/serializers.py index b31338c..8547ca3 100644 --- a/src/protean_flask/core/serializers.py +++ b/src/protean_flask/core/serializers.py @@ -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: diff --git a/src/protean_flask/core/views.py b/src/protean_flask/core/views.py index af0e428..5cb9523 100644 --- a/src/protean_flask/core/views.py +++ b/src/protean_flask/core/views.py @@ -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 @@ -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 diff --git a/tests/conftest.py b/tests/conftest.py index 8b1e9a6..00ea256 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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() diff --git a/tests/core/test_blueprint.py b/tests/core/test_blueprint.py index 54129c1..bd6d26f 100644 --- a/tests/core/test_blueprint.py +++ b/tests/core/test_blueprint.py @@ -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 @@ -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""" diff --git a/tests/core/test_serializer.py b/tests/core/test_serializer.py index ba7b5d3..316e34f 100644 --- a/tests/core/test_serializer.py +++ b/tests/core/test_serializer.py @@ -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 @@ -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 """ @@ -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) diff --git a/tests/core/test_view.py b/tests/core/test_view.py index 09b851d..8e5003b 100644 --- a/tests/core/test_view.py +++ b/tests/core/test_view.py @@ -4,7 +4,6 @@ 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 @@ -12,28 +11,19 @@ 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 = { @@ -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') @@ -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 = { @@ -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 = { @@ -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'' diff --git a/tests/core/test_viewset.py b/tests/core/test_viewset.py index c10ab3f..d262493 100644 --- a/tests/core/test_viewset.py +++ b/tests/core/test_viewset.py @@ -1,7 +1,7 @@ """Module to test Viewset functionality and features""" +import pytest import json -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 @@ -10,27 +10,18 @@ class TestGenericAPIResourceSet: """Class to test GenericAPIResourceSet 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.Human.delete_all() - - def test_set_show(self): + def test_set_show(self, client): """ Test retrieving an entity using the resource set""" # Create a human object Human.create(id=1, name='John') # Fetch this human by ID - rv = self.client.get('/humans/1') + rv = client.get('/humans/1') assert rv.status_code == 200 expected_resp = { 'human': {'contact': None, 'id': 1, 'name': 'John'} @@ -41,26 +32,24 @@ def test_set_show(self): human = Human.get(1) human.delete() - def test_set_list(self): + def test_set_list(self, client): """ Test listing an entity using the resource set""" # Create Human objects Human.create(id=2, name='Jane') Human.create(id=3, name='Mary') # Get the list of humans - rv = self.client.get('/humans?order_by[]=id') + rv = client.get('/humans?order_by[]=id') assert rv.status_code == 200 assert rv.json['total'] == 2 assert rv.json['humans'][0] == {'id': 2, 'name': 'Jane', 'contact': None} - def test_set_create(self): + def test_set_create(self, client): """ Test creating an entity using the resource set """ # Create a human object - rv = self.client.post('/humans', - data=json.dumps( - dict(id=1, name='John')), - content_type='application/json') + rv = client.post('/humans', data=json.dumps(dict(id=1, name='John')), + content_type='application/json') assert rv.status_code == 201 expected_resp = { @@ -72,16 +61,15 @@ def test_set_create(self): human = Human.get(1) human.delete() - def test_set_update(self): + def test_set_update(self, client): """ Test updating an entity using the resource set """ # Create a human object Human.create(id=1, name='John') # Update the human object - rv = self.client.put('/humans/1', - data=json.dumps(dict(contact='9000900090')), - content_type='application/json') + rv = client.put('/humans/1', data=json.dumps(dict(contact='9000900090')), + content_type='application/json') assert rv.status_code == 200 expected_resp = { @@ -93,18 +81,18 @@ def test_set_update(self): human = Human.get(1) human.delete() - def test_set_delete(self): + def test_set_delete(self, client): """ Test deleting an entity using the resource set """ # Create a human object Human.create(id=1, name='John') # Delete the dog object - rv = self.client.delete('/humans/1') + rv = client.delete('/humans/1') assert rv.status_code == 204 assert rv.data == b'' - def test_custom_route(self): + def test_custom_route(self, client): """ Test custom routes using the resource set """ # Create a human object @@ -114,7 +102,7 @@ def test_custom_route(self): Dog.create(id=3, name='Grady', owner='Jane', age=8) # Get the custom route - rv = self.client.get('/humans/1/my_dogs') + rv = client.get('/humans/1/my_dogs') assert rv.status_code == 200 assert rv.json['total'] == 2 assert rv.json['dogs'][0] == {'age': 3, 'id': 2, 'name': 'Mary', diff --git a/tests/support/sample_app/blueprint.py b/tests/support/sample_app/blueprint.py index 93b0f42..b4ac7fa 100644 --- a/tests/support/sample_app/blueprint.py +++ b/tests/support/sample_app/blueprint.py @@ -5,12 +5,12 @@ from protean_flask.core.views import ShowAPIResource from protean_flask.core.viewsets import GenericAPIResourceSet -from .schemas import Dog -from .schemas import Human -from .serializers import DogSerializer -from .serializers import HumanSerializer -from .usecases import ListMyDogsRequestObject -from .usecases import ListMyDogsUsecase +from tests.support.sample_app.entities import Dog +from tests.support.sample_app.entities import Human +from tests.support.sample_app.serializers import DogSerializer +from tests.support.sample_app.serializers import HumanSerializer +from tests.support.sample_app.usecases import ListMyDogsRequestObject +from tests.support.sample_app.usecases import ListMyDogsUsecase blueprint = Blueprint('test_blueprint', __name__) api_bp = Protean(blueprint) diff --git a/tests/support/sample_app/schemas.py b/tests/support/sample_app/schemas.py deleted file mode 100644 index cb45aaf..0000000 --- a/tests/support/sample_app/schemas.py +++ /dev/null @@ -1,39 +0,0 @@ -""" Schemas used by the sample app""" -from protean.core.repository import repo_factory -from protean.impl.repository.dict_repo import DictModel - -from .entities import Dog -from .entities import Human -from .entities import RelatedDog - - -class DogModel(DictModel): - """ Schema for the Dog Entity""" - - class Meta: - """ Meta class for schema options""" - entity = Dog - schema_name = 'dogs' - - -class HumanModel(DictModel): - """ Schema for the Human Entity Entity""" - - class Meta: - """ Meta class for schema options""" - entity = Human - schema_name = 'humans' - - -class RelatedDogModel(DictModel): - """ Schema for the RelatedDog Entity""" - - class Meta: - """ Meta class for schema options""" - entity = RelatedDog - schema_name = 'related_dogs' - - -repo_factory.register(DogModel) -repo_factory.register(HumanModel) -repo_factory.register(RelatedDogModel) diff --git a/tests/support/sample_app/usecases.py b/tests/support/sample_app/usecases.py index d61a5d2..a9e1305 100644 --- a/tests/support/sample_app/usecases.py +++ b/tests/support/sample_app/usecases.py @@ -6,8 +6,8 @@ from protean.core.usecase import ShowRequestObject from protean.core.usecase import UseCase -from .schemas import Dog -from .schemas import Human +from .entities import Dog +from .entities import Human class ListMyDogsRequestObject(ShowRequestObject): diff --git a/tests/support/sample_app/views.py b/tests/support/sample_app/views.py index ba79068..aacfaff 100644 --- a/tests/support/sample_app/views.py +++ b/tests/support/sample_app/views.py @@ -63,10 +63,11 @@ def my_dogs(self, identifier): # Serialize the results and return the response serializer = DogSerializer(many=True) items = serializer.dump(dogs_list.items) + page = int(dogs_list.offset / dogs_list.limit) + 1 result = { 'dogs': items.data, 'total': dogs_list.total, - 'page': dogs_list.page + 'page': page } return result, 200 diff --git a/tests/support/sample_config.py b/tests/support/sample_config.py index 9a621f7..151a111 100644 --- a/tests/support/sample_config.py +++ b/tests/support/sample_config.py @@ -17,8 +17,8 @@ TESTING = True # Define the repositories -REPOSITORIES = { +DATABASES = { 'default': { - 'PROVIDER': 'protean.impl.repository.dict_repo' + 'PROVIDER': 'protean.impl.repository.dict_repo.DictProvider' } } diff --git a/tests/test_protean_flask.py b/tests/test_protean_flask.py index 3c92efe..29c8a79 100644 --- a/tests/test_protean_flask.py +++ b/tests/test_protean_flask.py @@ -26,7 +26,6 @@ def test_protean_context(): 'remote_addr': '127.0.0.1', 'tenant_id': 'localhost', 'url': 'http://localhost/current-context', - 'user_agent': 'werkzeug/0.14.1', - 'user_agent_hash': '11372f4fe6d226c4bb7fbd038c9c11d7d5ee400b847c9' - 'fb4f37fe7642b25e182' + 'user_agent': 'werkzeug/0.15.2', + 'user_agent_hash': 'f40d428dd92918eb5d1c3eaf8c83f8b18ebdbc10aa09d8b0d78deeff9b60f4bb' } diff --git a/tox.ini b/tox.ini index a291fdc..4531c1f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,13 @@ envlist = clean, check, - {py36}, + {py37}, report, docs [testenv] basepython = - {py36,docs,spell}: {env:TOXPYTHON:python3.6} + {py37,docs,spell}: {env:TOXPYTHON:python3.7} {bootstrap,clean,check,report,coveralls,codecov}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests