Use positional library instead of our own copy

The positional library was spun directly out of what keystoneauth1 was
using so this is a fairly trivial change.

Change-Id: I7931ed1547d2a05e2d248bc3240a576dc68a0a40
changes/00/267300/2
Jamie Lennox 2016-01-14 15:39:03 +11:00
parent f33cb0e280
commit f21def7061
17 changed files with 56 additions and 199 deletions

View File

@ -11,8 +11,6 @@
# under the License.
import datetime
import functools
import inspect
import logging
import iso8601
@ -27,161 +25,6 @@ def get_logger(name):
logger = get_logger(__name__)
class positional(object):
"""A decorator which enforces only some args may be passed positionally.
This idea and some of the code was taken from the oauth2 client of the
google-api client.
This decorator makes it easy to support Python 3 style key-word only
parameters. For example, in Python 3 it is possible to write::
def fn(pos1, *, kwonly1, kwonly2=None):
...
All named parameters after * must be a keyword::
fn(10, 'kw1', 'kw2') # Raises exception.
fn(10, kwonly1='kw1', kwonly2='kw2') # Ok.
To replicate this behaviour with the positional decorator you simply
specify how many arguments may be passed positionally. To replicate the
example above::
@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 that in python the
first positional argument passed is always the instance so you will need 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):
...
If you would prefer not to account for `self` and `cls` you can use the
`method` and `classmethod` helpers which do not consider the initial
positional argument. So the following class is exactly the same as the one
above::
class MyClass(object):
@positional.method(1)
def my_method(self, pos1, kwonly1=None):
...
@positional.classmethod(1)
def my_method(cls, pos1, kwonly1=None):
...
If a value isn't provided to the decorator then it will enforce that
every variable without a default value will be required to be a kwarg::
@positional()
def fn(pos1, kwonly1=None):
...
fn(10) # Ok.
fn(10, 20) # Raises exception.
fn(10, kwonly1=20) # Ok.
This behaviour will work with the `positional.method` and
`positional.classmethod` helper functions as well::
class MyClass(object):
@positional.classmethod()
def my_method(cls, pos1, kwonly1=None):
...
MyClass.my_method(10) # Ok.
MyClass.my_method(10, 20) # Raises exception.
MyClass.my_method(10, kwonly1=20) # Ok.
For compatibility reasons you may wish to not always raise an exception so
a WARN mode is available. Rather than raise an exception a warning message
will be logged::
@positional(1, enforcement=positional.WARN):
def fn(pos1, kwonly=1):
...
Available modes are:
- positional.EXCEPT - the default, raise an exception.
- positional.WARN - log a warning on mistake.
"""
EXCEPT = 'except'
WARN = 'warn'
def __init__(self, max_positional_args=None, enforcement=EXCEPT):
self._max_positional_args = max_positional_args
self._enforcement = enforcement
@classmethod
def method(cls, max_positional_args=None, enforcement=EXCEPT):
if max_positional_args is not None:
max_positional_args += 1
def f(func):
return cls(max_positional_args, enforcement)(func)
return f
@classmethod
def classmethod(cls, *args, **kwargs):
def f(func):
return classmethod(cls.method(*args, **kwargs)(func))
return f
def __call__(self, func):
if self._max_positional_args is None:
spec = inspect.getargspec(func)
self._max_positional_args = len(spec.args) - len(spec.defaults)
plural = '' if self._max_positional_args == 1 else 's'
@functools.wraps(func)
def inner(*args, **kwargs):
if len(args) > self._max_positional_args:
message = ('%(name)s takes at most %(max)d positional '
'argument%(plural)s (%(given)d given)' %
{'name': func.__name__,
'max': self._max_positional_args,
'given': len(args),
'plural': plural})
if self._enforcement == self.EXCEPT:
raise TypeError(message)
elif self._enforcement == self.WARN:
logger.warning(message)
return func(*args, **kwargs)
return inner
def normalize_time(timestamp):
"""Normalize time in arbitrary timezone to UTC naive object."""
offset = timestamp.utcoffset()

View File

@ -16,6 +16,8 @@
import functools
from positional import positional
from keystoneauth1 import _utils as utils
from keystoneauth1.access import service_catalog
from keystoneauth1.access import service_providers
@ -31,7 +33,7 @@ __all__ = ('AccessInfo',
'create')
@utils.positional()
@positional()
def create(resp=None, body=None, auth_token=None):
if resp and not body:
body = resp.json()

View File

@ -18,9 +18,9 @@
import abc
from positional import positional
import six
from keystoneauth1 import _utils as utils
from keystoneauth1 import exceptions
@ -65,7 +65,7 @@ class ServiceCatalog(object):
"""
return interface
@utils.positional()
@positional()
def get_endpoints(self, service_type=None, interface=None,
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
@ -141,7 +141,7 @@ class ServiceCatalog(object):
return endpoints
@abc.abstractmethod
@utils.positional()
@positional()
def get_urls(self, service_type=None, interface='public',
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
@ -165,7 +165,7 @@ class ServiceCatalog(object):
"""
raise NotImplementedError()
@utils.positional()
@positional()
def url_for(self, service_type=None, interface='public',
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
@ -251,7 +251,7 @@ class ServiceCatalogV2(ServiceCatalog):
def is_interface_match(self, endpoint, interface):
return interface in endpoint
@utils.positional()
@positional()
def get_urls(self, service_type=None, interface='publicURL',
region_name=None, service_name=None,
service_id=None, endpoint_id=None):
@ -293,7 +293,7 @@ class ServiceCatalogV3(ServiceCatalog):
except KeyError:
return False
@utils.positional()
@positional()
def get_urls(self, service_type=None, interface='publicURL',
region_name=None, service_name=None,
service_id=None, endpoint_id=None):

View File

@ -12,7 +12,7 @@
import os
from keystoneauth1 import _utils as utils
from positional import positional
class Adapter(object):
@ -45,7 +45,7 @@ class Adapter(object):
:type logger: logging.Logger
"""
@utils.positional()
@positional()
def __init__(self, session, service_type=None, service_name=None,
interface=None, region_name=None, endpoint_override=None,
version=None, auth=None, user_agent=None,

View File

@ -23,6 +23,8 @@ raw data specified in version discovery responses.
import re
from positional import positional
from keystoneauth1 import _utils as utils
from keystoneauth1 import exceptions
@ -30,7 +32,7 @@ from keystoneauth1 import exceptions
_LOGGER = utils.get_logger(__name__)
@utils.positional()
@positional()
def get_version_data(session, url, authenticated=None):
"""Retrieve raw version data from a url."""
headers = {'Accept': 'application/json'}
@ -133,7 +135,7 @@ class Discover(object):
DEPRECATED_STATUSES = ('deprecated',)
EXPERIMENTAL_STATUSES = ('experimental',)
@utils.positional()
@positional()
def __init__(self, session, url, authenticated=None):
self._data = get_version_data(session, url,
authenticated=authenticated)
@ -178,7 +180,7 @@ class Discover(object):
return versions
@utils.positional()
@positional()
def version_data(self, reverse=False, **kwargs):
"""Get normalized version data.

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from positional import positional
from keystoneauth1 import _utils as utils
__all__ = ('DiscoveryList',
@ -30,7 +32,7 @@ class DiscoveryBase(dict):
:param DateTime updated: When the API was last updated.
"""
@utils.positional()
@positional()
def __init__(self, id, status=None, updated=None):
super(DiscoveryBase, self).__init__()
@ -74,7 +76,7 @@ class DiscoveryBase(dict):
def updated(self, value):
self.updated_str = value.isoformat()
@utils.positional()
@positional()
def add_link(self, href, rel='self', type=None):
link = {'href': href, 'rel': rel}
if type:
@ -86,7 +88,7 @@ class DiscoveryBase(dict):
def media_types(self):
return self.setdefault('media-types', [])
@utils.positional(1)
@positional(1)
def add_media_type(self, base, type):
mt = {'base': base, 'type': type}
self.media_types.append(mt)
@ -110,7 +112,7 @@ class V2Discovery(DiscoveryBase):
_DESC_URL = 'http://docs.openstack.org/api/openstack-identity-service/2.0/'
@utils.positional()
@positional()
def __init__(self, href, id=None, html=True, pdf=True, **kwargs):
super(V2Discovery, self).__init__(id or 'v2.0', **kwargs)
@ -156,7 +158,7 @@ class V3Discovery(DiscoveryBase):
:param bool xml: Add XML media-type elements to the structure.
"""
@utils.positional()
@positional()
def __init__(self, href, id=None, json=True, xml=True, **kwargs):
super(V3Discovery, self).__init__(id or 'v3.0', **kwargs)
@ -207,7 +209,7 @@ class DiscoveryList(dict):
TEST_URL = 'http://keystone.host:5000/'
@utils.positional(2)
@positional(2)
def __init__(self, href=None, v2=True, v3=True, v2_id=None, v3_id=None,
v2_status=None, v2_updated=None, v2_html=True, v2_pdf=True,
v3_status=None, v3_updated=None, v3_json=True, v3_xml=True):

View File

@ -10,7 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import _utils as utils
from positional import positional
from keystoneauth1.identity import base
@ -31,7 +32,7 @@ class AccessInfoPlugin(base.BaseIdentityPlugin):
if using the AUTH_INTERFACE with get_endpoint. (optional)
"""
@utils.positional()
@positional()
def __init__(self, auth_ref, auth_url=None):
super(AccessInfoPlugin, self).__init__(auth_url=auth_url,
reauthenticate=False)

View File

@ -16,6 +16,7 @@ import hashlib
import json
import threading
from positional import positional
import six
from keystoneauth1 import _utils as utils
@ -261,7 +262,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
except exceptions.ServiceProviderNotFound:
return None
@utils.positional()
@positional()
def get_discovery(self, session, url, authenticated=None):
"""Return the discovery object for a URL.

View File

@ -10,14 +10,13 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import _utils as utils
from positional import positional
from keystoneauth1 import discover
from keystoneauth1.identity.generic import base
from keystoneauth1.identity import v2
from keystoneauth1.identity import v3
LOG = utils.get_logger(__name__)
class Password(base.BaseGenericPlugin):
"""A common user/password authentication plugin.
@ -30,7 +29,7 @@ class Password(base.BaseGenericPlugin):
"""
@utils.positional()
@positional()
def __init__(self, auth_url, username=None, user_id=None, password=None,
user_domain_id=None, user_domain_name=None, **kwargs):
super(Password, self).__init__(auth_url=auth_url, **kwargs)

View File

@ -12,6 +12,7 @@
import abc
from positional import positional
import six
from keystoneauth1 import _utils as utils
@ -34,7 +35,7 @@ class Auth(base.BaseIdentityPlugin):
is going to expire. (optional) default True
"""
@utils.positional()
@positional()
def __init__(self, auth_url,
trust_id=None,
tenant_id=None,
@ -110,7 +111,7 @@ class Password(Auth):
:raises TypeError: if a user_id or username is not provided.
"""
@utils.positional(4)
@positional(4)
def __init__(self, auth_url, username=_NOT_PASSED, password=None,
user_id=_NOT_PASSED, **kwargs):
super(Password, self).__init__(auth_url, **kwargs)

View File

@ -12,6 +12,7 @@
import abc
from positional import positional
import six
from keystoneauth1 import _utils as utils
@ -43,7 +44,7 @@ class BaseAuth(base.BaseIdentityPlugin):
token. (optional) default True.
"""
@utils.positional()
@positional()
def __init__(self, auth_url,
trust_id=None,
domain_id=None,

View File

@ -10,7 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import _utils
from positional import positional
from keystoneauth1 import access
from keystoneauth1.identity.v3 import federation
@ -131,7 +132,7 @@ class _OidcBase(federation.FederationBaseAuth):
class OidcPassword(_OidcBase):
"""Implementation for OpenID Connect Resource Owner Password Credential"""
@_utils.positional(4)
@positional(4)
def __init__(self, auth_url, identity_provider, protocol,
client_id, client_secret, access_token_endpoint,
grant_type='password', access_token_type='access_token',
@ -203,7 +204,7 @@ class OidcPassword(_OidcBase):
class OidcAuthorizationCode(_OidcBase):
"""Implementation for OpenID Connect Authorization Code"""
@_utils.positional(4)
@positional(4)
def __init__(self, auth_url, identity_provider, protocol,
client_id, client_secret, access_token_endpoint,
grant_type='authorization_code',

View File

@ -13,7 +13,8 @@
import argparse
import os
from keystoneauth1 import _utils as utils
from positional import positional
from keystoneauth1.loading import base
@ -30,7 +31,7 @@ def _register_plugin_argparse_arguments(parser, plugin):
dest='os_%s' % opt.dest)
@utils.positional()
@positional()
def register_argparse_arguments(parser, argv, default=None):
"""Register CLI options needed to create a plugin.

View File

@ -13,13 +13,13 @@
import itertools
import os
from positional import positional
try:
from oslo_config import cfg
except ImportError:
cfg = None
from keystoneauth1 import _utils as utils
__all__ = ('Opt',)
@ -62,7 +62,7 @@ class Opt(object):
required option is not present loading should fail.
"""
@utils.positional()
@positional()
def __init__(self,
name,
type=str,

View File

@ -13,12 +13,13 @@
import argparse
import os
from positional import positional
try:
from oslo_config import cfg
except ImportError:
cfg = None
from keystoneauth1 import _utils as utils
from keystoneauth1.loading import base
from keystoneauth1 import session
@ -53,7 +54,7 @@ class Session(base.BaseLoader):
def get_options(self):
return []
@utils.positional(1)
@positional(1)
def load_from_options(self,
insecure=False,
verify=None,

View File

@ -20,6 +20,7 @@ import socket
import time
import uuid
from positional import positional
import requests
import six
from six.moves import urllib
@ -117,7 +118,7 @@ class Session(object):
_DEFAULT_REDIRECT_LIMIT = 30
@utils.positional(2)
@positional(2)
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
cert=None, timeout=None, user_agent=None,
redirect=_DEFAULT_REDIRECT_LIMIT):
@ -185,7 +186,7 @@ class Session(object):
return (header[0], '{SHA1}%s' % token_hash)
return header
@utils.positional()
@positional()
def _http_log_request(self, url, method=None, data=None,
json=None, headers=None, logger=_logger):
if not logger.isEnabledFor(logging.DEBUG):
@ -224,7 +225,7 @@ class Session(object):
logger.debug(' '.join(string_parts))
@utils.positional()
@positional()
def _http_log_response(self, response=None, json=None,
status_code=None, headers=None, text=None,
logger=_logger):
@ -253,7 +254,7 @@ class Session(object):
logger.debug(' '.join(string_parts))
@utils.positional()
@positional()
def request(self, url, method, json=None, original_ip=None,
user_agent=None, redirect=None, authenticated=None,
endpoint_filter=None, auth=None, requests_auth=None,

View File

@ -5,6 +5,7 @@
pbr>=1.6 # Apache-2.0
argparse # PSF
iso8601>=0.1.9 # MIT
positional>=1.0.1 # Apache-2.0
requests!=2.9.0,>=2.8.1 # Apache-2.0
six>=1.9.0 # MIT
stevedore>=1.5.0 # Apache-2.0