Skip to content

Commit 8c88be8

Browse files
authored
Testing and Py3 compatibility changes (#5)
* Made Py3 compatible * Added unit tests using pytest * Travis CI, versioneye, codecov, and tox integrations. * Pointed to new `edx/pyfilesystem` dependency (needed to support Py3) * Updated license to Apache 2.0
1 parent cc88f19 commit 8c88be8

18 files changed

+750
-712
lines changed

.coveragerc

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[run]
2+
branch = True
3+
source = djpyfs
4+
data_file = .coverage
5+
omit = djpyfs/tests.py
6+
7+
[report]
8+
exclude_lines =
9+
pragma: no cover
10+
raise NotImplementedError

.gitignore

+9-1
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,12 @@ coverage.xml
5353
docs/_build/
5454

5555
# Emacs backups
56-
*~
56+
*~
57+
58+
# PyCharm config
59+
.idea/
60+
61+
# Paths generated in testing
62+
django-pyfs/
63+
example/db.sql
64+
example/sample/static/

.travis.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
language: python
2+
3+
python:
4+
- 2.7
5+
- 3.5
6+
7+
env:
8+
- TOXENV=django18
9+
- TOXENV=django19
10+
- TOXENV=django110
11+
12+
sudo: false
13+
14+
branches:
15+
only:
16+
- master
17+
18+
install:
19+
- pip install -r requirements/test_requirements.txt
20+
21+
script:
22+
- tox
23+
24+
after_success:
25+
- bash <(curl -s https://codecov.io/bash)

LICENSE

+176-660
Large diffs are not rendered by default.

conftest.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from django.conf import settings
2+
3+
4+
def pytest_configure():
5+
settings.configure(
6+
DEBUG=True,
7+
USE_TZ=True,
8+
DATABASES={
9+
"default": {
10+
"ENGINE": "django.db.backends.sqlite3",
11+
}
12+
},
13+
ROOT_URLCONF="test_urls",
14+
INSTALLED_APPS=[
15+
"django.contrib.auth",
16+
"django.contrib.contenttypes",
17+
"django.contrib.sites",
18+
"django.contrib.sessions",
19+
"djpyfs",
20+
],
21+
MIDDLEWARE_CLASSES=[
22+
'django.contrib.sessions.middleware.SessionMiddleware',
23+
'restrictedsessions.middleware.RestrictedSessionsMiddleware',
24+
'django.middleware.common.CommonMiddleware',
25+
'django.middleware.csrf.CsrfViewMiddleware',
26+
'django.contrib.auth.middleware.AuthenticationMiddleware',
27+
'django.contrib.messages.middleware.MessageMiddleware',
28+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
29+
],
30+
SITE_ID=1,
31+
)

djpyfs/djpyfs.py

+17-18
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,27 @@
1010
task can garbage-collect those objects.
1111
1212
'''
13+
from __future__ import absolute_import
1314

14-
import json
1515
import os
1616
import os.path
1717
import types
1818

1919
from django.conf import settings
20+
from fs.osfs import OSFS
2021

21-
from models import FSExpirations
22+
from .models import FSExpirations
2223

2324

2425
if hasattr(settings, 'DJFS'):
25-
djfs_settings = settings.DJFS
26+
djfs_settings = settings.DJFS #pragma: no cover
2627
else:
2728
djfs_settings = {'type' : 'osfs',
2829
'directory_root' : 'django-pyfs/static/django-pyfs',
2930
'url_root' : '/static/django-pyfs'}
3031

31-
if djfs_settings['type'] == 'osfs':
32-
from fs.osfs import OSFS
33-
elif djfs_settings['type'] == 's3fs':
34-
from fs.s3fs import S3FS
35-
from boto.s3.connection import S3Connection
36-
from boto.s3.key import Key
37-
key_id = djfs_settings.get('aws_access_key_id', None)
38-
key_secret = djfs_settings.get('aws_secret_access_key', None)
39-
s3conn = None
40-
else:
41-
raise AttributeError("Bad filesystem: "+str(djfs_settings['type']))
32+
s3conn = None
33+
4234

4335
def get_filesystem(namespace):
4436
''' Returns a pyfilesystem for static module storage.
@@ -56,7 +48,7 @@ def get_filesystem(namespace):
5648

5749
def expire_objects():
5850
''' Remove all obsolete objects from the file systems. Untested. '''
59-
objects = sorted(FSExpirations.expired(), key=lambda x:x.module)
51+
objects = sorted(FSExpirations.expired(), key=lambda x: x.module)
6052
fs = None
6153
module = None
6254
for o in objects:
@@ -98,12 +90,20 @@ def get_osfs(namespace):
9890
if not os.path.exists(full_path):
9991
os.makedirs(full_path)
10092
osfs = OSFS(full_path)
101-
osfs = patch_fs(osfs, namespace, lambda self, filename, timeout=0:os.path.join(djfs_settings['url_root'], namespace, filename))
93+
osfs = patch_fs(osfs, namespace, lambda self, filename, timeout=0: os.path.join(djfs_settings['url_root'], namespace, filename))
10294
return osfs
10395

10496
def get_s3fs(namespace):
10597
''' Helper method to get_filesystem for a file system on S3 '''
106-
global key_id, key_secret
98+
# Our test suite does not presume Amazon S3, and we would prefer not to have a global import so that we can run
99+
# tests without requiring boto. These will become global when and if we include S3/boto in our test suite.
100+
from fs.s3fs import S3FS
101+
from boto.s3.connection import S3Connection
102+
103+
key_id = djfs_settings.get('aws_access_key_id', None)
104+
key_secret = djfs_settings.get('aws_secret_access_key', None)
105+
s3conn = None
106+
107107
fullpath = namespace
108108
if 'prefix' in djfs_settings:
109109
fullpath = os.path.join(djfs_settings['prefix'], fullpath)
@@ -121,4 +121,3 @@ def get_s3_url(self, filename, timeout=60):
121121

122122
s3fs = patch_fs(s3fs, namespace, get_s3_url)
123123
return s3fs
124-

djpyfs/models.py

+22-23
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
1-
import django
2-
from django.db import models
3-
import datetime
1+
import os
42

3+
from django.db import models
54
from django.utils import timezone
65

7-
## Create your models here.
8-
#class StudentBookAccesses(models.Model):
9-
# username = models.CharField(max_length=500, unique=True) # TODO: Should not have max_length
10-
# count = models.IntegerField()
116

127
class FSExpirations(models.Model):
13-
''' The modules have access to a pyfilesystem object where they
8+
'''
9+
Model to handle expiring temporary files.
10+
11+
The modules have access to a pyfilesystem object where they
1412
can store big data, images, etc. In most cases, we would like
1513
those objects to expire (e.g. if a view generates a .PNG analytic
1614
to show to a user). This model keeps track of files stored, as
17-
well as the expirations of those models.
15+
well as the expirations of those models.
1816
'''
17+
module = models.CharField(max_length=382) # Defines the namespace
18+
filename = models.CharField(max_length=382) # Filename within the namespace
19+
expires = models.BooleanField() # Does it expire?
20+
expiration = models.DateTimeField(db_index=True)
1921

2022
@classmethod
2123
def create_expiration(cls, module, filename, seconds, days=0, expires = True):
22-
''' May be used instead of the constructor to create a new expiration.
24+
'''
25+
May be used instead of the constructor to create a new expiration.
2326
Automatically applies timedelta and saves to DB.
2427
'''
2528
expiration_time = timezone.now() + timezone.timedelta(days, seconds)
2629

2730
# If object exists, update it
28-
objects = cls.objects.filter(module = module, filename = filename)
31+
objects = cls.objects.filter(module=module, filename=filename)
2932
if objects:
3033
exp = objects[0]
3134
exp.expires = expires
@@ -41,30 +44,26 @@ def create_expiration(cls, module, filename, seconds, days=0, expires = True):
4144
f.expiration = expiration_time
4245
f.save()
4346

44-
45-
module = models.CharField(max_length=382) # Defines the namespace
46-
filename = models.CharField(max_length=382) # Filename within the namespace
47-
expires = models.BooleanField() # Does it expire?
48-
expiration = models.DateTimeField(db_index = True)
49-
5047
@classmethod
5148
def expired(cls):
52-
''' Returns a list of expired objects '''
49+
'''
50+
Returns a list of expired objects
51+
'''
5352

5453
expiration_lte = timezone.now()
5554
return cls.objects.filter(expires=True, expiration__lte = expiration_lte)
5655

57-
class Meta:
56+
class Meta(object):
5857
app_label = 'djpyfs'
59-
unique_together = (("module","filename"))
58+
unique_together = (("module", "filename"),)
6059
# We'd like to create an index first on expiration than on expires (so we can
6160
# search for objects where expires=True and expiration is before now).
6261
index_together = [
6362
["expiration", "expires"],
64-
]
63+
]
6564

6665
def __str__(self):
6766
if self.expires:
68-
return self.module+'/'+self.filename+" Expires "+str(self.expiration)
67+
return "{} Expires {}".format(os.path.join(self.module, self.filename), str(self.expiration))
6968
else:
70-
return self.module+'/'+self.filename+" Permanent ("+str(self.expiration)+")"
69+
return "{} Permanent ({})".format(os.path.join(self.module, self.filename), str(self.expiration))

0 commit comments

Comments
 (0)