diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..a21e161
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,18 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+- repo: https://github.com/psf/black
+ rev: 22.1.0
+ hooks:
+ - id: black
+ args: ['--target-version', 'py37', '--skip-string-normalization', '--line-length', '110']
+- repo: https://github.com/PyCQA/isort
+ rev: 5.7.0
+ hooks:
+ - id: isort
+ args: ['--profile', 'black', '--line-length', '110']
+- repo: https://github.com/asottile/pyupgrade
+ rev: v2.20.0
+ hooks:
+ - id: pyupgrade
+ args: ['--keep-percent-format', '--py37-plus']
diff --git a/debian/debian_config.py b/debian/debian_config.py
index ca86917..115994c 100644
--- a/debian/debian_config.py
+++ b/debian/debian_config.py
@@ -1,7 +1,7 @@
# This file is sourced by "execfile" from petale.settings
-import os
import glob
+import os
from django.core.exceptions import ImproperlyConfigured
diff --git a/debian/settings.py b/debian/settings.py
index 25c4d9f..da6396d 100644
--- a/debian/settings.py
+++ b/debian/settings.py
@@ -15,15 +15,15 @@
DEBUG = False
TEMPLATE_DEBUG = False
-#ADMINS = (
+# ADMINS = (
# # ('User 1', 'watchdog@example.net'),
# # ('User 2', 'janitor@example.net'),
-#)
+# )
# ALLOWED_HOSTS must be correct in production!
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = [
- '*',
+ '*',
]
# Databases
diff --git a/petale/admin.py b/petale/admin.py
index e08efa7..a1d94dd 100644
--- a/petale/admin.py
+++ b/petale/admin.py
@@ -24,6 +24,7 @@ class PartnerAdmin(admin.ModelAdmin):
list_display = ['name', 'admin_emails']
readonly_fields = ['size']
+
admin.site.register(models.Partner, PartnerAdmin)
@@ -31,6 +32,7 @@ class CUTAdmin(admin.ModelAdmin):
search_fields = ['uuid']
list_display = ['uuid']
+
admin.site.register(models.CUT, CUTAdmin)
@@ -40,6 +42,7 @@ class PetalAdmin(admin.ModelAdmin):
readonly_fields = ['etag', 'size']
raw_id_fields = ['cut']
+
admin.site.register(models.Petal, PetalAdmin)
@@ -47,4 +50,5 @@ class AccessControlListAdmin(admin.ModelAdmin):
search_fields = ['partner__name', 'user__username']
list_display = ['partner', 'user', 'order', 'methods', 'key']
+
admin.site.register(models.AccessControlList, AccessControlListAdmin)
diff --git a/petale/api_views.py b/petale/api_views.py
index 82c9244..e5e21c3 100644
--- a/petale/api_views.py
+++ b/petale/api_views.py
@@ -14,36 +14,41 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from __future__ import unicode_literals
import logging
+
try:
from functools import reduce
except ImportError:
pass
import requests
+
try:
from time import process_time
except ImportError:
from time import clock as process_time
-from django.utils.six.moves.urllib import parse as urlparse
-
-from django.db.models.query import Q, F
-from django.http import StreamingHttpResponse, HttpResponse
-from django.conf import settings
-from django.db.transaction import atomic
-
-from rest_framework import status
-from rest_framework.views import APIView
-from rest_framework.response import Response
-
from atomicwrites import atomic_write
+from django.conf import settings
+from django.db.models.query import F, Q
+from django.db.transaction import atomic
+from django.http import HttpResponse, StreamingHttpResponse
+from django.utils.six.moves.urllib import parse as urlparse
+from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.views import APIView
-from .models import CUT, Petal, Partner, AccessControlList
-from .utils import logit, StreamingHash
-from .exceptions import (PartnerNotFound, CutNotFound, KeyNotFound, NotFound, MissingContentType,
- ConcurrentAccess, PreconditionException)
+from .exceptions import (
+ ConcurrentAccess,
+ CutNotFound,
+ KeyNotFound,
+ MissingContentType,
+ NotFound,
+ PartnerNotFound,
+ PreconditionException,
+)
+from .models import CUT, AccessControlList, Partner, Petal
+from .utils import StreamingHash, logit
def cut_exists(request, cut_uuid):
@@ -55,25 +60,26 @@ def cut_exists(request, cut_uuid):
authentic_url = getattr(settings, 'PETALE_AUTHENTIC_URL', None)
if not authentic_url:
- logger.warning(u'PETALE_AUTHENTIC SETTINGS improperly defined')
+ logger.warning('PETALE_AUTHENTIC SETTINGS improperly defined')
return False
url = urlparse.urljoin(authentic_url, 'api/users/synchronization/')
try:
- response = requests.post(url, json={"known_uuids": [cut_uuid]},
- auth=request.user.credentials, verify=False)
+ response = requests.post(
+ url, json={"known_uuids": [cut_uuid]}, auth=request.user.credentials, verify=False
+ )
response.raise_for_status()
except requests.RequestException as e:
- logger.warning(u'authentic synchro API failed: %s', e)
+ logger.warning('authentic synchro API failed: %s', e)
return False
try:
data = response.json()
except ValueError as e:
- logger.warning(u'authentic synchro API failed to decode response: %s', e)
+ logger.warning('authentic synchro API failed to decode response: %s', e)
return False
if data.get("unknown_uuids"):
- logger.warning(u'unknown uuid : %s %s', request.user.credentials[0], data)
+ logger.warning('unknown uuid : %s %s', request.user.credentials[0], data)
return False
CUT.objects.get_or_create(uuid=cut_uuid)
@@ -122,9 +128,7 @@ class PetalAPIKeysView(APIView):
if key_filter:
qs = qs.filter(key_filter)
- return Response({
- 'keys': [petal.name for petal in qs]
- })
+ return Response({'keys': [petal.name for petal in qs]})
@logit
def delete(self, request, partner_name, cut_uuid):
@@ -146,11 +150,7 @@ class PetalAPIView(APIView):
if_none_match = if_none_match and [x.strip() for x in if_none_match.split(',')]
try:
- qs = Petal.objects.filter(
- name=petal_name,
- partner_id__name=partner_name,
- cut_id__uuid=cut_uuid
- )
+ qs = Petal.objects.filter(name=petal_name, partner_id__name=partner_name, cut_id__uuid=cut_uuid)
qs = qs.select_related('partner', 'cut')
petal = qs.get()
except Petal.DoesNotExist:
@@ -193,18 +193,13 @@ class PetalAPIView(APIView):
petal = self.get_petal(partner_name, cut_uuid, petal_name)
if if_none_match:
if if_none_match == ['*'] or petal.etag in if_none_match:
- return Response(
- status=status.HTTP_304_NOT_MODIFIED,
- headers={
- 'ETag': petal.etag
- }
- )
+ return Response(status=status.HTTP_304_NOT_MODIFIED, headers={'ETag': petal.etag})
# verify file exists before creating a StreamingHttpResponse
# as StreamingHttpResponse generate its content after the Django global try/catch
try:
petal.data.open(mode='rb')
response = HttpResponse(petal.data.read(), content_type=petal.content_type)
- except IOError:
+ except OSError:
continue
finally:
petal.data.close()
@@ -227,9 +222,9 @@ class PetalAPIView(APIView):
raise MissingContentType
try:
- petal = self.get_petal(partner_name, cut_uuid, petal_name,
- if_match=if_match,
- if_none_match=if_none_match)
+ petal = self.get_petal(
+ partner_name, cut_uuid, petal_name, if_match=if_match, if_none_match=if_none_match
+ )
created = False
except PreconditionException:
raise ConcurrentAccess
@@ -244,8 +239,8 @@ class PetalAPIView(APIView):
raise CutNotFound
petal, created = Petal.objects.get_or_create(
- name=petal_name, partner=partner, cut=cut,
- defaults={'size': 0})
+ name=petal_name, partner=partner, cut=cut, defaults={'size': 0}
+ )
if not created and if_none_match:
raise ConcurrentAccess
else:
@@ -283,12 +278,7 @@ class PetalAPIView(APIView):
fd.write(block)
update_meta()
- return Response(
- {},
- status=status_code,
- headers={
- 'ETag': petal.etag
- })
+ return Response({}, status=status_code, headers={'ETag': petal.etag})
@logit
def delete(self, request, partner_name, cut_uuid, petal_name):
@@ -297,9 +287,8 @@ class PetalAPIView(APIView):
try:
petal = self.get_petal(
- partner_name, cut_uuid, petal_name,
- if_match=if_match,
- if_none_match=if_none_match)
+ partner_name, cut_uuid, petal_name, if_match=if_match, if_none_match=if_none_match
+ )
except PreconditionException:
raise ConcurrentAccess
petal.delete()
diff --git a/petale/authentication.py b/petale/authentication.py
index ab7d42f..01a09b2 100644
--- a/petale/authentication.py
+++ b/petale/authentication.py
@@ -17,52 +17,48 @@
import logging
import requests
-
-from django.utils.six.moves.urllib import parse as urlparse
-
from django.conf import settings
from django.contrib.auth.models import User
+from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
-
from rest_framework.authentication import BasicAuthentication
from rest_framework.exceptions import AuthenticationFailed
class PetalAuthentication(BasicAuthentication):
-
def authentic_proxy(self, userid, password):
'''Check userid and password with configured Authentic IdP, and verify it is an OIDC
- client.
+ client.
'''
logger = logging.getLogger(__name__)
authentic_url = getattr(settings, 'PETALE_AUTHENTIC_URL', None)
if not authentic_url:
- logger.warning(u'authentic check-password not configured')
+ logger.warning('authentic check-password not configured')
return False, ''
authentic_auth = getattr(settings, 'PETALE_AUTHENTIC_AUTH', None)
if not authentic_auth:
- logger.warning(u'authentic check-password not configured')
+ logger.warning('authentic check-password not configured')
return False, ''
url = urlparse.urljoin(authentic_url, 'api/check-password/')
try:
- response = requests.post(url, json={
- 'username': userid,
- 'password': password}, auth=authentic_auth, verify=False)
+ response = requests.post(
+ url, json={'username': userid, 'password': password}, auth=authentic_auth, verify=False
+ )
response.raise_for_status()
except requests.RequestException as e:
- logger.warning(u'authentic check-password API failed: %s', e)
+ logger.warning('authentic check-password API failed: %s', e)
return False, 'authentic is down'
try:
response = response.json()
except ValueError as e:
- logger.warning(u'authentic check-password API failed: %s, %r', e, response.content)
+ logger.warning('authentic check-password API failed: %s, %r', e, response.content)
return False, 'authentic is down'
if response.get('result') == 0:
- logger.warning(u'authentic check-password API failed')
+ logger.warning('authentic check-password API failed')
return False, response.get('errors', [''])[0]
return True, None
@@ -70,8 +66,7 @@ class PetalAuthentication(BasicAuthentication):
def authenticate_credentials(self, userid, password, request=None):
username = userid[:30]
try:
- user, auth = super(PetalAuthentication, self).authenticate_credentials(username,
- password)
+ user, auth = super().authenticate_credentials(username, password)
except AuthenticationFailed:
success, error = self.authentic_proxy(userid, password)
if not success:
diff --git a/petale/migrations/0001_initial.py b/petale/migrations/0001_initial.py
index 125cda1..6f6431a 100644
--- a/petale/migrations/0001_initial.py
+++ b/petale/migrations/0001_initial.py
@@ -1,10 +1,8 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
import django.core.validators
-import petale.models
from django.conf import settings
+from django.db import migrations, models
+
+import petale.models
class Migration(migrations.Migration):
@@ -17,25 +15,73 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='AccessControlList',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
('order', models.IntegerField(verbose_name='Order')),
- ('methods', models.CharField(default='GET,PUT,DELETE', help_text='GET, PUT, DELETE', max_length=128, verbose_name='Allowed methods')),
- ('key', models.CharField(default='*', max_length=128, verbose_name='Allowed keys', validators=[])),
+ (
+ 'methods',
+ models.CharField(
+ default='GET,PUT,DELETE',
+ help_text='GET, PUT, DELETE',
+ max_length=128,
+ verbose_name='Allowed methods',
+ ),
+ ),
+ (
+ 'key',
+ models.CharField(default='*', max_length=128, verbose_name='Allowed keys', validators=[]),
+ ),
],
),
migrations.CreateModel(
name='CUTIdentifier',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('uuid', models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$', message='Invalid uuid format')])),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
+ (
+ 'uuid',
+ models.CharField(
+ max_length=32,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[A-Za-z0-9-_]+$', message='Invalid uuid format'
+ )
+ ],
+ ),
+ ),
],
),
migrations.CreateModel(
name='Partner',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('name', models.CharField(max_length=64, verbose_name='Partner', validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$', message='Invalid name format')])),
- ('admin_emails', models.CharField(help_text='List of admin emails separated by comma', max_length=256, verbose_name='Admin emails')),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
+ (
+ 'name',
+ models.CharField(
+ max_length=64,
+ verbose_name='Partner',
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[A-Za-z0-9-_]+$', message='Invalid name format'
+ )
+ ],
+ ),
+ ),
+ (
+ 'admin_emails',
+ models.CharField(
+ help_text='List of admin emails separated by comma',
+ max_length=256,
+ verbose_name='Admin emails',
+ ),
+ ),
('hard_global_max_size', models.IntegerField(verbose_name='Hard max size')),
('soft_global_max_size', models.IntegerField(verbose_name='Soft max size')),
('hard_per_key_max_size', models.IntegerField(verbose_name='Hard max size per key')),
@@ -45,10 +91,24 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Petal',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
- ('name', models.CharField(max_length=128, verbose_name='Name', validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$', message='Invalid name format')])),
+ (
+ 'name',
+ models.CharField(
+ max_length=128,
+ verbose_name='Name',
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[A-Za-z0-9-_]+$', message='Invalid name format'
+ )
+ ],
+ ),
+ ),
('etag', models.CharField(max_length=256, verbose_name='ETag', blank=True)),
('data', models.FileField(upload_to='data', verbose_name='Data Content', blank=True)),
('content_type', models.CharField(max_length=128, verbose_name='Content type', blank=True)),
@@ -69,6 +129,6 @@ class Migration(migrations.Migration):
),
migrations.AlterUniqueTogether(
name='petal',
- unique_together=set([('name', 'partner_id', 'cut_id')]),
+ unique_together={('name', 'partner_id', 'cut_id')},
),
]
diff --git a/petale/migrations/0002_auto_20170329_1823.py b/petale/migrations/0002_auto_20170329_1823.py
index 281404f..39b4bda 100644
--- a/petale/migrations/0002_auto_20170329_1823.py
+++ b/petale/migrations/0002_auto_20170329_1823.py
@@ -1,10 +1,8 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import petale.models
-from django.conf import settings
import django.core.validators
+from django.conf import settings
+from django.db import migrations, models
+
+import petale.models
class Migration(migrations.Migration):
@@ -32,17 +30,25 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='accesscontrollist',
name='user',
- field=models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
+ field=models.ForeignKey(
+ verbose_name='User', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
+ ),
),
migrations.AlterField(
model_name='cutidentifier',
name='uuid',
- field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')]),
+ field=models.CharField(
+ max_length=32, validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')]
+ ),
),
migrations.AlterField(
model_name='partner',
name='name',
- field=models.CharField(max_length=64, verbose_name='Partner', validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')]),
+ field=models.CharField(
+ max_length=64,
+ verbose_name='Partner',
+ validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')],
+ ),
),
migrations.AlterField(
model_name='petal',
@@ -67,7 +73,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='petal',
name='name',
- field=models.CharField(max_length=128, verbose_name='Name', validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')]),
+ field=models.CharField(
+ max_length=128,
+ verbose_name='Name',
+ validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')],
+ ),
),
migrations.AlterField(
model_name='petal',
diff --git a/petale/migrations/0003_rename_model_and_fields.py b/petale/migrations/0003_rename_model_and_fields.py
index 91bb490..99ef469 100644
--- a/petale/migrations/0003_rename_model_and_fields.py
+++ b/petale/migrations/0003_rename_model_and_fields.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
from django.db import migrations
diff --git a/petale/migrations/0004_auto_20170330_0046.py b/petale/migrations/0004_auto_20170330_0046.py
index cdf4993..838d1f5 100644
--- a/petale/migrations/0004_auto_20170330_0046.py
+++ b/petale/migrations/0004_auto_20170330_0046.py
@@ -1,8 +1,5 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
import django.core.validators
+from django.db import migrations, models
class Migration(migrations.Migration):
@@ -14,7 +11,11 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='accesscontrollist',
- options={'ordering': ['partner__name', 'user__username', 'order', 'key', 'methods'], 'verbose_name': 'Access control list', 'verbose_name_plural': 'Access control lists'},
+ options={
+ 'ordering': ['partner__name', 'user__username', 'order', 'key', 'methods'],
+ 'verbose_name': 'Access control list',
+ 'verbose_name_plural': 'Access control lists',
+ },
),
migrations.AlterModelOptions(
name='cut',
@@ -36,7 +37,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='partner',
name='admin_emails',
- field=models.CharField(help_text='List of admin emails separated by comma', max_length=256, verbose_name='Admin emails', blank=True),
+ field=models.CharField(
+ help_text='List of admin emails separated by comma',
+ max_length=256,
+ verbose_name='Admin emails',
+ blank=True,
+ ),
),
migrations.AlterField(
model_name='partner',
@@ -51,7 +57,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='partner',
name='name',
- field=models.CharField(unique=True, max_length=64, verbose_name='Partner', validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')]),
+ field=models.CharField(
+ unique=True,
+ max_length=64,
+ verbose_name='Partner',
+ validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')],
+ ),
),
migrations.AlterField(
model_name='partner',
diff --git a/petale/migrations/0005_auto_20170721_1416.py b/petale/migrations/0005_auto_20170721_1416.py
index 8d2e582..57d8df7 100644
--- a/petale/migrations/0005_auto_20170721_1416.py
+++ b/petale/migrations/0005_auto_20170721_1416.py
@@ -1,9 +1,7 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import petale.models
import django.core.validators
+from django.db import migrations, models
+
+import petale.models
class Migration(migrations.Migration):
@@ -16,11 +14,15 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cut',
name='uuid',
- field=models.CharField(max_length=255, validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')]),
+ field=models.CharField(
+ max_length=255, validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')]
+ ),
),
migrations.AlterField(
model_name='petal',
name='data',
- field=models.FileField(upload_to=petale.models.petal_directory, max_length=512, verbose_name='Data Content'),
+ field=models.FileField(
+ upload_to=petale.models.petal_directory, max_length=512, verbose_name='Data Content'
+ ),
),
]
diff --git a/petale/migrations/0006_auto_20171017_1625.py b/petale/migrations/0006_auto_20171017_1625.py
index abb8e73..9182cb6 100644
--- a/petale/migrations/0006_auto_20171017_1625.py
+++ b/petale/migrations/0006_auto_20171017_1625.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
from django.db import migrations, models
diff --git a/petale/migrations/0007_auto_20180110_1126.py b/petale/migrations/0007_auto_20180110_1126.py
index f85fcff..aa440f9 100644
--- a/petale/migrations/0007_auto_20180110_1126.py
+++ b/petale/migrations/0007_auto_20180110_1126.py
@@ -1,8 +1,5 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
import django.core.validators
+from django.db import migrations, models
class Migration(migrations.Migration):
@@ -15,6 +12,10 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cut',
name='uuid',
- field=models.CharField(unique=True, max_length=255, validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')]),
+ field=models.CharField(
+ unique=True,
+ max_length=255,
+ validators=[django.core.validators.RegexValidator('^[A-Za-z0-9-_]+$')],
+ ),
),
]
diff --git a/petale/models.py b/petale/models.py
index 6c4af68..0266b73 100644
--- a/petale/models.py
+++ b/petale/models.py
@@ -14,53 +14,39 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from __future__ import unicode_literals
import hashlib
-from django.utils import six
-from django.db import models
from django.contrib.auth.models import User
-from django.utils.translation import ugettext_lazy as _
-from django.core.validators import RegexValidator
from django.core.mail import send_mail
-
-from .exceptions import GlobalSpaceExhausted, PetalSizeExhausted
+from django.core.validators import RegexValidator
+from django.db import models
+from django.utils import six
+from django.utils.translation import ugettext_lazy as _
from . import utils
-
+from .exceptions import GlobalSpaceExhausted, PetalSizeExhausted
id_validator = RegexValidator('^[A-Za-z0-9-_]+$')
-@six.python_2_unicode_compatible
class Partner(models.Model):
- name = models.CharField(
- verbose_name=_('Partner'),
- max_length=64,
- unique=True,
- validators=[id_validator])
+ name = models.CharField(verbose_name=_('Partner'), max_length=64, unique=True, validators=[id_validator])
admin_emails = models.CharField(
verbose_name=_('Admin emails'),
max_length=256,
blank=True,
- help_text=_('List of admin emails separated by comma'))
- hard_global_max_size = models.IntegerField(
- verbose_name=_('Hard max size'),
- help_text=_('as kilobytes'))
- soft_global_max_size = models.IntegerField(
- verbose_name=_('Soft max size'),
- help_text=_('as kilobytes'))
+ help_text=_('List of admin emails separated by comma'),
+ )
+ hard_global_max_size = models.IntegerField(verbose_name=_('Hard max size'), help_text=_('as kilobytes'))
+ soft_global_max_size = models.IntegerField(verbose_name=_('Soft max size'), help_text=_('as kilobytes'))
hard_per_key_max_size = models.IntegerField(
- verbose_name=_('Hard max size per key'),
- help_text=_('as kilobytes'))
+ verbose_name=_('Hard max size per key'), help_text=_('as kilobytes')
+ )
soft_per_key_max_size = models.IntegerField(
- verbose_name=_('Soft max size per key'),
- help_text=_('as kilobytes'))
- size = models.BigIntegerField(
- verbose_name=_('Size'),
- default=0,
- help_text=_('as bytes'))
+ verbose_name=_('Soft max size per key'), help_text=_('as kilobytes')
+ )
+ size = models.BigIntegerField(verbose_name=_('Size'), default=0, help_text=_('as bytes'))
def __str__(self):
return self.name
@@ -71,15 +57,15 @@ class Partner(models.Model):
if new_size > self.hard_global_max_size * 1024:
raise GlobalSpaceExhausted
- if (self.size < self.soft_global_max_size * 1024
- and new_size > self.soft_global_max_size * 1024):
+ if self.size < self.soft_global_max_size * 1024 and new_size > self.soft_global_max_size * 1024:
self.notify_admins(
subject=_('Partner %s space almost exhausted') % self.name,
# pylint: disable=no-member
body=_('Current size: {current_size}, Max size: {max_size}').format(
- current_size=new_size,
- max_size=self.hard_global_max_size * 1024),
- **kwargs)
+ current_size=new_size, max_size=self.hard_global_max_size * 1024
+ ),
+ **kwargs,
+ )
def notify_admins(self, subject, body, **kwargs):
if kwargs:
@@ -94,12 +80,8 @@ class Partner(models.Model):
ordering = ['name']
-@six.python_2_unicode_compatible
class CUT(models.Model):
- uuid = models.CharField(
- max_length=255,
- validators=[id_validator],
- unique=True)
+ uuid = models.CharField(max_length=255, validators=[id_validator], unique=True)
def __str__(self):
return self.uuid
@@ -116,44 +98,27 @@ def petal_directory(instance, filename):
assert instance.cut
assert instance.cut.uuid
- return 'data/{0}/{1}/{2}/{3}'.format(
+ return 'data/{}/{}/{}/{}'.format(
instance.partner.name,
hashlib.md5(instance.cut.uuid.encode('ascii')).hexdigest()[:3],
instance.cut.uuid,
- instance.name)
+ instance.name,
+ )
-@six.python_2_unicode_compatible
class Petal(models.Model):
- created_at = models.DateTimeField(
- _('Created'),
- auto_now_add=True)
- updated_at = models.DateTimeField(
- _('Updated'),
- auto_now=True)
- name = models.CharField(
- _('Name'),
- max_length=128,
- validators=[id_validator])
- etag = models.CharField(
- _('ETag'),
- max_length=256)
- data = models.FileField(
- _('Data Content'),
- max_length=512,
- upload_to=petal_directory)
- content_type = models.CharField(
- _('Content type'),
- max_length=128)
- size = models.IntegerField(
- _('Size'),
- default=0,
- help_text=_('as bytes'))
+ created_at = models.DateTimeField(_('Created'), auto_now_add=True)
+ updated_at = models.DateTimeField(_('Updated'), auto_now=True)
+ name = models.CharField(_('Name'), max_length=128, validators=[id_validator])
+ etag = models.CharField(_('ETag'), max_length=256)
+ data = models.FileField(_('Data Content'), max_length=512, upload_to=petal_directory)
+ content_type = models.CharField(_('Content type'), max_length=128)
+ size = models.IntegerField(_('Size'), default=0, help_text=_('as bytes'))
cut = models.ForeignKey(CUT, on_delete=models.CASCADE)
partner = models.ForeignKey(Partner, on_delete=models.CASCADE)
def __str__(self):
- return u'%s/%s/%s' % (self.partner.name, self.cut.uuid, self.name)
+ return '%s/%s/%s' % (self.partner.name, self.cut.uuid, self.name)
def clean(self):
if self.data:
@@ -164,60 +129,46 @@ class Petal(models.Model):
'''Delegate global limits check to partner, and check per key size limits'''
size_delta = content_length - self.size
- self.partner.check_limits(size_delta,
- partner=self.partner.name,
- cut=self.cut.uuid,
- key=self.name)
+ self.partner.check_limits(size_delta, partner=self.partner.name, cut=self.cut.uuid, key=self.name)
if content_length > self.partner.hard_per_key_max_size * 1024:
raise PetalSizeExhausted
- if (self.size <= self.partner.soft_per_key_max_size * 1024
- and content_length > self.partner.soft_per_key_max_size * 1024):
+ if (
+ self.size <= self.partner.soft_per_key_max_size * 1024
+ and content_length > self.partner.soft_per_key_max_size * 1024
+ ):
self.partner.notify_admins(
# pylint: disable=no-member
_('Key {key} space of partner {partner} almost exhausted').format(
- key=self.name,
- partner=self.partner.name),
+ key=self.name, partner=self.partner.name
+ ),
# pylint: disable=no-member
_('Current size: {current_size}, Max size: {max_size}').format(
- current_size=content_length,
- max_size=self.partner.hard_per_key_max_size * 1024),
+ current_size=content_length, max_size=self.partner.hard_per_key_max_size * 1024
+ ),
partner=self.partner.name,
cut=self.cut.uuid,
- key=self.name)
+ key=self.name,
+ )
class Meta:
- unique_together = (('name', 'partner', 'cut'))
+ unique_together = ('name', 'partner', 'cut')
verbose_name = _('Petal')
verbose_name_plural = _('Petals')
-@six.python_2_unicode_compatible
class AccessControlList(models.Model):
- order = models.IntegerField(
- _('Order'))
- partner = models.ForeignKey(
- Partner,
- verbose_name=_('Partner'),
- on_delete=models.CASCADE)
- user = models.ForeignKey(
- User,
- verbose_name=_('User'),
- on_delete=models.CASCADE)
+ order = models.IntegerField(_('Order'))
+ partner = models.ForeignKey(Partner, verbose_name=_('Partner'), on_delete=models.CASCADE)
+ user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE)
methods = models.CharField(
- _('Allowed methods'),
- max_length=128,
- default='GET,PUT,DELETE',
- help_text=("GET, PUT, DELETE"))
- key = models.CharField(
- _('Allowed keys'),
- max_length=128,
- default='*')
+ _('Allowed methods'), max_length=128, default='GET,PUT,DELETE', help_text=("GET, PUT, DELETE")
+ )
+ key = models.CharField(_('Allowed keys'), max_length=128, default='*')
def __str__(self):
- return u'%s %s %s %s' % (
- self.partner.name, self.user.username, self.methods, self.key)
+ return '%s %s %s %s' % (self.partner.name, self.user.username, self.methods, self.key)
class Meta:
verbose_name = _('Access control list')
diff --git a/petale/permissions.py b/petale/permissions.py
index 4e59e61..1ff6628 100644
--- a/petale/permissions.py
+++ b/petale/permissions.py
@@ -16,8 +16,8 @@
from rest_framework.permissions import BasePermission
-from .models import AccessControlList
from .exceptions import AccessForbidden
+from .models import AccessControlList
class PetaleAccessPermission(BasePermission):
@@ -26,9 +26,8 @@ class PetaleAccessPermission(BasePermission):
petal_name = view.kwargs.get('petal_name')
qs = AccessControlList.objects.filter(
- partner__name=partner_name,
- user=request.user,
- methods__contains=request.method)
+ partner__name=partner_name, user=request.user, methods__contains=request.method
+ )
if not qs.exists():
raise AccessForbidden
diff --git a/petale/settings.py b/petale/settings.py
index a0e6875..33a5108 100644
--- a/petale/settings.py
+++ b/petale/settings.py
@@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
+
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@@ -37,7 +38,7 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
- 'petale'
+ 'petale',
)
MIDDLEWARE = [
@@ -82,8 +83,7 @@ USE_TZ = True
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [
- ],
+ 'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -108,13 +108,11 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'static')
REST_FRAMEWORK = {
- 'DEFAULT_AUTHENTICATION_CLASSES': (
- 'petale.authentication.PetalAuthentication',
- ),
+ 'DEFAULT_AUTHENTICATION_CLASSES': ('petale.authentication.PetalAuthentication',),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'petale.permissions.PetaleAccessPermission',
- )
+ ),
}
@@ -147,14 +145,13 @@ LOGGING = {
'handlers': ['syslog'],
'level': 'DEBUG',
'propagate': True,
- }
- }
+ },
+ },
}
local_settings_file = os.environ.get(
- 'PETALE_SETTINGS_FILE',
- os.path.join(os.path.dirname(__file__), 'local_settings.py'))
+ 'PETALE_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
+)
if os.path.exists(local_settings_file):
with open(local_settings_file) as fd:
exec(fd.read())
-
diff --git a/petale/urls.py b/petale/urls.py
index 26af7b3..ba29b50 100644
--- a/petale/urls.py
+++ b/petale/urls.py
@@ -17,14 +17,18 @@
from django.conf.urls import include, url
from django.contrib import admin
-from .api_views import PetalAPIView, PetalAPIKeysView
-
+from .api_views import PetalAPIKeysView, PetalAPIView
urlpatterns = [
url(r'^admin/', admin.site.urls),
- url(r'^api/(?P[\w,-]+)/(?P[\w,-]{,255})/$',
+ url(
+ r'^api/(?P[\w,-]+)/(?P[\w,-]{,255})/$',
PetalAPIKeysView.as_view(),
- name='api-keys'),
- url(r'^api/(?P[\w,-]+)/(?P[\w,-]{,255})/(?P[\w,-]+)/$',
- PetalAPIView.as_view(), name='api')
+ name='api-keys',
+ ),
+ url(
+ r'^api/(?P[\w,-]+)/(?P[\w,-]{,255})/(?P[\w,-]+)/$',
+ PetalAPIView.as_view(),
+ name='api',
+ ),
]
diff --git a/petale/utils.py b/petale/utils.py
index d80a75e..bd2bff4 100644
--- a/petale/utils.py
+++ b/petale/utils.py
@@ -18,7 +18,6 @@ import hashlib
import logging
from functools import wraps
-
DEFAULT_HASH_ALGO = 'sha1'
@@ -28,19 +27,20 @@ def logit(func):
logger = logging.getLogger('petale')
req_url = '%s %s %s' % (request.method, request.path, request.GET.urlencode())
logger.info(req_url, extra={'request_url': req_url})
- req_headers = ''.join(
- ['%s: %s | ' % (k, v) for k, v in request.META.items() if k.isupper()])
+ req_headers = ''.join(['%s: %s | ' % (k, v) for k, v in request.META.items() if k.isupper()])
logger.debug('Request Headers: %s', req_headers, extra={'request_headers': req_headers})
response = func(self, request, *args, **kwargs)
resp_headers = ''.join(['%s: %s | ' % (k, v) for k, v in response.items()])
- logger.debug('Response Headers: %s', resp_headers,
- extra={'response_headers': resp_headers})
+ logger.debug('Response Headers: %s', resp_headers, extra={'response_headers': resp_headers})
if hasattr(response, 'data'):
- logger.debug('Response Data: %r', response.data,
- extra={'response_body': response.data})
- logger.debug('Response Status Code: %s', response.status_code,
- extra={'response_status_code': response.status_code})
+ logger.debug('Response Data: %r', response.data, extra={'response_body': response.data})
+ logger.debug(
+ 'Response Status Code: %s',
+ response.status_code,
+ extra={'response_status_code': response.status_code},
+ )
return response
+
return wrapper
@@ -57,7 +57,7 @@ def etag(stream):
return '"%s:%s"' % (DEFAULT_HASH_ALGO, digest.hexdigest())
-class StreamingHash(object):
+class StreamingHash:
def __init__(self, readable, hash_algo=DEFAULT_HASH_ALGO):
self.readable = readable
self.hash_algo = hash_algo
diff --git a/setup.py b/setup.py
index ac7a9fc..3f9bb2c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,15 +1,14 @@
#! /usr/bin/env python
-# -*- coding: utf-8 -*-
import os
import subprocess
import sys
-
-from setuptools.command.install_lib import install_lib as _install_lib
+from distutils.cmd import Command
from distutils.command.build import build as _build
from distutils.command.sdist import sdist
-from distutils.cmd import Command
-from setuptools import setup, find_packages
+
+from setuptools import find_packages, setup
+from setuptools.command.install_lib import install_lib as _install_lib
class eo_sdist(sdist):
@@ -29,15 +28,17 @@ class eo_sdist(sdist):
def get_version():
'''Use the VERSION, if absent generates a version with git describe, if not
- tag exists, take 0.0- and add the length of the commit log.
+ tag exists, take 0.0- and add the length of the commit log.
'''
if os.path.exists('VERSION'):
- with open('VERSION', 'r') as v:
+ with open('VERSION') as v:
return v.read()
if os.path.exists('.git'):
p = subprocess.Popen(
['git', 'describe', '--dirty=.dirty', '--match=v*'],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
result = p.communicate()[0]
if p.returncode == 0:
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
@@ -48,9 +49,7 @@ def get_version():
version = result
return version
else:
- return '0.0.post%s' % len(
- subprocess.check_output(
- ['git', 'rev-list', 'HEAD']).splitlines())
+ return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
return '0.0'
@@ -76,6 +75,7 @@ class compile_translations(Command):
def run(self):
try:
from django.core.management import call_command
+
for path, dirs, files in os.walk('petale'):
if 'locale' not in dirs:
continue
@@ -94,7 +94,6 @@ class build(_build):
class install_lib(_install_lib):
-
def run(self):
self.run_command('compile_translations')
_install_lib.run(self)
diff --git a/tests/conftest.py b/tests/conftest.py
index eb53fef..ac0bcaf 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,13 +1,17 @@
-from __future__ import unicode_literals
-
-from tempfile import mkdtemp
import shutil
+from tempfile import mkdtemp
-import pytest
import django_webtest
-
-from utils import (create_partner, create_petal, create_service, create_cut,
- get_service, create_acl_record, get_tests_file_content)
+import pytest
+from utils import (
+ create_acl_record,
+ create_cut,
+ create_partner,
+ create_petal,
+ create_service,
+ get_service,
+ get_tests_file_content,
+)
@pytest.fixture
@@ -19,6 +23,7 @@ def app(request, settings):
def fin():
shutil.rmtree(settings.MEDIA_ROOT)
+
request.addfinalizer(fin)
return django_webtest.DjangoTestApp()
@@ -65,12 +70,18 @@ def acl(db, partner_southpark, partner_gotham):
create_acl_record(1, partner_southpark, get_service('family'), 'invoice', methods='GET,HEAD,PUT'),
create_acl_record(2, partner_southpark, get_service('library'), 'books', methods='GET,HEAD'),
create_acl_record(3, partner_gotham, get_service('arkham'), 'taxes*', methods='GET,HEAD,PUT,DELETE'),
- create_acl_record(4, partner_southpark, get_service('library'), 'loans', methods='GET,HEAD,PUT,DELETE'),
+ create_acl_record(
+ 4, partner_southpark, get_service('library'), 'loans', methods='GET,HEAD,PUT,DELETE'
+ ),
create_acl_record(5, partner_southpark, get_service('library'), 'favourite*', methods='GET,PUT'),
- create_acl_record(6, partner_southpark, get_service('cityhall'), 'favourite*', methods='GET,PUT,DELETE'),
+ create_acl_record(
+ 6, partner_southpark, get_service('cityhall'), 'favourite*', methods='GET,PUT,DELETE'
+ ),
create_acl_record(3, partner_southpark, get_service('library'), 'profile', methods='GET,PUT,DELETE'),
create_acl_record(1, partner_southpark, get_service('arkham'), 'ma*', methods='GET,HEAD,PUT,DELETE'),
- create_acl_record(4, partner_southpark, get_service(('a1b2' * 8)[:30]), 'profile*', methods='GET,PUT')
+ create_acl_record(
+ 4, partner_southpark, get_service(('a1b2' * 8)[:30]), 'profile*', methods='GET,PUT'
+ ),
]
diff --git a/tests/test_admin.py b/tests/test_admin.py
index 93fb540..aff74f2 100644
--- a/tests/test_admin.py
+++ b/tests/test_admin.py
@@ -1,16 +1,11 @@
-#-*- coding: utf-8 -*-
-
import pytest
@pytest.fixture
def admin(db):
from django.contrib.auth.models import User
- user = User.objects.create(
- username='admin',
- email='admin@example.com',
- is_superuser=True,
- is_staff=True)
+
+ user = User.objects.create(username='admin', email='admin@example.com', is_superuser=True, is_staff=True)
user.set_password('admin')
user.save()
return user
@@ -18,6 +13,7 @@ def admin(db):
def test_create_user(settings, app, db, admin):
from django.contrib.auth.models import User
+
settings.PETALE_CHECK_CUT_UUID = False
# Login
@@ -50,7 +46,7 @@ def test_create_user(settings, app, db, admin):
response = response.click('Accueil')
# Création de la règle de contrôle d'accès
- response = response.click(u'Règles de cont')
+ response = response.click('Règles de cont')
response = response.click('Ajouter')
response.form.set('order', '1')
response.form.select('partner', text='partenaire1')
@@ -77,7 +73,7 @@ def test_create_user(settings, app, db, admin):
response.form.set('password', 'admin')
response = response.form.submit().follow()
- response = response.click(u'Pétales')
+ response = response.click('Pétales')
row = [e.text() for e in response.pyquery('tbody tr > *').items()]
assert row[:1] + row[2:] == ['', 'partenaire1', 'cut1', 'cle1']
response = response.click(row[1])
diff --git a/tests/test_api.py b/tests/test_api.py
index 281faf0..cf78f96 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,17 +1,15 @@
-import os
import json
-from xml.etree import ElementTree as etree
+import os
from multiprocessing.pool import ThreadPool
+from unittest import mock
+from xml.etree import ElementTree as etree
import pytest
-import mock
-
-from utils import get_tests_file_content, FakedResponse
-
from django.utils.encoding import force_text
+from utils import FakedResponse, get_tests_file_content
-from petale.utils import etag
from petale.models import CUT, Petal
+from petale.utils import etag
pytestmark = pytest.mark.django_db
@@ -44,8 +42,7 @@ def test_resource_not_found(app, partner_southpark, cut_kevin_uuid, acl):
app.head(url, status=404)
-def test_access_control_list(app, partner_southpark, cut_kevin_uuid,
- petal_books, petal_invoice, acl):
+def test_access_control_list(app, partner_southpark, cut_kevin_uuid, petal_books, petal_invoice, acl):
app.authorization = ('Basic', ('arkham', 'arkham'))
# test permission on requested partner
@@ -79,16 +76,18 @@ def test_simple_api(app, partner_southpark, cut_kevin_uuid, acl, petal_invoice):
# test create key without content-type
resp = app.put(
- url, params=json.dumps(payload),
+ url,
+ params=json.dumps(payload),
headers={
'If-None-Match': '*',
'Content-Type': '',
- }, status=400)
+ },
+ status=400,
+ )
assert resp.json['error'] == 'missing-content-type'
# test create key with cut uuid length over 32
- app.put_json('/api/southaprk/%s12/whatever/' % cut_kevin_uuid,
- params=json.dumps(payload), status=404)
+ app.put_json('/api/southaprk/%s12/whatever/' % cut_kevin_uuid, params=json.dumps(payload), status=404)
# test create key
resp = app.put_json(url, params=payload, headers={'If-None-Match': '*'}, status=201)
@@ -115,7 +114,7 @@ def test_simple_api(app, partner_southpark, cut_kevin_uuid, acl, petal_invoice):
resp = app.head(cut_keys_url, status=405)
resp = app.get(cut_keys_url, status=200)
- assert set(resp.json['keys']) == set(['loans'])
+ assert set(resp.json['keys']) == {'loans'}
# test get all keys and prefix
resp = app.get('%s?prefix=invoice' % cut_keys_url, status=200)
@@ -154,8 +153,12 @@ def test_binary_data(app, partner_southpark, cut_kevin_uuid, acl):
# test create xml data
url = '/api/southpark/%s/profile-friends/' % cut_kevin_uuid
content_type = 'text/xml'
- resp = app.put(url, params=get_tests_file_content('users.xml'),
- headers={'If-None-Match': '*', 'Content-Type': content_type}, status=201)
+ resp = app.put(
+ url,
+ params=get_tests_file_content('users.xml'),
+ headers={'If-None-Match': '*', 'Content-Type': content_type},
+ status=201,
+ )
resp = app.get(url)
assert resp.headers.get('Content-Type') == content_type
xml_data = etree.fromstring(resp.content)
@@ -178,8 +181,9 @@ def test_binary_data(app, partner_southpark, cut_kevin_uuid, acl):
url = '/api/southpark/%s/profile-picture/' % cut_kevin_uuid
content_type = 'application/octet-stream'
content = get_tests_file_content('fg.jpg') * 100
- resp = app.put(url, params=content,
- headers={'If-None-Match': '*', 'Content-Type': content_type}, status=201)
+ resp = app.put(
+ url, params=content, headers={'If-None-Match': '*', 'Content-Type': content_type}, status=201
+ )
resp = app.get(url)
assert resp.headers.get('Content-Type') == content_type
assert resp.content == content
@@ -187,8 +191,12 @@ def test_binary_data(app, partner_southpark, cut_kevin_uuid, acl):
# test create binary data
url = '/api/southpark/%s/profile-invoice/' % cut_kevin_uuid
content_type = 'application/pdf'
- resp = app.put(url, params=get_tests_file_content('invoice.pdf'),
- headers={'If-None-Match': '*', 'Content-Type': content_type}, status=201)
+ resp = app.put(
+ url,
+ params=get_tests_file_content('invoice.pdf'),
+ headers={'If-None-Match': '*', 'Content-Type': content_type},
+ status=201,
+ )
resp = app.get(url)
assert resp.headers.get('Content-Type') == content_type
@@ -224,7 +232,7 @@ def test_caching(app, partner_southpark, cut_kevin_uuid, acl):
etags = [
'"sha1:5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9"',
'"sha1:c75ac194bf3ed4ef3f3e14343585c2fd7ed9b06bbdfbf0eb9f817b7337b966ea" ',
- ' "sha1:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"'
+ ' "sha1:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"',
]
etags.append(cache)
resp = app.put_json(url, params=payload, headers={'If-None-Match': ','.join(etags)}, status=412)
@@ -235,11 +243,13 @@ def test_caching(app, partner_southpark, cut_kevin_uuid, acl):
app.authorization = ('Basic', ('cityhall', 'cityhall'))
resp = app.get(url, status=200)
data = json.loads(resp.text)
- data['favourites'].append({
- "name": "best books",
- "url": "https://southpark.com/library",
- "items": ["Guide To Life by E. Cartman", "Gingers Have Sools by Kyle"]
- })
+ data['favourites'].append(
+ {
+ "name": "best books",
+ "url": "https://southpark.com/library",
+ "items": ["Guide To Life by E. Cartman", "Gingers Have Sools by Kyle"],
+ }
+ )
etag = resp.headers['Etag']
resp = app.put_json(url, params=data, headers={'If-Match': etag}, status=200)
@@ -249,7 +259,7 @@ def test_caching(app, partner_southpark, cut_kevin_uuid, acl):
new_item = {
"name": "Green zones",
"url": "https://southpark.com/parks",
- "items": ["Main Street", "Emo Kids Street"]
+ "items": ["Main Street", "Emo Kids Street"],
}
payload['favourites'].append(new_item)
resp = app.put_json(url, params=payload, headers={'If-Match': cache}, status=412)
@@ -272,19 +282,23 @@ def test_partner_size_limit(app, cut_kevin_uuid, acl, petal_invoice, petal_books
# test sending data sized above per key hard max size
url = '/api/gotham/%s/taxes-fail/' % cut_kevin_uuid
- resp = app.put(url,
- params='a' * 3 * 1024, # 3ko
- headers={'If-None-Match': '*'},
- content_type='text/plain',
- status=500)
+ resp = app.put(
+ url,
+ params='a' * 3 * 1024, # 3ko
+ headers={'If-None-Match': '*'},
+ content_type='text/plain',
+ status=500,
+ )
assert resp.json['error'] == 'key-space-exhausted'
# test sending data sized within soft and hard key limit
- resp = app.put(url,
- params='a' * (1024 + 1), # 1ko + 1
- headers={'If-None-Match': '*'},
- content_type='text/plain',
- status=201)
+ resp = app.put(
+ url,
+ params='a' * (1024 + 1), # 1ko + 1
+ headers={'If-None-Match': '*'},
+ content_type='text/plain',
+ status=201,
+ )
assert len(mailoutbox) == 1
sent_mail = mailoutbox[0]
assert sent_mail.to[0] == 'b.wayne@gotham.gov'
@@ -295,11 +309,7 @@ def test_partner_size_limit(app, cut_kevin_uuid, acl, petal_invoice, petal_books
for i in range(18):
url = '/api/gotham/%s/taxes-%d/' % (cut_kevin_uuid, i)
- app.put(url,
- params='a' * 1024,
- headers={'If-None-Match': '*'},
- content_type='text/plain',
- status=201)
+ app.put(url, params='a' * 1024, headers={'If-None-Match': '*'}, content_type='text/plain', status=201)
assert len(mailoutbox) == 2
sent_mail = mailoutbox[1]
@@ -309,11 +319,13 @@ def test_partner_size_limit(app, cut_kevin_uuid, acl, petal_invoice, petal_books
assert str(1024 * 20) in sent_mail.message().as_string()
url = '/api/gotham/%s/taxes-100/' % cut_kevin_uuid
- resp = app.put(url,
- params='a' * (1024 * 2 - 1),
- headers={'If-None-Match': '*'},
- content_type='text/plain',
- status=500)
+ resp = app.put(
+ url,
+ params='a' * (1024 * 2 - 1),
+ headers={'If-None-Match': '*'},
+ content_type='text/plain',
+ status=500,
+ )
assert resp.json['error'] == 'global-space-exhausted'
@@ -349,9 +361,7 @@ def test_idp_based_partner_authentication(mocked_post, app, cut_kevin_uuid, acl)
payload = {"friends": [{"name": "Token", "age": 10}, {"name": "Kenny", "age": 10}]}
url = '/api/southpark/%s/profile-whatever/' % cut_kevin_uuid
# failure
- response = {
- "result": 0, "errors": ["Invalid username/password."]
- }
+ response = {"result": 0, "errors": ["Invalid username/password."]}
mocked_post.return_value = FakedResponse(content=json.dumps(response))
app.put_json(url, params=payload, status=401, headers={'If-None-Match': '*'})
response = {"result": 1, "errors": []}
@@ -376,15 +386,12 @@ def test_cut_uuid_idp_checking(mocked_post, settings, app, acl):
# failure with AUTHENTIC_PETALE improperly defined
settings.PETALE_AUTHENTIC_URL = ''
- resp = app.put_json('/api/southpark/%s/profile/' % cut_uuid, params=payload,
- headers=headers, status=404)
+ resp = app.put_json('/api/southpark/%s/profile/' % cut_uuid, params=payload, headers=headers, status=404)
assert resp.json["error"] == "cut-not-found"
# failure when cut uuid doesn't exist on idp
response = {"unknown_uuids": [cut_uuid], "result": 1}
- mocked_post.return_value = FakedResponse(content=json.dumps(response),
- status_code=200)
- resp = app.put_json('/api/southpark/%s/profile/' % cut_uuid, params=payload,
- headers=headers, status=404)
+ mocked_post.return_value = FakedResponse(content=json.dumps(response), status_code=200)
+ resp = app.put_json('/api/southpark/%s/profile/' % cut_uuid, params=payload, headers=headers, status=404)
assert resp.json["error"] == "cut-not-found"
resp = app.get('/api/southpark/%s/' % cut_uuid, headers=headers, status=404)
@@ -396,8 +403,7 @@ def test_cut_uuid_idp_checking(mocked_post, settings, app, acl):
# sucess
settings.PETALE_AUTHENTIC_URL = 'http://example.net/idp/'
response = {"unknown_uuids": [], "result": 1}
- mocked_post.return_value = FakedResponse(content=json.dumps(response),
- status_code=200)
+ mocked_post.return_value = FakedResponse(content=json.dumps(response), status_code=200)
resp = app.get('/api/southpark/%s/profile/' % cut_uuid, headers=headers, status=404)
assert resp.json["error"] == "key-not-found"
@@ -405,8 +411,7 @@ def test_cut_uuid_idp_checking(mocked_post, settings, app, acl):
resp = app.get('/api/southpark/%s/' % cut_uuid, headers=headers)
assert resp.json["keys"] == []
- resp = app.put_json('/api/southpark/%s/profile/' % cut_uuid, params=payload,
- headers=headers, status=201)
+ resp = app.put_json('/api/southpark/%s/profile/' % cut_uuid, params=payload, headers=headers, status=201)
assert CUT.objects.get(uuid=cut_uuid)
assert Petal.objects.get(name='profile', cut__uuid=cut_uuid, partner__name='southpark')
@@ -426,7 +431,7 @@ def test_storage_error(app, partner_southpark, cut_kevin_uuid, acl, caplog):
def test_concurrent_put(app, transactional_db, settings):
'''Test concurrent PUT to the same key'''
- from utils import create_cut, create_partner, create_service, create_acl_record
+ from utils import create_acl_record, create_cut, create_partner, create_service
uuid = create_cut('a' * 255).uuid
southpark = create_partner('southpark', hg=20240, hk=19728)
@@ -446,6 +451,7 @@ def test_concurrent_put(app, transactional_db, settings):
def f(i):
from django.db import connection
+
if i % 2 == 0:
response = app.get(url, status=200)
else:
diff --git a/tests/utils.py b/tests/utils.py
index 89a0b18..e0c26f1 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,13 +1,13 @@
-import os
import json
+import os
from io import BytesIO
+from unittest import mock
import pytest
-import mock
-
from django.contrib.auth.models import User
from django.core.files import File
-from petale.models import Partner, CUT, AccessControlList, Petal
+
+from petale.models import CUT, AccessControlList, Partner, Petal
from petale.utils import etag
pytestmark = pytest.mark.django_db
@@ -39,9 +39,13 @@ def create_partner(name, admins=None, hg=2, sg=1, hk=1, sk=1):
if not admins:
admins = 'e.cartman@southpark.com,t.blakc@southpark.com'
return Partner.objects.create(
- name=name, admin_emails=admins,
- hard_global_max_size=hg, soft_global_max_size=sg,
- hard_per_key_max_size=hk, soft_per_key_max_size=sk)
+ name=name,
+ admin_emails=admins,
+ hard_global_max_size=hg,
+ soft_global_max_size=sg,
+ hard_per_key_max_size=hk,
+ soft_per_key_max_size=sk,
+ )
def create_acl_record(order, partner, user, key, methods='*'):
@@ -57,12 +61,11 @@ def create_petal(cut_uuid, partner, name, data, content_type):
size=len(data),
etag=etag(data),
data=File(BytesIO(data), name),
- content_type=content_type
+ content_type=content_type,
)
class FakedResponse(mock.Mock):
-
def json(self):
return json.loads(self.content)
diff --git a/tox.ini b/tox.ini
index b5b2170..7500737 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,10 +2,12 @@
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/petale/
envlist =
py3-dj22-drf39
+ code-style
[tox:jenkins]
envlist =
pylint
+ code-style
py3-dj22-drf39
[testenv]
@@ -62,6 +64,13 @@ deps =
commands =
/bin/bash -c "./pylint.sh petale/"
+[testenv:code-style]
+skip_install = true
+deps =
+ pre-commit
+commands =
+ pre-commit run --all-files --show-diff-on-failure
+
[pytest]
junit_family=xunit2
filterwarnings =