diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2f0cf69 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{py,rst,ini,html}] +indent_style = space +indent_size = 4 + +[*.{css,scss,json,yml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index ac7365e..98a45ff 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,15 @@ .cache # tox -.tox +.tox* # python __pycache__/ *.py[cod] *$py.class *.egg-info/ +dist/ +build/ # pyenv .python-version diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..40eef3f --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,13 @@ +======= +Credits +======= + +Development Lead +---------------- + +* Sven Grunewaldt + +Contributors +------------ + +* Titusz Pan diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..ad3954b --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,10 @@ +.. :changelog: + +History +------- + +0.1.0 (2016-02-01) +~~~~~~~~~~~~~~~~~~ + +* First release on PyPI. +* Development of this release was kindly sponsored by `Craft AG `_. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..62a1db6 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,12 @@ +Copyright (c) 2016, Sven Grunewaldt +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of django-gcloud-storage nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..31b7fd3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include AUTHORS.rst +include HISTORY.rst +include LICENSE.txt +include README.rst +include requirements-test.txt +include tox.ini +recursive-include test_app *.html *.py +recursive-include tests *.py diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..2f13e69 --- /dev/null +++ b/README.rst @@ -0,0 +1,146 @@ +============================= +django-gcloud-storage +============================= + +.. image:: https://badge.fury.io/py/django-gcloud-storage.png + :target: https://badge.fury.io/py/django-gcloud-storage +.. image:: https://img.shields.io/pypi/l/django-gcloud-storage.svg + :target: https://pypi.python.org/pypi/django-gcloud-storage +.. image:: https://img.shields.io/pypi/pyversions/django-gcloud-storage.svg + :target: https://pypi.python.org/pypi/django-gcloud-storage + +Django storage module implementation for Google Cloud Storage using the gcloud_ +python module by Google. + +.. _gcloud: https://pypi.python.org/pypi/gcloud + +Notice: alpha release +--------------------- + +Please keep in mind that this version is not yet used in any production application +(as far as I know of) and thus is an alpha release, even though fully tested! +Any kind of feedback is greatly appreciated. + +Installation +------------ + +Install django-gcloud-storage:: + + pip install django-gcloud-storage + +Create a GCS service account JSON keyfile and a bucket for your application. +Check the documentation of gcloud-python and Google Cloud Platform for more +details: + +https://gcloud-python.readthedocs.org/en/latest/gcloud-auth.html +https://cloud.google.com/storage/docs/authentication#generating-a-private-key + +Update your Django settings and use it like any other Django storage module:: + + DEFAULT_FILE_STORAGE = 'django_gcloud_storage.DjangoGCloudStorage' + + GCS_PROJECT = "django-gcloud-storage" + GCS_BUCKET = "django-gcloud-storage-bucket" + GCS_CREDENTIALS_FILE_PATH = "/path/to/gcs-credentials.json" + +Features +-------- + +* Fully tested on Python 2.7, 3.3 - 3.5 and PyPy with Django 1.7, 1.8 and 1.9 +* Files are locally downloaded as SpooledTemporaryFile objects to avoid memory + abuse +* Changed files will automatically be reuploaded to GCS when closed + +Caveats +------- + +* Files must be fully downloaded to be accessed and fully uploaded when changed +* Everytime a file is opened via the storage module, it will be downloaded again +* (development) Most tests need access to Google Cloud Storage + +Contributing +------------ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/strayer/django-gcloud-storage/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "feature" +is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +django-gcloud-storage could always use more documentation, whether as part of the +official django-gcloud-storage docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Pull Request Guidelines +~~~~~~~~~~~~~~~~~~~~~~~ + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. The pull request should work for all supported Python and Django versions + (see above). Make sure that the tests pass. + +Running Tests +------------- + +Warning: Most of the tests require a GCS project and will do API requests that +may end up costing you money! + +You can run the test suite either in a virtualenv with py.test or with tox - both +require a valid service account JSON keyfile called `test-credentials.json` in +the project root. The GCS project name will be provided via a command argument. + +The tests will create and (hopefully) remove buckets on their own. To be safe, +check if there are any leftover buckets in your GCS project after running the +tests! + +:: + + source /bin/activate + (myenv) $ pip install -r requirements-test.txt + (myenv) $ pip install -e . + (myenv) $ py.test --gcs-project-name="project-name" + + or + + $ tox -- --gcs-project-name="project-name" + +Credits +------- + +Inspired by: + +* `django-storages`_ + +.. _`django-storages`: https://pypi.python.org/pypi/django-storages + +Tools (partly) used in rendering this package: + +* Cookiecutter_ +* `cookiecutter-djangopackage`_ + +.. _Cookiecutter: https://github.com/audreyr/cookiecutter +.. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage diff --git a/django_gcloud_storage/__init__.py b/django_gcloud_storage/__init__.py index 57ae8fa..7873978 100644 --- a/django_gcloud_storage/__init__.py +++ b/django_gcloud_storage/__init__.py @@ -18,6 +18,8 @@ from gcloud.exceptions import NotFound from gcloud.storage.bucket import Bucket +__version__ = '0.1.0' + DJANGO_17 = django.get_version().startswith('1.7.') try: diff --git a/requirements-test.txt b/requirements-test.txt index 3cb1693..5af1644 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,3 @@ --r requirements.txt pytest pytest-django +pytest-pythonpath diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0a3264c..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# django>=1.8 # specified in tox.ini -gcloud>=0.8.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ea1c8d1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,9 @@ +[bumpversion] +current_version = 0.1.0 +commit = True +tag = True + +[bumpversion:file:django_gcloud_storage/__init__.py] + +[wheel] +universal = 1 diff --git a/setup.py b/setup.py index e8dea1a..02db5bc 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,65 @@ -import setuptools +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import re +import sys -setuptools.setup(name='django-gcloud-storage', version='1.0') +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + + +def get_version(*file_paths): + filename = os.path.join(os.path.dirname(__file__), *file_paths) + with open(filename) as f: + version_file = f.read() + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError('Unable to find version string.') + +version = get_version('django_gcloud_storage', '__init__.py') + +with open('README.rst') as f: + readme = f.read() +with open('HISTORY.rst') as f: + history = f.read().replace('.. :changelog:', '') + +setup( + name='django-gcloud-storage', + version=version, + description="""Django storage module implementation for Google Cloud Storage""", + long_description=readme + '\n\n' + history, + author='Sven Grunewaldt', + author_email='strayer@olle-orks.org', + url='https://github.com/strayer/django-gcloud-storage', + packages=[ + 'django_gcloud_storage', + ], + include_package_data=True, + install_requires=[ + "gcloud>=0.8.0", + "django>=1.7" + ], + license="BSD", + zip_safe=False, + keywords='django-gcloud-storage gcloud gcs', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Framework :: Django', + 'Framework :: Django :: 1.7', + 'Framework :: Django :: 1.8', + 'Framework :: Django :: 1.9', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ], +) diff --git a/test_app/app/settings.py b/test_app/app/settings.py index 445f40b..6e51585 100644 --- a/test_app/app/settings.py +++ b/test_app/app/settings.py @@ -13,12 +13,8 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os -import sys - BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -#sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ @@ -71,7 +67,7 @@ }, ] -WSGI_APPLICATION = 'test_app.app.wsgi.application' +WSGI_APPLICATION = 'tests.test_app.app.wsgi.application' # Database diff --git a/test_app/app/wsgi.py b/test_app/app/wsgi.py index fe47479..2525a4d 100644 --- a/test_app/app/wsgi.py +++ b/test_app/app/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.app.settings") application = get_wsgi_application() diff --git a/test_app/manage.py b/test_app/manage.py index 7223825..1a837ba 100755 --- a/test_app/manage.py +++ b/test_app/manage.py @@ -3,7 +3,7 @@ import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.app.settings") from django.core.management import execute_from_command_line diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_class.py b/tests/test_class.py index 8d9d0db..ad9f0d6 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals import datetime -import os import ssl from tempfile import TemporaryFile @@ -14,8 +13,6 @@ from django_gcloud_storage import safe_join, remove_prefix, GCloudFile -TESTS_DIR = os.path.dirname(os.path.realpath(__file__)) - def urlopen(*args, **kwargs): try: diff --git a/tests/test_django.py b/tests/test_django.py index b53b90a..72c763a 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -2,14 +2,13 @@ from __future__ import unicode_literals import contextlib +import os +import shutil import tempfile import pytest -import shutil - from django.core.files import File from django.utils.crypto import get_random_string -import os from test_app.app.models import ModelWithFileField diff --git a/tox.ini b/tox.ini index a0333b3..d27b907 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,6 @@ envlist = {py27,py34,pypy}-{django17,django18,django19}, py35-{django18,django19}, py33-{django17,django18} -skipsdist = True [testenv] deps = -rrequirements-test.txt @@ -11,7 +10,8 @@ deps = django18: django<1.9 django19: django<1.10 commands = py.test {posargs} -usedevelop = True [pytest] DJANGO_SETTINGS_MODULE = test_app.app.settings -#django_find_project = false +django_find_project = false +python_paths = + test_app