Skip to content

Commit

Permalink
refactor: Move fully to Python 3 (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
taion authored Oct 4, 2019
2 parents 5a309c4 + 2c25dd1 commit 279c329
Show file tree
Hide file tree
Showing 14 changed files with 61 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[flake8]
ignore = C813
ignore = C814
import-order-style = google
application-import-names = flask_annex
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,3 @@ docs/_build/

# PyBuilder
target/

# Converted README for PyPI
README.rst
20 changes: 7 additions & 13 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
sudo: false
dist: xenial

language: python
python:
- "3.7"
- "3.6"
- "pypy"

env:
- TOXENV=py-s3

matrix:
include:
- python: "3.6"
env: TOXENV=py-base
- # TODO: Remove this workaround once travis-ci/travis-ci#9815 is fixed.
sudo: true
dist: xenial
python: "3.7"
env: TOXENV=py-s3

cache:
directories:
- $HOME/.cache/pip
- env: TOXENV=lint
- env: TOXENV=py-base
- python: pypy3

cache: pip

before_install:
- pip install -U pip
Expand Down
6 changes: 3 additions & 3 deletions flask_annex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ def get_annex_class(storage):
from .s3 import S3Annex
return S3Annex
else:
raise ValueError("unsupported storage {}".format(storage))
raise ValueError(f"unsupported storage {storage}")


# -----------------------------------------------------------------------------


# We don't use the base class here, as this is just a convenience thing rather
# than an actual annex class.
class Annex(object):
class Annex:
def __new__(cls, storage, *args, **kwargs):
annex_class = get_annex_class(storage)
return annex_class(*args, **kwargs)
Expand All @@ -30,7 +30,7 @@ def from_env(namespace):

# Use storage-specific env namespace when configuring a generic annex,
# to avoid having unrecognized extra keys when changing storage.
storage_namespace = '{}_{}'.format(namespace, storage.upper())
storage_namespace = f'{namespace}_{storage.upper()}'

annex_class = get_annex_class(storage)
return annex_class.from_env(storage_namespace)
2 changes: 1 addition & 1 deletion flask_annex/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# -----------------------------------------------------------------------------


class AnnexBase(object):
class AnnexBase:
@classmethod
def from_env(cls, namespace):
return cls(**utils.get_config_from_env(namespace))
Expand Down
28 changes: 0 additions & 28 deletions flask_annex/compat.py

This file was deleted.

37 changes: 20 additions & 17 deletions flask_annex/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import flask

from .base import AnnexBase
from .compat import recursive_glob, string_types

# -----------------------------------------------------------------------------

Expand All @@ -20,10 +19,8 @@ def _get_filename(self, key):
def delete(self, key):
try:
os.unlink(self._get_filename(key))
except OSError as e:
if e.errno != errno.ENOENT:
# It's fine if the file doesn't exist.
raise # pragma: no cover
except FileNotFoundError:
pass

self._clean_empty_dirs(key)

Expand All @@ -34,11 +31,13 @@ def _clean_empty_dirs(self, key):
dir_name = self._get_filename(key_dir_name)
try:
os.rmdir(dir_name)
except FileNotFoundError:
pass
except OSError as e:
if e.errno == errno.ENOTEMPTY:
break
if e.errno != errno.ENOENT:
raise # pragma: no cover

raise # pragma: no cover

key_dir_name = os.path.dirname(key_dir_name)

Expand All @@ -49,15 +48,23 @@ def delete_many(self, keys):
def get_file(self, key, out_file):
in_filename = self._get_filename(key)

if isinstance(out_file, string_types):
if isinstance(out_file, str):
shutil.copyfile(in_filename, out_file)
else:
with open(in_filename, 'rb') as in_fp:
shutil.copyfileobj(in_fp, out_file)

def list_keys(self, prefix):
root = self._get_filename(prefix)
filenames = (root,) if os.path.isfile(root) else recursive_glob(root)
filenames = (
(root,)
if os.path.isfile(root)
else tuple(
os.path.join(root, filename)
for root, _dirnames, filenames in os.walk(root)
for filename in filenames
)
)

return tuple(
os.path.relpath(filename, self._root_path)
Expand All @@ -68,7 +75,7 @@ def save_file(self, key, in_file):
out_filename = self._get_filename(key)
self._ensure_key_dir(key)

if isinstance(in_file, string_types):
if isinstance(in_file, str):
shutil.copyfile(in_file, out_filename)
else:
with open(out_filename, 'wb') as out_fp:
Expand All @@ -81,15 +88,11 @@ def _ensure_key_dir(self, key):

# Verify that we aren't trying to create the root path.
if not os.path.exists(self._root_path):
raise IOError(
"root path {} does not exist".format(self._root_path),
raise FileNotFoundError(
f"root path {self._root_path} does not exist",
)

try:
os.makedirs(dir_name)
except OSError as e: # pragma: no cover
if e.errno != errno.EEXIST:
raise
os.makedirs(dir_name, exist_ok=True)

def send_file(self, key):
return flask.send_from_directory(
Expand Down
9 changes: 4 additions & 5 deletions flask_annex/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import flask

from .base import AnnexBase
from .compat import string_types

# -----------------------------------------------------------------------------

Expand All @@ -19,6 +18,7 @@ class S3Annex(AnnexBase):
def __init__(
self,
bucket_name,
*,
region=None,
access_key_id=None,
secret_access_key=None,
Expand All @@ -43,8 +43,7 @@ def delete_many(self, keys):
# casting to tuple because keys might be iterable
objects = tuple({'Key': key} for key in keys)
if not objects:
# this is not just an optimization, boto fails if the array
# is empty
# boto fails if the array is empty.
return

self._client.delete_objects(
Expand All @@ -53,7 +52,7 @@ def delete_many(self, keys):
)

def get_file(self, key, out_file):
if isinstance(out_file, string_types):
if isinstance(out_file, str):
self._client.download_file(self._bucket_name, key, out_file)
else:
self._client.download_fileobj(self._bucket_name, key, out_file)
Expand All @@ -77,7 +76,7 @@ def save_file(self, key, in_file):
else:
extra_args = None

if isinstance(in_file, string_types):
if isinstance(in_file, str):
self._client.upload_file(
in_file, self._bucket_name, key, extra_args,
)
Expand Down
2 changes: 1 addition & 1 deletion flask_annex/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


def get_config_from_env(namespace):
prefix = '{}_'.format(namespace)
prefix = f'{namespace}_'

return {
key[len(prefix):].lower(): value
Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
universal = 1

[metadata]
long_description = file: README.rst
long_description = file: README.md
long_description_content_type = text/markdown
20 changes: 10 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ def run(self):
author="Jimmy Jia",
author_email='tesrin@gmail.com',
license='MIT',
classifiers=(
python_requires=">=3.6",
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Framework :: Flask',
'Environment :: Web Environment',
Expand All @@ -40,27 +41,26 @@ def run(self):
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries :: Python Modules',
),
],
keywords='storage s3 flask',
packages=('flask_annex',),
install_requires=(
'Flask >= 0.10',
),
extras_require={
's3': ('boto3 >= 1.4.0',),
'tests': (
'mock',
'moto',
'pytest',
'requests',
),
'lint': ('flake8', 'flake8-config-4catalyzer'),
'tests': ('pytest', 'pytest-cov'),
'tests-s3': ('moto', 'requests'),
},
cmdclass={
'clean': system('rm -rf build dist *.egg-info'),
'package': system('python setup.py pandoc sdist bdist_wheel'),
'pandoc': system('pandoc README.md -o README.rst'),
'package': system('python setup.py sdist bdist_wheel'),
'publish': system('twine upload dist/*'),
'release': system('python setup.py clean package publish'),
'test': system('tox'),
Expand Down
4 changes: 2 additions & 2 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ def assert_key_value(annex, key, value):


def get_upload_info(client, key, **kwargs):
response = client.get('/upload_info/{}'.format(key), **kwargs)
response = client.get(f'/upload_info/{key}', **kwargs)
return json.loads(response.get_data(as_text=True))


# -----------------------------------------------------------------------------


class AbstractTestAnnex(object):
class AbstractTestAnnex:
@pytest.fixture
def annex(self, annex_base):
annex_base.save_file('foo/bar.txt', BytesIO(b'1\n'))
Expand Down
2 changes: 1 addition & 1 deletion tests/test_s3.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import base64
from io import BytesIO
import json
from unittest.mock import Mock

from mock import Mock
import pytest

from flask_annex import Annex
Expand Down
22 changes: 9 additions & 13 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
[tox]
envlist = py{36,37}-{base,s3}
envlist =
lint
py{36,37}-{base,s3}

[testenv]
usedevelop = True

deps =
flake8
flake8-config-4catalyzer
mock
pytest
pytest-cov

s3: moto
s3: requests

extras =
s3: s3
tests
s3: s3, tests-s3
commands = pytest --cov {posargs}

commands =
flake8 .
pytest --cov
[testenv:lint]
extras = lint
commands = flake8 .

0 comments on commit 279c329

Please sign in to comment.