Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

Commit

Permalink
Merge util.py and _helpers.py
Browse files Browse the repository at this point in the history
A new file, `_helpers.py`, was created without realizing that
`utils.py` existed for the same purpose.

Moving all to `_helpers.py`.
  • Loading branch information
pferate committed Aug 4, 2016
1 parent f04d521 commit 943ff6c
Show file tree
Hide file tree
Showing 23 changed files with 387 additions and 419 deletions.
1 change: 0 additions & 1 deletion docs/source/oauth2client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ Submodules
oauth2client.service_account
oauth2client.tools
oauth2client.transport
oauth2client.util

Module contents
---------------
Expand Down
7 changes: 0 additions & 7 deletions docs/source/oauth2client.util.rst

This file was deleted.

204 changes: 204 additions & 0 deletions oauth2client/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,216 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helper functions for commonly used utilities."""

import base64
import functools
import inspect
import json
import logging
import os
import warnings

import six
from six.moves import urllib


__author__ = [
'[email protected] (Rafe Kaplan)',
'[email protected] (Guido van Rossum)',
]

__all__ = [
'positional',
'POSITIONAL_WARNING',
'POSITIONAL_EXCEPTION',
'POSITIONAL_IGNORE',
]

logger = logging.getLogger(__name__)

POSITIONAL_WARNING = 'WARNING'
POSITIONAL_EXCEPTION = 'EXCEPTION'
POSITIONAL_IGNORE = 'IGNORE'
POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION,
POSITIONAL_IGNORE])

positional_parameters_enforcement = POSITIONAL_WARNING

_SYM_LINK_MESSAGE = 'File: {0}: Is a symbolic link.'
_IS_DIR_MESSAGE = '{0}: Is a directory'
_MISSING_FILE_MESSAGE = 'Cannot access {0}: No such file or directory'


def positional(max_positional_args):
"""A decorator to declare that only the first N arguments my be positional.
This decorator makes it easy to support Python 3 style keyword-only
parameters. For example, in Python 3 it is possible to write::
def fn(pos1, *, kwonly1=None, kwonly1=None):
...
All named parameters after ``*`` must be a keyword::
fn(10, 'kw1', 'kw2') # Raises exception.
fn(10, kwonly1='kw1') # Ok.
Example
^^^^^^^
To define a function like above, do::
@positional(1)
def fn(pos1, kwonly1=None, kwonly2=None):
...
If no default value is provided to a keyword argument, it becomes a
required keyword argument::
@positional(0)
def fn(required_kw):
...
This must be called with the keyword parameter::
fn() # Raises exception.
fn(10) # Raises exception.
fn(required_kw=10) # Ok.
When defining instance or class methods always remember to account for
``self`` and ``cls``::
class MyClass(object):
@positional(2)
def my_method(self, pos1, kwonly1=None):
...
@classmethod
@positional(2)
def my_method(cls, pos1, kwonly1=None):
...
The positional decorator behavior is controlled by
``_helpers.positional_parameters_enforcement``, which may be set to
``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
nothing, respectively, if a declaration is violated.
Args:
max_positional_arguments: Maximum number of positional arguments. All
parameters after the this index must be
keyword only.
Returns:
A decorator that prevents using arguments after max_positional_args
from being used as positional parameters.
Raises:
TypeError: if a key-word only argument is provided as a positional
parameter, but only if
_helpers.positional_parameters_enforcement is set to
POSITIONAL_EXCEPTION.
"""

def positional_decorator(wrapped):
@functools.wraps(wrapped)
def positional_wrapper(*args, **kwargs):
if len(args) > max_positional_args:
plural_s = ''
if max_positional_args != 1:
plural_s = 's'
message = ('{function}() takes at most {args_max} positional '
'argument{plural} ({args_given} given)'.format(
function=wrapped.__name__,
args_max=max_positional_args,
args_given=len(args),
plural=plural_s))
if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
raise TypeError(message)
elif positional_parameters_enforcement == POSITIONAL_WARNING:
logger.warning(message)
return wrapped(*args, **kwargs)
return positional_wrapper

if isinstance(max_positional_args, six.integer_types):
return positional_decorator
else:
args, _, _, defaults = inspect.getargspec(max_positional_args)
return positional(len(args) - len(defaults))(max_positional_args)


def scopes_to_string(scopes):
"""Converts scope value to a string.
If scopes is a string then it is simply passed through. If scopes is an
iterable then a string is returned that is all the individual scopes
concatenated with spaces.
Args:
scopes: string or iterable of strings, the scopes.
Returns:
The scopes formatted as a single string.
"""
if isinstance(scopes, six.string_types):
return scopes
else:
return ' '.join(scopes)


def string_to_scopes(scopes):
"""Converts stringifed scope value to a list.
If scopes is a list then it is simply passed through. If scopes is an
string then a list of each individual scope is returned.
Args:
scopes: a string or iterable of strings, the scopes.
Returns:
The scopes in a list.
"""
if not scopes:
return []
if isinstance(scopes, six.string_types):
return scopes.split(' ')
else:
return scopes


def _add_query_parameter(url, name, value):
"""Adds a query parameter to a url.
Replaces the current value if it already exists in the URL.
Args:
url: string, url to add the query parameter to.
name: string, query parameter name.
value: string, query parameter value.
Returns:
Updated query parameter. Does not update the url if value is None.
"""
if value is None:
return url
else:
parsed = list(urllib.parse.urlparse(url))
q = dict(urllib.parse.parse_qsl(parsed[4]))
q[name] = value
parsed[4] = urllib.parse.urlencode(q)
return urllib.parse.urlunparse(parsed)


def validate_file(filename):
if os.path.islink(filename):
raise IOError(_SYM_LINK_MESSAGE.format(filename))
elif os.path.isdir(filename):
raise IOError(_IS_DIR_MESSAGE.format(filename))
elif not os.path.isfile(filename):
warnings.warn(_MISSING_FILE_MESSAGE.format(filename))


def _parse_pem_key(raw_key_input):
Expand Down
29 changes: 14 additions & 15 deletions oauth2client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
from oauth2client import _helpers
from oauth2client import clientsecrets
from oauth2client import transport
from oauth2client import util


__author__ = '[email protected] (Joe Gregorio)'
Expand Down Expand Up @@ -466,7 +465,7 @@ class OAuth2Credentials(Credentials):
OAuth2Credentials objects may be safely pickled and unpickled.
"""

@util.positional(8)
@_helpers.positional(8)
def __init__(self, access_token, client_id, client_secret, refresh_token,
token_expiry, token_uri, user_agent, revoke_uri=None,
id_token=None, token_response=None, scopes=None,
Expand Down Expand Up @@ -513,7 +512,7 @@ def __init__(self, access_token, client_id, client_secret, refresh_token,
self.revoke_uri = revoke_uri
self.id_token = id_token
self.token_response = token_response
self.scopes = set(util.string_to_scopes(scopes or []))
self.scopes = set(_helpers.string_to_scopes(scopes or []))
self.token_info_uri = token_info_uri

# True if the credentials have been revoked or expired and can't be
Expand Down Expand Up @@ -592,7 +591,7 @@ def has_scopes(self, scopes):
not have scopes. In both cases, you can use refresh_scopes() to
obtain the canonical set of scopes.
"""
scopes = util.string_to_scopes(scopes)
scopes = _helpers.string_to_scopes(scopes)
return set(scopes).issubset(self.scopes)

def retrieve_scopes(self, http):
Expand Down Expand Up @@ -908,7 +907,7 @@ def _do_retrieve_scopes(self, http_request, token):
content = _helpers._from_bytes(content)
if resp.status == http_client.OK:
d = json.loads(content)
self.scopes = set(util.string_to_scopes(d.get('scope', '')))
self.scopes = set(_helpers.string_to_scopes(d.get('scope', '')))
else:
error_msg = 'Invalid response {0}.'.format(resp.status)
try:
Expand Down Expand Up @@ -1469,7 +1468,7 @@ class AssertionCredentials(GoogleCredentials):
AssertionCredentials objects may be safely pickled and unpickled.
"""

@util.positional(2)
@_helpers.positional(2)
def __init__(self, assertion_type, user_agent=None,
token_uri=oauth2client.GOOGLE_TOKEN_URI,
revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
Expand Down Expand Up @@ -1545,7 +1544,7 @@ def _require_crypto_or_die():
raise CryptoUnavailableError('No crypto library available')


@util.positional(2)
@_helpers.positional(2)
def verify_id_token(id_token, audience, http=None,
cert_uri=ID_TOKEN_VERIFICATION_CERTS):
"""Verifies a signed JWT id_token.
Expand Down Expand Up @@ -1633,7 +1632,7 @@ def _parse_exchange_token_response(content):
return resp


@util.positional(4)
@_helpers.positional(4)
def credentials_from_code(client_id, client_secret, scope, code,
redirect_uri='postmessage', http=None,
user_agent=None,
Expand Down Expand Up @@ -1684,7 +1683,7 @@ def credentials_from_code(client_id, client_secret, scope, code,
return credentials


@util.positional(3)
@_helpers.positional(3)
def credentials_from_clientsecrets_and_code(filename, scope, code,
message=None,
redirect_uri='postmessage',
Expand Down Expand Up @@ -1803,7 +1802,7 @@ class OAuth2WebServerFlow(Flow):
OAuth2WebServerFlow objects may be safely pickled and unpickled.
"""

@util.positional(4)
@_helpers.positional(4)
def __init__(self, client_id,
client_secret=None,
scope=None,
Expand Down Expand Up @@ -1862,7 +1861,7 @@ def __init__(self, client_id,
raise TypeError("The value of scope must not be None")
self.client_id = client_id
self.client_secret = client_secret
self.scope = util.scopes_to_string(scope)
self.scope = _helpers.scopes_to_string(scope)
self.redirect_uri = redirect_uri
self.login_hint = login_hint
self.user_agent = user_agent
Expand All @@ -1874,7 +1873,7 @@ def __init__(self, client_id,
self.authorization_header = authorization_header
self.params = _oauth2_web_server_flow_params(kwargs)

@util.positional(1)
@_helpers.positional(1)
def step1_get_authorize_url(self, redirect_uri=None, state=None):
"""Returns a URI to redirect to the provider.
Expand Down Expand Up @@ -1915,7 +1914,7 @@ def step1_get_authorize_url(self, redirect_uri=None, state=None):
query_params.update(self.params)
return _update_query_params(self.auth_uri, query_params)

@util.positional(1)
@_helpers.positional(1)
def step1_get_device_and_user_codes(self, http=None):
"""Returns a user code and the verification URL where to enter it
Expand Down Expand Up @@ -1963,7 +1962,7 @@ def step1_get_device_and_user_codes(self, http=None):
pass
raise OAuth2DeviceCodeError(error_msg)

@util.positional(2)
@_helpers.positional(2)
def step2_exchange(self, code=None, http=None, device_flow_info=None):
"""Exchanges a code for OAuth2Credentials.
Expand Down Expand Up @@ -2060,7 +2059,7 @@ def step2_exchange(self, code=None, http=None, device_flow_info=None):
raise FlowExchangeError(error_msg)


@util.positional(2)
@_helpers.positional(2)
def flow_from_clientsecrets(filename, scope, redirect_uri=None,
message=None, cache=None, login_hint=None,
device_uri=None):
Expand Down
4 changes: 2 additions & 2 deletions oauth2client/contrib/_fcntl_opener.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import fcntl
import time

from oauth2client import util
from oauth2client import _helpers
from oauth2client.contrib import locked_file


Expand All @@ -40,7 +40,7 @@ def open_and_lock(self, timeout, delay):
'File {0} is already locked'.format(self._filename))
start_time = time.time()

util.validate_file(self._filename)
_helpers.validate_file(self._filename)
try:
self._fh = open(self._filename, self._mode)
except IOError as e:
Expand Down
Loading

0 comments on commit 943ff6c

Please sign in to comment.