Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ __pycache__/
# Distribution / packaging
bin/
.Python
env/
env*/
build/
develop-eggs/
dist/
Expand All @@ -27,8 +27,10 @@ var/
*.egg
MANIFEST
.eggs/
.env*/
.pyenv/
.venv/
venv*/
.venv*/

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down
4 changes: 2 additions & 2 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ defining-attr-methods=__init__,__new__,setUp
valid-classmethod-first-arg=cls

# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
valid-metaclass-classmethod-first-arg=mcs,metacls


[DESIGN]
Expand All @@ -226,7 +226,7 @@ max-branches=12
max-statements=50

# Maximum number of parents for a class (see R0901).
max-parents=7
max-parents=14

# Maximum number of attributes for a class (see R0902).
max-attributes=15
Expand Down
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ matrix:
env: TOX_ENV=py34
- python: 3.5
env: TOX_ENV=py35
- python: 3.6
env: TOX_ENV=py36
- python: pypy
env: TOX_ENV=pypy PYPY_VERSION='4.0.0'
env: TOX_ENV=pypy PYPY_VERSION='2.7-5.8.0'
- python: 2.7
env: TOX_ENV=pep8
- python: 2.7
Expand Down
12 changes: 8 additions & 4 deletions .travis/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ if [[ "$(uname -s)" == 'Darwin' ]]; then
pyenv install 3.5.0
pyenv global 3.5.0
;;
py36)
pyenv install 3.6.0
pyenv global 3.6.0
;;
pypy)
pyenv install "pypy-${PYPY_VERSION}"
pyenv global "pypy-${PYPY_VERSION}"
pyenv install "pypy${PYPY_VERSION}"
pyenv global "pypy${PYPY_VERSION}"
;;
esac
pyenv rehash
Expand All @@ -55,8 +59,8 @@ else
export PYENV_ROOT="$PWD/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
pyenv install "pypy-${PYPY_VERSION}"
pyenv global "pypy-${PYPY_VERSION}"
pyenv install "pypy${PYPY_VERSION}"
pyenv global "pypy${PYPY_VERSION}"
fi
pip install -U virtualenv
fi
Expand Down
2 changes: 1 addition & 1 deletion .travis/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ else
export PYENV_ROOT="$PWD/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
pyenv global "pypy-${PYPY_VERSION}"
pyenv global "pypy${PYPY_VERSION}"
fi
fi
source $PWD/.venv/bin/activate
Expand Down
13 changes: 12 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@ Release History
Upcoming
++++++++

1.5.3
1.5.4
++++++++++++++++++

- Bugfix so that the return value of ``JWTAuth.refresh()`` correctly matches
that of the auth interface (by returning a tuple of
((access token), (refresh token or None)), instead of just the access token).
In particular, this fixes an exception in ``BoxSession`` that always occurred
when it tried to refresh any ``JWTAuth`` object.
- Fixed an exception that was being raised from ``ExtendableEnumMeta.__dir__()``.
- CPython 3.6 support.

1.5.3 (2016-05-26)
++++++++++++++++++

- Bugfix so that ``JWTAuth`` opens the PEM private key file in ``'rb'`` mode.
Expand Down
34 changes: 30 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,34 @@ Installing

.. code-block:: console

pip install boxsdk

pip install "boxsdk>=1.5,<2.0"

Important Versioning Note
-------------------------

The production version of the ``boxsdk`` package is the 1.5.x release track.
The code can be found on Github at
<https://github.com/box/box-python-sdk/tree/1.5>. This is what will be
installed with ``pip install boxsdk``.

The development version of the ``boxsdk`` package is the 2.0.0 release track,
currently in an alpha release. The code can be found on Github at
<https://github.com/box/box-python-sdk/tree/master>. This isn't installed by
``pip`` by default, but can be installed if specifying a specific version. For
example, ``pip install boxsdk==2.0.0a5``. We welcome early testing and feedback
on these alpha releases. Note that while we're still in alpha, version 2.0.0 is
subject to change, and there might be breaking changes between alpha releases
(e.g. between 2.0.0a2 and 2.0.0a3).

The 1.5.x release track will receive bugfix support at least until the final
release of 2.0.0.

We always recommend pinning to a specific version (e.g. by adding
``boxsdk==1.5.3`` in your ``requirements.txt`` file), so that you don't risk
accidentally upgrading to a new version and breaking your application. This is
particularly important with the upcoming 2.0.0 release. There will be some
breaking changes in version 2.0.0, which might break your application if you
upgrade without adapting your code first.

Authorization
-------------
Expand Down Expand Up @@ -418,8 +444,8 @@ Run all tests using -

The tox tests include code style checks via pep8 and pylint.

The tox tests are configured to run on Python 2.6, 2.7, 3.3, 3.4, 3.5, and
PyPy (our CI is configured to run PyPy tests on PyPy 4.0).
The tox tests are configured to run on Python 2.6, 2.7, 3.3, 3.4, 3.5, 3.6, and
PyPy2.7 (our CI is configured to run PyPy2.7 tests on PyPy2.7 5.8.0).


Support
Expand Down
5 changes: 3 additions & 2 deletions boxsdk/auth/jwt_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ def _refresh(self, access_token):
"""
# pylint:disable=unused-argument
if self._user_id is None:
return self.authenticate_instance()
new_access_token = self.authenticate_instance()
else:
return self._auth_with_jwt(self._user_id, 'user')
new_access_token = self._auth_with_jwt(self._user_id, 'user')
return new_access_token, None
11 changes: 5 additions & 6 deletions boxsdk/auth/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,13 @@ def refresh(self, access_token_to_refresh):
# The lock here is for handling that case that multiple requests fail, due to access token expired, at the
# same time to avoid multiple session renewals.
if (access_token is None) or (access_token_to_refresh == access_token):
# If the active access token is the same as the token needs to
# If the active access token is the same as the token that needs to
# be refreshed, or if we don't currently have any active access
# token, we make the request to refresh the token.
return self._refresh(access_token_to_refresh)
else:
# If the active access token (self._access_token) is not the same as the token needs to be refreshed,
# it means the expired token has already been refreshed. Simply return the current active tokens.
return access_token, refresh_token
access_token, refresh_token = self._refresh(access_token_to_refresh)
# Else, if the active access token (self._access_token) is not the same as the token needs to be refreshed,
# it means the expired token has already been refreshed. Simply return the current active tokens.
return access_token, refresh_token

@staticmethod
def _get_state_csrf_token():
Expand Down
14 changes: 7 additions & 7 deletions boxsdk/object/events.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# coding: utf-8

from __future__ import unicode_literals

from __future__ import unicode_literals, absolute_import
from requests.exceptions import Timeout
from six import with_metaclass

from boxsdk.object.base_endpoint import BaseEndpoint
from boxsdk.util.enum import ExtendableEnumMeta
from boxsdk.util.lru_cache import LRUCache
from boxsdk.util.text_enum import TextEnum
from .base_endpoint import BaseEndpoint
from ..util.compat import with_metaclass
from ..util.enum import ExtendableEnumMeta
from ..util.lru_cache import LRUCache
from ..util.text_enum import TextEnum


# pylint:disable=too-many-ancestors
Expand Down Expand Up @@ -55,6 +54,7 @@ class Events(BaseEndpoint):

def get_url(self, *args):
"""Base class override."""
# pylint:disable=arguments-differ
return super(Events, self).get_url('events', *args)

def get_events(self, limit=100, stream_position=0, stream_type=UserEventsStreamType.ALL):
Expand Down
64 changes: 62 additions & 2 deletions boxsdk/util/compat.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# coding: utf-8

from __future__ import division, unicode_literals

from __future__ import absolute_import, division, unicode_literals

from datetime import timedelta

import six


if not hasattr(timedelta, 'total_seconds'):
def total_seconds(delta):
"""
Expand All @@ -15,3 +17,61 @@ def total_seconds(delta):
else:
def total_seconds(delta):
return delta.total_seconds()


def with_metaclass(meta, *bases, **with_metaclass_kwargs):
"""Extends the behavior of six.with_metaclass.

The normal usage (expanded to include temporaries, to make the illustration
easier) is:

.. code-block:: python

temporary_class = six.with_metaclass(meta, *bases)
temporary_metaclass = type(temporary_class)

class Subclass(temporary_class):
...

SubclassMeta = type(Subclass)

In this example:

- ``temporary_class`` is a class with ``(object,)`` as its bases.
- ``temporary_metaclass`` is a metaclass with ``(meta,)`` as its bases.
- ``Subclass`` is a class with ``bases`` as its bases.
- ``SubclassMeta`` is ``meta``.

``six.with_metaclass()`` is defined in such a way that it can make sure
that ``Subclass`` has the correct metaclass and bases, while only using
syntax which is common to both Python 2 and Python 3.
``temporary_metaclass()`` returns an instance of ``meta``, rather than an
instance of itself / a subclass of ``temporary_class``, which is how
``SubclassMeta`` ends up being ``meta``, and how the temporaries don't
appear anywhere in the final subclass.

There are two problems with the current (as of six==1.10.0) implementation
of ``six.with_metaclass()``, which this function solves.

``six.with_metaclass()`` does not define ``__prepare__()`` on the temporary
metaclass. This means that ``meta.__prepare__()`` gets called directly,
with bases set to ``(object,)``. If it needed to actually receive
``bases``, then errors might occur. For example, this was a problem when
used with ``enum.EnumMeta`` in Python 3.6. Here we make sure that
``__prepare__()`` is defined on the temporary metaclass, and pass ``bases``
to ``meta.__prepare__()``.

Since ``temporary_class`` doesn't have the correct bases, in theory this
could cause other problems, besides the previous one, in certain edge
cases. To make sure that doesn't become a problem, we make sure that
``temporary_class`` has ``bases`` as its bases, just like the final class.
"""
temporary_class = six.with_metaclass(meta, *bases, **with_metaclass_kwargs)
temporary_metaclass = type(temporary_class)

class TemporaryMetaSubclass(temporary_metaclass):
@classmethod
def __prepare__(cls, name, this_bases, **kwds): # pylint:disable=unused-argument
return meta.__prepare__(name, bases, **kwds)

return type.__new__(TemporaryMetaSubclass, str('temporary_class'), bases, {})
9 changes: 6 additions & 3 deletions boxsdk/util/enum.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# coding: utf-8
# pylint:disable=no-value-for-parameter

from __future__ import absolute_import, unicode_literals

Expand All @@ -20,7 +21,9 @@ class ExtendableEnumMeta(EnumMeta):

This allows you to define hierarchies such as this:

class EnumBase(six.with_metaclass(ExtendableEnumMeta, Enum)): pass
from box.util.compat import with_metaclass

class EnumBase(with_metaclass(ExtendableEnumMeta, Enum)): pass

class Enum1(EnumBase):
A = 'A'
Expand Down Expand Up @@ -99,7 +102,7 @@ def in_(subclass):
return any(map(in_, cls.__subclasses__()))

def __dir__(cls):
return list(set(super(ExtendableEnumMeta, cls).__dir__()).union(set(map(dir, cls.__subclasses__()))))
return list(set(super(ExtendableEnumMeta, cls).__dir__()).union(*map(dir, cls.__subclasses__())))

def __getitem__(cls, name):
try:
Expand Down Expand Up @@ -129,7 +132,7 @@ def __getattr__(cls, name):
# and __getitem__ have the same behavior. And __getitem__ has
# the advantage of never grabbing anything other than enum
# members.
return cls[name]
return cls[name] # pylint:disable=unsubscriptable-object
except KeyError:
pass
# This needs to be `reraise()`, and not just `raise`. Otherwise,
Expand Down
2 changes: 1 addition & 1 deletion boxsdk/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from __future__ import unicode_literals, absolute_import


__version__ = '1.5.3'
__version__ = '1.5.4'
11 changes: 8 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ bottle
jsonpatch
mock>=2.0.0
pep8
pylint

# Pin to an old version so that new errors don't start appearing on the
# bugfix-only 1.5 branch. Don't propagate this pinned version to the master
# branch.
pylint==1.5.5

pytest>=2.8.3
pytest-cov
pytest-xdist
sphinx
sphinx>=1.5,<1.6
sqlalchemy
tox
tox
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ pyjwt>=1.3.0
requests>=2.4.3
requests-toolbelt>=0.4.0
six >= 1.4.0
-e .
-e .[all]
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ universal=1

[metadata]
license_file=LICENSE

[isort]
known_first_party=test
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Operating System :: OS Independent',
Expand Down
Loading