From 1e4df57ed8848f3a94c1182efef339cb0c21dc19 Mon Sep 17 00:00:00 2001 From: Jeroen Op 't Eynde Date: Thu, 18 May 2017 15:46:04 +0200 Subject: [PATCH 1/4] Modernizer compatibility Python 2 and 3 --- .coveragerc | 26 ++++++++++++++++++- locking/admin.py | 1 + locking/migrations/0001_initial.py | 1 + locking/migrations/0002_rename.py | 1 + locking/migrations/0003_optimize_queries.py | 1 + locking/migrations/0004_auto_20151115_0703.py | 1 + locking/migrations/0004_auto_20160907_0942.py | 1 + .../migrations/0005_merge_20170504_1024.py | 1 + locking/models.py | 1 + locking/tasks.py | 1 + locking/tests.py | 25 ++++++++++++------ test_project/manage.py | 1 + test_project/test_project/settings.py | 1 + test_project/test_project/urls.py | 1 + test_project/test_project/wsgi.py | 1 + tox.ini | 3 ++- 16 files changed, 57 insertions(+), 10 deletions(-) diff --git a/.coveragerc b/.coveragerc index 6e90972..decf5ef 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,26 @@ [run] -omit = setup.py +branch = True +source = locking/ +omit = + # Ignore migrations + */migrations/* + + # Ignore deployment files + deployment/* + + # Ignore manage files + manage* + + # Ignore admin files + locking/*/admin.py + + # Ignore test_project file + test_project/* +[report] +exclude_lines= + pragma: no cover + def __repr__ + def __iter__ + def __str__ + def __unicode__ +fail_under = 99 diff --git a/locking/admin.py b/locking/admin.py index 5fc2492..427ac1f 100644 --- a/locking/admin.py +++ b/locking/admin.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.contrib import admin from .models import NonBlockingLock diff --git a/locking/migrations/0001_initial.py b/locking/migrations/0001_initial.py index e8a3e8b..1d65a26 100644 --- a/locking/migrations/0001_initial.py +++ b/locking/migrations/0001_initial.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from __future__ import absolute_import from django.db import models, migrations diff --git a/locking/migrations/0002_rename.py b/locking/migrations/0002_rename.py index a157794..1318d4f 100644 --- a/locking/migrations/0002_rename.py +++ b/locking/migrations/0002_rename.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from __future__ import absolute_import from django.db import migrations diff --git a/locking/migrations/0003_optimize_queries.py b/locking/migrations/0003_optimize_queries.py index acf141e..3967877 100644 --- a/locking/migrations/0003_optimize_queries.py +++ b/locking/migrations/0003_optimize_queries.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from __future__ import absolute_import from django.db import models, migrations import datetime from django.utils.timezone import utc diff --git a/locking/migrations/0004_auto_20151115_0703.py b/locking/migrations/0004_auto_20151115_0703.py index 7ac7229..e1104ff 100644 --- a/locking/migrations/0004_auto_20151115_0703.py +++ b/locking/migrations/0004_auto_20151115_0703.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from __future__ import absolute_import from django.db import models, migrations import uuid diff --git a/locking/migrations/0004_auto_20160907_0942.py b/locking/migrations/0004_auto_20160907_0942.py index f78b342..2e5e21e 100644 --- a/locking/migrations/0004_auto_20160907_0942.py +++ b/locking/migrations/0004_auto_20160907_0942.py @@ -2,6 +2,7 @@ # Generated by Django 1.9.7 on 2016-09-07 09:42 from __future__ import unicode_literals +from __future__ import absolute_import from django.db import migrations, models diff --git a/locking/migrations/0005_merge_20170504_1024.py b/locking/migrations/0005_merge_20170504_1024.py index 370626a..f4ac9e6 100644 --- a/locking/migrations/0005_merge_20170504_1024.py +++ b/locking/migrations/0005_merge_20170504_1024.py @@ -2,6 +2,7 @@ # Generated by Django 1.11 on 2017-05-04 10:24 from __future__ import unicode_literals +from __future__ import absolute_import from django.db import migrations diff --git a/locking/models.py b/locking/models.py index 849cbce..6699145 100644 --- a/locking/models.py +++ b/locking/models.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import uuid from datetime import timedelta diff --git a/locking/tasks.py b/locking/tasks.py index 0d3ce2b..a31bf9c 100644 --- a/locking/tasks.py +++ b/locking/tasks.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from celery import shared_task from .models import NonBlockingLock diff --git a/locking/tests.py b/locking/tests.py index ea0af95..0475967 100644 --- a/locking/tests.py +++ b/locking/tests.py @@ -1,6 +1,7 @@ """ Tests for the locking application """ +from __future__ import absolute_import import uuid from freezegun import freeze_time @@ -8,7 +9,7 @@ from django.contrib.auth.models import User from django.test import TestCase -from .exceptions import AlreadyLocked, RenewalError +from .exceptions import AlreadyLocked, RenewalError, NonexistentLock, NotLocked, Expired from .models import NonBlockingLock, _get_lock_name from .tasks import clean_expired_locks @@ -23,6 +24,7 @@ def test_acquire_and_release(self): self.assertTrue(NonBlockingLock.objects.is_locked(self.user)) l.release() self.assertTrue(not NonBlockingLock.objects.is_locked(self.user)) + self.assertRaises(NotLocked, l.release, silent=False) l2 = NonBlockingLock.objects.acquire_lock(self.user) self.assertTrue(NonBlockingLock.objects.is_locked(self.user)) NonBlockingLock.objects.release_lock(l2.pk) @@ -57,6 +59,20 @@ def test_acquire_and_renew(self): self.assertEqual(l.pk, l2.pk) self.assertEqual(l.expires_on, l2.expires_on) + def test_renew_expired(self): + ''' Tests renew an expired lock ''' + with freeze_time("2015-01-01 10:00"): + l = NonBlockingLock.objects.acquire_lock(self.user, 1) + self.assertRaises(Expired, l.renew) + + def test_renew_nonexistinglock(self): + ''' Tests renewing a non existent lock ''' + self.assertRaises(NonexistentLock, NonBlockingLock.objects.renew_lock, uuid.uuid4()) + + def test_release_nonexistinglock(self): + ''' Tests release a non existent lock ''' + self.assertRaises(NotLocked, NonBlockingLock.objects.release_lock, uuid.uuid4()) + def test_lock_twice(self): ''' Tests a double locking (lock and try to lock again) ''' l = NonBlockingLock.objects.acquire_lock(self.user) @@ -85,13 +101,6 @@ def test_model(self): self.assertEquals(l.locked_object, 'test_lock') self.assertIsInstance(l.id, uuid.UUID) - def test_unicode(self): - ''' Test the __unicode__ ''' - l = NonBlockingLock.objects.acquire_lock(self.user) - str_rep = '%s' % l - self.assertNotEquals(str_rep.find('%d' % self.user.id), -1) - l.release() - def test_relock(self): '''Test to allow lock if lock is expired''' with freeze_time("2015-01-01 10:00"): diff --git a/test_project/manage.py b/test_project/manage.py index 0fc36a3..313e033 100644 --- a/test_project/manage.py +++ b/test_project/manage.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import absolute_import import os import sys diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index 4f3e64e..d22fa42 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -11,6 +11,7 @@ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) +from __future__ import absolute_import import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/test_project/test_project/urls.py b/test_project/test_project/urls.py index e49aec4..ae0d53e 100644 --- a/test_project/test_project/urls.py +++ b/test_project/test_project/urls.py @@ -13,6 +13,7 @@ 1. Add an import: from blog import urls as blog_urls 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) """ +from __future__ import absolute_import from django.conf.urls import include, url from django.contrib import admin diff --git a/test_project/test_project/wsgi.py b/test_project/test_project/wsgi.py index cb26c81..a8f6d32 100644 --- a/test_project/test_project/wsgi.py +++ b/test_project/test_project/wsgi.py @@ -7,6 +7,7 @@ https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ """ +from __future__ import absolute_import import os from django.core.wsgi import get_wsgi_application diff --git a/tox.ini b/tox.ini index 653745d..26f9dea 100644 --- a/tox.ini +++ b/tox.ini @@ -39,5 +39,6 @@ commands = [testenv:coverage] commands = - coverage run --source='{toxinidir}' {toxinidir}/test_project/manage.py test locking + coverage run --source='{toxinidir}/locking' {toxinidir}/test_project/manage.py test locking + coverage report -m coverage xml -i -o {toxinidir}/coverage.xml From 0b6d7fc8b1e558a90540ec7e9d6a8b76a535332c Mon Sep 17 00:00:00 2001 From: Jeroen Op 't Eynde Date: Thu, 18 May 2017 16:05:01 +0200 Subject: [PATCH 2/4] Use pytest and test on python3.6 and python2.7 * Add Travis-CI build script --- .flake8 | 9 ++++ .gitignore | 7 +-- .travis.yml | 19 ++++++++ README.rst | 6 +++ circle.yml | 14 ------ deployment/requirements_test.txt | 24 ++++++++-- pytest.ini | 4 ++ setup.cfg | 2 + setup.py | 30 +------------ test_project/{test_project => }/__init__.py | 0 test_project/{test_project => }/settings.py | 0 test_project/{test_project => }/urls.py | 0 test_project/{test_project => }/wsgi.py | 0 tox.ini | 49 ++++----------------- 14 files changed, 75 insertions(+), 89 deletions(-) create mode 100644 .flake8 create mode 100644 .travis.yml delete mode 100644 circle.yml create mode 100644 pytest.ini create mode 100644 setup.cfg rename test_project/{test_project => }/__init__.py (100%) rename test_project/{test_project => }/settings.py (100%) rename test_project/{test_project => }/urls.py (100%) rename test_project/{test_project => }/wsgi.py (100%) diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..1cbbf8e --- /dev/null +++ b/.flake8 @@ -0,0 +1,9 @@ +[flake8] +; We use a line length of 120 instead of pep8's default 79 +max-line-length = 120 +; Files not checked: +; - migrations: most of these are autogenerated and don't need a check +; - manage: these are autogenerated and don't need a check +exclude = *migrations,manage.py +; Cyclomatic complexity check with mccabe. +max-complexity = 20 diff --git a/.gitignore b/.gitignore index a29d86d..08b5f34 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,7 @@ *.py[co] # Packages -*.egg -*.egg-info +*.egg* dist build eggs @@ -26,4 +25,6 @@ coverage.xml #Mr Developer .mr.developer.cfg -/.eggs/ +# Virtualenvs +.venv* +.cache/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..985efc3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: python +env: + - DJANGO='>=1.8,<1.9' + - DJANGO='>=1.9,<1.10' + - DJANGO='>=1.10,<1.11' + - DJANGO='>=1.11,<1.12' +python: + - "2.7" + - "3.6" +install: + - pip install -e . + - pip install -r deployment/requirements_test.txt + - pip install "Django${DJANGO}" +before_script: + flake8 locking/ +script: + pytest -v --capture=sys --cov=locking/ locking/ --cov-report term-missing:skip-covered +after_success: + coveralls diff --git a/README.rst b/README.rst index 2c89393..7b0ca52 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,11 @@ Django-locking ============== + +.. image:: https://coveralls.io/repos/github/vikingco/django-db-locking/badge.svg?branch=py23 + :target: https://coveralls.io/github/vikingco/django-db-locking?branch=py23 +.. image:: https://travis-ci.org/vikingco/django-db-locking.svg?branch=master + :target: https://travis-ci.org/vikingco/django-db-locking + Usage ----- The simplest use is by using it as a context manager: diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 9e7c1bd..0000000 --- a/circle.yml +++ /dev/null @@ -1,14 +0,0 @@ -machine: - python: - version: 2.7.6 - -dependencies: - override: - - pip install -U tox virtualenv - -test: - override: - - tox - post: - - pip install codecov - - codecov --token=${CODECOV_TOKEN} diff --git a/deployment/requirements_test.txt b/deployment/requirements_test.txt index 312d89d..33b5a46 100644 --- a/deployment/requirements_test.txt +++ b/deployment/requirements_test.txt @@ -1,6 +1,24 @@ Django +celery + +# freeze time freezegun -tox + +# pytest +pytest +pytest-django + +# coverage +pytest-cov coverage -celery -amqp>=1.3.0,<2.1.0 +coveralls + + +# flake8 +flake8 +pep8 +pyflakes +mccabe +pep8-naming +flake8-print +flake8-debugger diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..569fb9c --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +DJANGO_SETTINGS_MODULE=test_project.settings +python_files = test*.py +norecursedirs = .* *.egg *.egg-info wheel dist build artifacts diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2a9acf1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py index 3b544b2..f1efb49 100644 --- a/setup.py +++ b/setup.py @@ -1,34 +1,7 @@ from setuptools import setup, find_packages -from setuptools.command.test import test as SetuptoolsTestCommand import locking -class RunTestsCommand(SetuptoolsTestCommand): - def initialize_options(self): - SetuptoolsTestCommand.initialize_options(self) - self.test_suite = "override" - - def finalize_options(self): - SetuptoolsTestCommand.finalize_options(self) - self.test_suite = None - - def run(self): - SetuptoolsTestCommand.run(self) - self.with_project_on_sys_path(self.run_tests) - - def run_tests(self): - import os - import subprocess - import sys - - env = os.environ.copy() - env["PYTHONPATH"] = os.pathsep.join(sys.path) - - cmd = [sys.executable, 'test_project/manage.py', 'test'] - errno = subprocess.call(cmd, env=env) - - raise SystemExit(errno) - setup( name="django-db-locking", version=locking.__version__, @@ -39,8 +12,7 @@ def run_tests(self): author='VikingCo', author_email='operations@unleashed.be', packages=find_packages('.'), - cmdclass={'test': RunTestsCommand}, - tests_require=['django', 'freezegun', 'celery'], + install_requires=['Django'], classifiers=[ 'Intended Audience :: Developers', 'Programming Language :: Python', diff --git a/test_project/test_project/__init__.py b/test_project/__init__.py similarity index 100% rename from test_project/test_project/__init__.py rename to test_project/__init__.py diff --git a/test_project/test_project/settings.py b/test_project/settings.py similarity index 100% rename from test_project/test_project/settings.py rename to test_project/settings.py diff --git a/test_project/test_project/urls.py b/test_project/urls.py similarity index 100% rename from test_project/test_project/urls.py rename to test_project/urls.py diff --git a/test_project/test_project/wsgi.py b/test_project/wsgi.py similarity index 100% rename from test_project/test_project/wsgi.py rename to test_project/wsgi.py diff --git a/tox.ini b/tox.ini index 26f9dea..149499f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,44 +1,13 @@ [tox] -envlist = - check, - coverage -skipsdist = True - -[flake8] -; We use a line length of 120 instead of pep8's default 79 -max-line-length = 120 -; Files not checked: -; - migrations: most of these are autogenerated and don't need a check -; - manage: these are autogenerated and don't need a check -exclude = *migrations,manage.py -; Cyclomatic complexity check with mccabe. -max-complexity = 20 +envlist=flake8,py27,py36,cov [testenv] -whitelist_externals = - /bin/mkdir -deps = +deps= -r{toxinidir}/deployment/requirements_test.txt -install_command = pip install --pre {opts} {packages} -recreate = True -passenv = * -commands = - python {toxinidir}/test_project/manage.py test locking - -[testenv:check] -deps = - flake8 - pep8 - pyflakes - mccabe - pep8-naming - flake8-print - flake8-debugger -commands = - flake8 locking - -[testenv:coverage] -commands = - coverage run --source='{toxinidir}/locking' {toxinidir}/test_project/manage.py test locking - coverage report -m - coverage xml -i -o {toxinidir}/coverage.xml +setenv= + py{27,36}: COVERAGE_FILE={envdir}/.coverage +commands= + flake8: flake8 locking + py{27,36}: python -m pytest --cov=locking --cov-report=term-missing --no-cov-on-fail + cov: /usr/bin/env bash -c '{envpython} -m coverage combine {toxworkdir}/py*/.coverage' + cov: coverage report -m From bf09ab4fa34f72e14f7ce026f2f36726fc1c3cdd Mon Sep 17 00:00:00 2001 From: Jeroen Op 't Eynde Date: Fri, 19 May 2017 12:01:38 +0200 Subject: [PATCH 3/4] Refactoring of requirements files --- .coveragerc | 4 +-- .travis.yml | 2 +- requirements/requirements.txt | 1 + .../requirements_test.txt | 1 - setup.py | 33 ++++++++++++++++++- tox.ini | 2 +- 6 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 requirements/requirements.txt rename {deployment => requirements}/requirements_test.txt (96%) diff --git a/.coveragerc b/.coveragerc index decf5ef..0775145 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,8 +5,8 @@ omit = # Ignore migrations */migrations/* - # Ignore deployment files - deployment/* + # Ignore requirement files + requirements/* # Ignore manage files manage* diff --git a/.travis.yml b/.travis.yml index 985efc3..48c495b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: - "3.6" install: - pip install -e . - - pip install -r deployment/requirements_test.txt + - pip install -r requirements/requirements_test.txt - pip install "Django${DJANGO}" before_script: flake8 locking/ diff --git a/requirements/requirements.txt b/requirements/requirements.txt new file mode 100644 index 0000000..94a0e83 --- /dev/null +++ b/requirements/requirements.txt @@ -0,0 +1 @@ +Django diff --git a/deployment/requirements_test.txt b/requirements/requirements_test.txt similarity index 96% rename from deployment/requirements_test.txt rename to requirements/requirements_test.txt index 33b5a46..6fa3ed7 100644 --- a/deployment/requirements_test.txt +++ b/requirements/requirements_test.txt @@ -1,4 +1,3 @@ -Django celery # freeze time diff --git a/setup.py b/setup.py index f1efb49..e424332 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,30 @@ from setuptools import setup, find_packages +from pip.req import parse_requirements +from pip.download import PipSession import locking +from os import path + + +# Lists of requirements and dependency links which are needed during runtime, testing and setup +install_requires = [] +tests_require = [] +dependency_links = [] + +# Inject requirements from requirements.txt into setup.py +requirements_file = parse_requirements(path.join('requirements', 'requirements.txt'), session=PipSession()) +for req in requirements_file: + install_requires.append(str(req.req)) + if req.link: + dependency_links.append(str(req.link)) + +# Inject test requirements from requirements_test.txt into setup.py +requirements_test_file = parse_requirements(path.join('requirements', 'requirements_test.txt'), session=PipSession()) +for req in requirements_test_file: + tests_require.append(str(req.req)) + if req.link: + dependency_links.append(str(req.link)) + setup( name="django-db-locking", @@ -12,10 +36,17 @@ author='VikingCo', author_email='operations@unleashed.be', packages=find_packages('.'), - install_requires=['Django'], + include_package_data=True, + install_requires=install_requires, + extras_require={'celery': ["celery"] }, + tests_require=tests_require, + dependency_links=dependency_links, classifiers=[ 'Intended Audience :: Developers', 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', 'Operating System :: OS Independent', 'Environment :: Web Environment', 'Framework :: Django', diff --git a/tox.ini b/tox.ini index 149499f..a0b880c 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist=flake8,py27,py36,cov [testenv] deps= - -r{toxinidir}/deployment/requirements_test.txt + -r{toxinidir}/requirements/requirements_test.txt setenv= py{27,36}: COVERAGE_FILE={envdir}/.coverage commands= From 62e1790dfb60bd754e6b4d9bd9b16cc8ec5669c2 Mon Sep 17 00:00:00 2001 From: Jeroen Op 't Eynde Date: Wed, 24 May 2017 13:16:17 +0200 Subject: [PATCH 4/4] Correct README badge and zip_false on setup.py --- README.rst | 4 ++-- setup.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 7b0ca52..cfa0917 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ Django-locking ============== -.. image:: https://coveralls.io/repos/github/vikingco/django-db-locking/badge.svg?branch=py23 - :target: https://coveralls.io/github/vikingco/django-db-locking?branch=py23 +.. image:: https://coveralls.io/repos/github/vikingco/django-db-locking/badge.svg?branch=master + :target: https://coveralls.io/github/vikingco/django-db-locking?branch=master .. image:: https://travis-ci.org/vikingco/django-db-locking.svg?branch=master :target: https://travis-ci.org/vikingco/django-db-locking diff --git a/setup.py b/setup.py index e424332..6336a3e 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ extras_require={'celery': ["celery"] }, tests_require=tests_require, dependency_links=dependency_links, + zip_safe=False, classifiers=[ 'Intended Audience :: Developers', 'Programming Language :: Python',