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
This commit is contained in:
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. # under the License.
import datetime import datetime
import functools
import inspect
import logging import logging
import iso8601 import iso8601
@@ -27,161 +25,6 @@ def get_logger(name):
logger = 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): def normalize_time(timestamp):
"""Normalize time in arbitrary timezone to UTC naive object.""" """Normalize time in arbitrary timezone to UTC naive object."""
offset = timestamp.utcoffset() offset = timestamp.utcoffset()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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