From f21def70615f079a5791da61cdb0fc6166b47c37 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Thu, 14 Jan 2016 15:39:03 +1100 Subject: [PATCH] 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 --- keystoneauth1/_utils.py | 157 --------------------- keystoneauth1/access/access.py | 4 +- keystoneauth1/access/service_catalog.py | 12 +- keystoneauth1/adapter.py | 4 +- keystoneauth1/discover.py | 8 +- keystoneauth1/fixture/discovery.py | 14 +- keystoneauth1/identity/access.py | 5 +- keystoneauth1/identity/base.py | 3 +- keystoneauth1/identity/generic/password.py | 7 +- keystoneauth1/identity/v2.py | 5 +- keystoneauth1/identity/v3/base.py | 3 +- keystoneauth1/identity/v3/oidc.py | 7 +- keystoneauth1/loading/cli.py | 5 +- keystoneauth1/loading/opts.py | 6 +- keystoneauth1/loading/session.py | 5 +- keystoneauth1/session.py | 9 +- requirements.txt | 1 + 17 files changed, 56 insertions(+), 199 deletions(-) diff --git a/keystoneauth1/_utils.py b/keystoneauth1/_utils.py index a6466b9..7d0c48f 100644 --- a/keystoneauth1/_utils.py +++ b/keystoneauth1/_utils.py @@ -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() diff --git a/keystoneauth1/access/access.py b/keystoneauth1/access/access.py index b668737..73d9c73 100644 --- a/keystoneauth1/access/access.py +++ b/keystoneauth1/access/access.py @@ -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() diff --git a/keystoneauth1/access/service_catalog.py b/keystoneauth1/access/service_catalog.py index 2f37e35..d8593ba 100644 --- a/keystoneauth1/access/service_catalog.py +++ b/keystoneauth1/access/service_catalog.py @@ -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): diff --git a/keystoneauth1/adapter.py b/keystoneauth1/adapter.py index 69f7e0a..034a7a5 100644 --- a/keystoneauth1/adapter.py +++ b/keystoneauth1/adapter.py @@ -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, diff --git a/keystoneauth1/discover.py b/keystoneauth1/discover.py index b3c5c4c..63900bb 100644 --- a/keystoneauth1/discover.py +++ b/keystoneauth1/discover.py @@ -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. diff --git a/keystoneauth1/fixture/discovery.py b/keystoneauth1/fixture/discovery.py index 702b88b..1859ee7 100644 --- a/keystoneauth1/fixture/discovery.py +++ b/keystoneauth1/fixture/discovery.py @@ -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): diff --git a/keystoneauth1/identity/access.py b/keystoneauth1/identity/access.py index fde9ea3..323df23 100644 --- a/keystoneauth1/identity/access.py +++ b/keystoneauth1/identity/access.py @@ -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) diff --git a/keystoneauth1/identity/base.py b/keystoneauth1/identity/base.py index 8496dff..6b255a0 100644 --- a/keystoneauth1/identity/base.py +++ b/keystoneauth1/identity/base.py @@ -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. diff --git a/keystoneauth1/identity/generic/password.py b/keystoneauth1/identity/generic/password.py index f382ec0..d6fc9d0 100644 --- a/keystoneauth1/identity/generic/password.py +++ b/keystoneauth1/identity/generic/password.py @@ -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) diff --git a/keystoneauth1/identity/v2.py b/keystoneauth1/identity/v2.py index d024c6d..e6ca445 100644 --- a/keystoneauth1/identity/v2.py +++ b/keystoneauth1/identity/v2.py @@ -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) diff --git a/keystoneauth1/identity/v3/base.py b/keystoneauth1/identity/v3/base.py index 9a9d9aa..4a549db 100644 --- a/keystoneauth1/identity/v3/base.py +++ b/keystoneauth1/identity/v3/base.py @@ -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, diff --git a/keystoneauth1/identity/v3/oidc.py b/keystoneauth1/identity/v3/oidc.py index 493ea18..5aa28f9 100644 --- a/keystoneauth1/identity/v3/oidc.py +++ b/keystoneauth1/identity/v3/oidc.py @@ -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', diff --git a/keystoneauth1/loading/cli.py b/keystoneauth1/loading/cli.py index fbcec72..60afb43 100644 --- a/keystoneauth1/loading/cli.py +++ b/keystoneauth1/loading/cli.py @@ -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. diff --git a/keystoneauth1/loading/opts.py b/keystoneauth1/loading/opts.py index 2c73b6f..5621a5f 100644 --- a/keystoneauth1/loading/opts.py +++ b/keystoneauth1/loading/opts.py @@ -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, diff --git a/keystoneauth1/loading/session.py b/keystoneauth1/loading/session.py index 01ac534..aa0830a 100644 --- a/keystoneauth1/loading/session.py +++ b/keystoneauth1/loading/session.py @@ -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, diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py index 6bb2c42..a77af39 100644 --- a/keystoneauth1/session.py +++ b/keystoneauth1/session.py @@ -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, diff --git a/requirements.txt b/requirements.txt index c4b408f..cd73aea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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