diff -Nru python-congressclient-1.5.0/congressclient/common/utils.py python-congressclient-1.6.0/congressclient/common/utils.py --- python-congressclient-1.5.0/congressclient/common/utils.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/common/utils.py 2017-01-24 03:25:31.000000000 +0000 @@ -133,7 +133,7 @@ # NOTE(arosen): this should only occur is using congress # as admin and multiple tenants use the same datsource name. raise exceptions.Conflict( - "Multiple resources have this name %s. Delete by id." % name) + "Multiple resources have this name %s. Please specify id." % name) if id_match: return id_match diff -Nru python-congressclient-1.5.0/congressclient/exceptions.py python-congressclient-1.6.0/congressclient/exceptions.py --- python-congressclient-1.5.0/congressclient/exceptions.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/exceptions.py 2017-01-24 03:25:31.000000000 +0000 @@ -1,4 +1,7 @@ -# Copyright (c) 2012 OpenStack Foundation. +# Copyright 2010 Jacob Kaplan-Moss +# Copyright 2011 Nebula, Inc. +# Copyright 2013 Alessio Ababilov +# Copyright 2013 OpenStack Foundation # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,4 +16,449 @@ # License for the specific language governing permissions and limitations # under the License. -from congressclient.openstack.common.apiclient.exceptions import * # noqa +""" +Exception definitions. +""" + +import inspect +import sys + +import six + +from congressclient.i18n import _ + + +class ClientException(Exception): + """The base exception class for all exceptions this library raises.""" + pass + + +class MissingArgs(ClientException): + """Supplied arguments are not sufficient for calling a function.""" + def __init__(self, missing): + self.missing = missing + msg = _("Missing arguments: %s") % ", ".join(missing) + super(MissingArgs, self).__init__(msg) + + +class ValidationError(ClientException): + """Error in validation on API client side.""" + pass + + +class UnsupportedVersion(ClientException): + """User is trying to use an unsupported version of the API.""" + pass + + +class CommandError(ClientException): + """Error in CLI tool.""" + pass + + +class AuthorizationFailure(ClientException): + """Cannot authorize API client.""" + pass + + +class ConnectionRefused(ClientException): + """Cannot connect to API service.""" + pass + + +class AuthPluginOptionsMissing(AuthorizationFailure): + """Auth plugin misses some options.""" + def __init__(self, opt_names): + super(AuthPluginOptionsMissing, self).__init__( + _("Authentication failed. Missing options: %s") % + ", ".join(opt_names)) + self.opt_names = opt_names + + +class AuthSystemNotFound(AuthorizationFailure): + """User has specified an AuthSystem that is not installed.""" + def __init__(self, auth_system): + super(AuthSystemNotFound, self).__init__( + _("AuthSystemNotFound: %s") % repr(auth_system)) + self.auth_system = auth_system + + +class NoUniqueMatch(ClientException): + """Multiple entities found instead of one.""" + pass + + +class EndpointException(ClientException): + """Something is rotten in Service Catalog.""" + pass + + +class EndpointNotFound(EndpointException): + """Could not find requested endpoint in Service Catalog.""" + pass + + +class AmbiguousEndpoints(EndpointException): + """Found more than one matching endpoint in Service Catalog.""" + def __init__(self, endpoints=None): + super(AmbiguousEndpoints, self).__init__( + _("AmbiguousEndpoints: %s") % repr(endpoints)) + self.endpoints = endpoints + + +class HttpError(ClientException): + """The base exception class for all HTTP exceptions.""" + http_status = 0 + message = _("HTTP Error") + + def __init__(self, message=None, details=None, + response=None, request_id=None, + url=None, method=None, http_status=None): + self.http_status = http_status or self.http_status + self.message = message or self.message + self.details = details + self.request_id = request_id + self.response = response + self.url = url + self.method = method + formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) + if request_id: + formatted_string += " (Request-ID: %s)" % request_id + super(HttpError, self).__init__(formatted_string) + + +class HTTPRedirection(HttpError): + """HTTP Redirection.""" + message = _("HTTP Redirection") + + +class HTTPClientError(HttpError): + """Client-side HTTP error. + + Exception for cases in which the client seems to have erred. + """ + message = _("HTTP Client Error") + + +class HttpServerError(HttpError): + """Server-side HTTP error. + + Exception for cases in which the server is aware that it has + erred or is incapable of performing the request. + """ + message = _("HTTP Server Error") + + +class MultipleChoices(HTTPRedirection): + """HTTP 300 - Multiple Choices. + + Indicates multiple options for the resource that the client may follow. + """ + + http_status = 300 + message = _("Multiple Choices") + + +class BadRequest(HTTPClientError): + """HTTP 400 - Bad Request. + + The request cannot be fulfilled due to bad syntax. + """ + http_status = 400 + message = _("Bad Request") + + +class Unauthorized(HTTPClientError): + """HTTP 401 - Unauthorized. + + Similar to 403 Forbidden, but specifically for use when authentication + is required and has failed or has not yet been provided. + """ + http_status = 401 + message = _("Unauthorized") + + +class PaymentRequired(HTTPClientError): + """HTTP 402 - Payment Required. + + Reserved for future use. + """ + http_status = 402 + message = _("Payment Required") + + +class Forbidden(HTTPClientError): + """HTTP 403 - Forbidden. + + The request was a valid request, but the server is refusing to respond + to it. + """ + http_status = 403 + message = _("Forbidden") + + +class NotFound(HTTPClientError): + """HTTP 404 - Not Found. + + The requested resource could not be found but may be available again + in the future. + """ + http_status = 404 + message = _("Not Found") + + +class MethodNotAllowed(HTTPClientError): + """HTTP 405 - Method Not Allowed. + + A request was made of a resource using a request method not supported + by that resource. + """ + http_status = 405 + message = _("Method Not Allowed") + + +class NotAcceptable(HTTPClientError): + """HTTP 406 - Not Acceptable. + + The requested resource is only capable of generating content not + acceptable according to the Accept headers sent in the request. + """ + http_status = 406 + message = _("Not Acceptable") + + +class ProxyAuthenticationRequired(HTTPClientError): + """HTTP 407 - Proxy Authentication Required. + + The client must first authenticate itself with the proxy. + """ + http_status = 407 + message = _("Proxy Authentication Required") + + +class RequestTimeout(HTTPClientError): + """HTTP 408 - Request Timeout. + + The server timed out waiting for the request. + """ + http_status = 408 + message = _("Request Timeout") + + +class Conflict(HTTPClientError): + """HTTP 409 - Conflict. + + Indicates that the request could not be processed because of conflict + in the request, such as an edit conflict. + """ + http_status = 409 + message = _("Conflict") + + +class Gone(HTTPClientError): + """HTTP 410 - Gone. + + Indicates that the resource requested is no longer available and will + not be available again. + """ + http_status = 410 + message = _("Gone") + + +class LengthRequired(HTTPClientError): + """HTTP 411 - Length Required. + + The request did not specify the length of its content, which is + required by the requested resource. + """ + http_status = 411 + message = _("Length Required") + + +class PreconditionFailed(HTTPClientError): + """HTTP 412 - Precondition Failed. + + The server does not meet one of the preconditions that the requester + put on the request. + """ + http_status = 412 + message = _("Precondition Failed") + + +class RequestEntityTooLarge(HTTPClientError): + """HTTP 413 - Request Entity Too Large. + + The request is larger than the server is willing or able to process. + """ + http_status = 413 + message = _("Request Entity Too Large") + + def __init__(self, *args, **kwargs): + try: + self.retry_after = int(kwargs.pop('retry_after')) + except (KeyError, ValueError): + self.retry_after = 0 + + super(RequestEntityTooLarge, self).__init__(*args, **kwargs) + + +class RequestUriTooLong(HTTPClientError): + """HTTP 414 - Request-URI Too Long. + + The URI provided was too long for the server to process. + """ + http_status = 414 + message = _("Request-URI Too Long") + + +class UnsupportedMediaType(HTTPClientError): + """HTTP 415 - Unsupported Media Type. + + The request entity has a media type which the server or resource does + not support. + """ + http_status = 415 + message = _("Unsupported Media Type") + + +class RequestedRangeNotSatisfiable(HTTPClientError): + """HTTP 416 - Requested Range Not Satisfiable. + + The client has asked for a portion of the file, but the server cannot + supply that portion. + """ + http_status = 416 + message = _("Requested Range Not Satisfiable") + + +class ExpectationFailed(HTTPClientError): + """HTTP 417 - Expectation Failed. + + The server cannot meet the requirements of the Expect request-header field. + """ + http_status = 417 + message = _("Expectation Failed") + + +class UnprocessableEntity(HTTPClientError): + """HTTP 422 - Unprocessable Entity. + + The request was well-formed but was unable to be followed due to semantic + errors. + """ + http_status = 422 + message = _("Unprocessable Entity") + + +class InternalServerError(HttpServerError): + """HTTP 500 - Internal Server Error. + + A generic error message, given when no more specific message is suitable. + """ + http_status = 500 + message = _("Internal Server Error") + + +# NotImplemented is a python keyword. +class HttpNotImplemented(HttpServerError): + """HTTP 501 - Not Implemented. + + The server either does not recognize the request method, or it lacks + the ability to fulfill the request. + """ + http_status = 501 + message = _("Not Implemented") + + +class BadGateway(HttpServerError): + """HTTP 502 - Bad Gateway. + + The server was acting as a gateway or proxy and received an invalid + response from the upstream server. + """ + http_status = 502 + message = _("Bad Gateway") + + +class ServiceUnavailable(HttpServerError): + """HTTP 503 - Service Unavailable. + + The server is currently unavailable. + """ + http_status = 503 + message = _("Service Unavailable") + + +class GatewayTimeout(HttpServerError): + """HTTP 504 - Gateway Timeout. + + The server was acting as a gateway or proxy and did not receive a timely + response from the upstream server. + """ + http_status = 504 + message = _("Gateway Timeout") + + +class HttpVersionNotSupported(HttpServerError): + """HTTP 505 - HttpVersion Not Supported. + + The server does not support the HTTP protocol version used in the request. + """ + http_status = 505 + message = _("HTTP Version Not Supported") + + +# _code_map contains all the classes that have http_status attribute. +_code_map = dict( + (getattr(obj, 'http_status', None), obj) + for name, obj in six.iteritems(vars(sys.modules[__name__])) + if inspect.isclass(obj) and getattr(obj, 'http_status', False) +) + + +def from_response(response, method, url): + """Returns an instance of :class:`HttpError` or subclass based on response. + + :param response: instance of `requests.Response` class + :param method: HTTP method used for request + :param url: URL used for request + """ + + req_id = response.headers.get("x-openstack-request-id") + # NOTE(hdd) true for older versions of nova and cinder + if not req_id: + req_id = response.headers.get("x-compute-request-id") + kwargs = { + "http_status": response.status_code, + "response": response, + "method": method, + "url": url, + "request_id": req_id, + } + if "retry-after" in response.headers: + kwargs["retry_after"] = response.headers["retry-after"] + + content_type = response.headers.get("Content-Type", "") + if content_type.startswith("application/json"): + try: + body = response.json() + except ValueError: + pass + else: + if isinstance(body, dict) and isinstance(body.get("error"), dict): + error = body["error"] + kwargs["message"] = error.get("message") + kwargs["details"] = error.get("details") + elif content_type.startswith("text/"): + kwargs["details"] = response.text + + try: + cls = _code_map[response.status_code] + except KeyError: + if 500 <= response.status_code < 600: + cls = HttpServerError + elif 400 <= response.status_code < 500: + cls = HTTPClientError + else: + cls = HttpError + return cls(**kwargs) diff -Nru python-congressclient-1.5.0/congressclient/i18n.py python-congressclient-1.6.0/congressclient/i18n.py --- python-congressclient-1.5.0/congressclient/i18n.py 1970-01-01 00:00:00.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/i18n.py 2017-01-24 03:25:31.000000000 +0000 @@ -0,0 +1,35 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, 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. + +"""oslo_i18n integration module for novaclient. + +See http://docs.openstack.org/developer/oslo.i18n/usage.html . + +""" + +import oslo_i18n + + +_translators = oslo_i18n.TranslatorFactory(domain='congressclient') + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical diff -Nru python-congressclient-1.5.0/congressclient/__init__.py python-congressclient-1.6.0/congressclient/__init__.py --- python-congressclient-1.5.0/congressclient/__init__.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/__init__.py 2017-01-24 03:25:31.000000000 +0000 @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at diff -Nru python-congressclient-1.5.0/congressclient/openstack/common/apiclient/auth.py python-congressclient-1.6.0/congressclient/openstack/common/apiclient/auth.py --- python-congressclient-1.5.0/congressclient/openstack/common/apiclient/auth.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/openstack/common/apiclient/auth.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,221 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 Spanish National Research Council. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, 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. - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -import abc -import argparse -import os - -import six -from stevedore import extension - -from congressclient.openstack.common.apiclient import exceptions - - -_discovered_plugins = {} - - -def discover_auth_systems(): - """Discover the available auth-systems. - - This won't take into account the old style auth-systems. - """ - global _discovered_plugins - _discovered_plugins = {} - - def add_plugin(ext): - _discovered_plugins[ext.name] = ext.plugin - - ep_namespace = "congressclient.openstack.common.apiclient.auth" - mgr = extension.ExtensionManager(ep_namespace) - mgr.map(add_plugin) - - -def load_auth_system_opts(parser): - """Load options needed by the available auth-systems into a parser. - - This function will try to populate the parser with options from the - available plugins. - """ - group = parser.add_argument_group("Common auth options") - BaseAuthPlugin.add_common_opts(group) - for name, auth_plugin in six.iteritems(_discovered_plugins): - group = parser.add_argument_group( - "Auth-system '%s' options" % name, - conflict_handler="resolve") - auth_plugin.add_opts(group) - - -def load_plugin(auth_system): - try: - plugin_class = _discovered_plugins[auth_system] - except KeyError: - raise exceptions.AuthSystemNotFound(auth_system) - return plugin_class(auth_system=auth_system) - - -def load_plugin_from_args(args): - """Load required plugin and populate it with options. - - Try to guess auth system if it is not specified. Systems are tried in - alphabetical order. - - :type args: argparse.Namespace - :raises: AuthPluginOptionsMissing - """ - auth_system = args.os_auth_system - if auth_system: - plugin = load_plugin(auth_system) - plugin.parse_opts(args) - plugin.sufficient_options() - return plugin - - for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)): - plugin_class = _discovered_plugins[plugin_auth_system] - plugin = plugin_class() - plugin.parse_opts(args) - try: - plugin.sufficient_options() - except exceptions.AuthPluginOptionsMissing: - continue - return plugin - raise exceptions.AuthPluginOptionsMissing(["auth_system"]) - - -@six.add_metaclass(abc.ABCMeta) -class BaseAuthPlugin(object): - """Base class for authentication plugins. - - An authentication plugin needs to override at least the authenticate - method to be a valid plugin. - """ - - auth_system = None - opt_names = [] - common_opt_names = [ - "auth_system", - "username", - "password", - "tenant_name", - "token", - "auth_url", - ] - - def __init__(self, auth_system=None, **kwargs): - self.auth_system = auth_system or self.auth_system - self.opts = dict((name, kwargs.get(name)) - for name in self.opt_names) - - @staticmethod - def _parser_add_opt(parser, opt): - """Add an option to parser in two variants. - - :param opt: option name (with underscores) - """ - dashed_opt = opt.replace("_", "-") - env_var = "OS_%s" % opt.upper() - arg_default = os.environ.get(env_var, "") - arg_help = "Defaults to env[%s]." % env_var - parser.add_argument( - "--os-%s" % dashed_opt, - metavar="<%s>" % dashed_opt, - default=arg_default, - help=arg_help) - parser.add_argument( - "--os_%s" % opt, - metavar="<%s>" % dashed_opt, - help=argparse.SUPPRESS) - - @classmethod - def add_opts(cls, parser): - """Populate the parser with the options for this plugin. - """ - for opt in cls.opt_names: - # use `BaseAuthPlugin.common_opt_names` since it is never - # changed in child classes - if opt not in BaseAuthPlugin.common_opt_names: - cls._parser_add_opt(parser, opt) - - @classmethod - def add_common_opts(cls, parser): - """Add options that are common for several plugins. - """ - for opt in cls.common_opt_names: - cls._parser_add_opt(parser, opt) - - @staticmethod - def get_opt(opt_name, args): - """Return option name and value. - - :param opt_name: name of the option, e.g., "username" - :param args: parsed arguments - """ - return (opt_name, getattr(args, "os_%s" % opt_name, None)) - - def parse_opts(self, args): - """Parse the actual auth-system options if any. - - This method is expected to populate the attribute `self.opts` with a - dict containing the options and values needed to make authentication. - """ - self.opts.update(dict(self.get_opt(opt_name, args) - for opt_name in self.opt_names)) - - def authenticate(self, http_client): - """Authenticate using plugin defined method. - - The method usually analyses `self.opts` and performs - a request to authentication server. - - :param http_client: client object that needs authentication - :type http_client: HTTPClient - :raises: AuthorizationFailure - """ - self.sufficient_options() - self._do_authenticate(http_client) - - @abc.abstractmethod - def _do_authenticate(self, http_client): - """Protected method for authentication. - """ - - def sufficient_options(self): - """Check if all required options are present. - - :raises: AuthPluginOptionsMissing - """ - missing = [opt - for opt in self.opt_names - if not self.opts.get(opt)] - if missing: - raise exceptions.AuthPluginOptionsMissing(missing) - - @abc.abstractmethod - def token_and_endpoint(self, endpoint_type, service_type): - """Return token and endpoint. - - :param service_type: Service type of the endpoint - :type service_type: string - :param endpoint_type: Type of endpoint. - Possible values: public or publicURL, - internal or internalURL, - admin or adminURL - :type endpoint_type: string - :returns: tuple of token and endpoint strings - :raises: EndpointException - """ diff -Nru python-congressclient-1.5.0/congressclient/openstack/common/apiclient/base.py python-congressclient-1.6.0/congressclient/openstack/common/apiclient/base.py --- python-congressclient-1.5.0/congressclient/openstack/common/apiclient/base.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/openstack/common/apiclient/base.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,519 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 OpenStack Foundation -# Copyright 2012 Grid Dynamics -# Copyright 2013 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, 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. - -""" -Base utilities to build API operation managers and objects on top of. -""" - -# E1102: %s is not callable -# pylint: disable=E1102 - -import abc -import copy - -from oslo_utils import strutils -import six -from six.moves.urllib import parse - -from congressclient.openstack.common.apiclient import exceptions -from congressclient.openstack.common.gettextutils import _ - - -def getid(obj): - """Return id if argument is a Resource. - - Abstracts the common pattern of allowing both an object or an object's ID - (UUID) as a parameter when dealing with relationships. - """ - try: - if obj.uuid: - return obj.uuid - except AttributeError: - pass - try: - return obj.id - except AttributeError: - return obj - - -# TODO(aababilov): call run_hooks() in HookableMixin's child classes -class HookableMixin(object): - """Mixin so classes can register and run hooks.""" - _hooks_map = {} - - @classmethod - def add_hook(cls, hook_type, hook_func): - """Add a new hook of specified type. - - :param cls: class that registers hooks - :param hook_type: hook type, e.g., '__pre_parse_args__' - :param hook_func: hook function - """ - if hook_type not in cls._hooks_map: - cls._hooks_map[hook_type] = [] - - cls._hooks_map[hook_type].append(hook_func) - - @classmethod - def run_hooks(cls, hook_type, *args, **kwargs): - """Run all hooks of specified type. - - :param cls: class that registers hooks - :param hook_type: hook type, e.g., '__pre_parse_args__' - :param args: args to be passed to every hook function - :param kwargs: kwargs to be passed to every hook function - """ - hook_funcs = cls._hooks_map.get(hook_type) or [] - for hook_func in hook_funcs: - hook_func(*args, **kwargs) - - -class BaseManager(HookableMixin): - """Basic manager type providing common operations. - - Managers interact with a particular type of API (servers, flavors, images, - etc.) and provide CRUD operations for them. - """ - resource_class = None - - def __init__(self, client): - """Initializes BaseManager with `client`. - - :param client: instance of BaseClient descendant for HTTP requests - """ - super(BaseManager, self).__init__() - self.client = client - - def _list(self, url, response_key=None, obj_class=None, json=None): - """List the collection. - - :param url: a partial URL, e.g., '/servers' - :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. - :param obj_class: class for constructing the returned objects - (self.resource_class will be used by default) - :param json: data that will be encoded as JSON and passed in POST - request (GET will be sent by default) - """ - if json: - body = self.client.post(url, json=json).json() - else: - body = self.client.get(url).json() - - if obj_class is None: - obj_class = self.resource_class - - data = body[response_key] if response_key is not None else body - # NOTE(ja): keystone returns values as list as {'values': [ ... ]} - # unlike other services which just return the list... - try: - data = data['values'] - except (KeyError, TypeError): - pass - - return [obj_class(self, res, loaded=True) for res in data if res] - - def _get(self, url, response_key=None): - """Get an object from collection. - - :param url: a partial URL, e.g., '/servers' - :param response_key: the key to be looked up in response dictionary, - e.g., 'server'. If response_key is None - all response body - will be used. - """ - body = self.client.get(url).json() - data = body[response_key] if response_key is not None else body - return self.resource_class(self, data, loaded=True) - - def _head(self, url): - """Retrieve request headers for an object. - - :param url: a partial URL, e.g., '/servers' - """ - resp = self.client.head(url) - return resp.status_code == 204 - - def _post(self, url, json, response_key=None, return_raw=False): - """Create an object. - - :param url: a partial URL, e.g., '/servers' - :param json: data that will be encoded as JSON and passed in POST - request (GET will be sent by default) - :param response_key: the key to be looked up in response dictionary, - e.g., 'server'. If response_key is None - all response body - will be used. - :param return_raw: flag to force returning raw JSON instead of - Python object of self.resource_class - """ - body = self.client.post(url, json=json).json() - data = body[response_key] if response_key is not None else body - if return_raw: - return data - return self.resource_class(self, data) - - def _put(self, url, json=None, response_key=None): - """Update an object with PUT method. - - :param url: a partial URL, e.g., '/servers' - :param json: data that will be encoded as JSON and passed in POST - request (GET will be sent by default) - :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. - """ - resp = self.client.put(url, json=json) - # PUT requests may not return a body - if resp.content: - body = resp.json() - if response_key is not None: - return self.resource_class(self, body[response_key]) - else: - return self.resource_class(self, body) - - def _patch(self, url, json=None, response_key=None): - """Update an object with PATCH method. - - :param url: a partial URL, e.g., '/servers' - :param json: data that will be encoded as JSON and passed in POST - request (GET will be sent by default) - :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. - """ - body = self.client.patch(url, json=json).json() - if response_key is not None: - return self.resource_class(self, body[response_key]) - else: - return self.resource_class(self, body) - - def _delete(self, url): - """Delete an object. - - :param url: a partial URL, e.g., '/servers/my-server' - """ - return self.client.delete(url) - - -@six.add_metaclass(abc.ABCMeta) -class ManagerWithFind(BaseManager): - """Manager with additional `find()`/`findall()` methods.""" - - @abc.abstractmethod - def list(self): - pass - - def find(self, **kwargs): - """Find a single item with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - matches = self.findall(**kwargs) - num_matches = len(matches) - if num_matches == 0: - msg = _("No %(name)s matching %(args)s.") % { - 'name': self.resource_class.__name__, - 'args': kwargs - } - raise exceptions.NotFound(msg) - elif num_matches > 1: - raise exceptions.NoUniqueMatch() - else: - return matches[0] - - def findall(self, **kwargs): - """Find all items with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - found = [] - searches = kwargs.items() - - for obj in self.list(): - try: - if all(getattr(obj, attr) == value - for (attr, value) in searches): - found.append(obj) - except AttributeError: - continue - - return found - - -class CrudManager(BaseManager): - """Base manager class for manipulating entities. - - Children of this class are expected to define a `collection_key` and `key`. - - - `collection_key`: Usually a plural noun by convention (e.g. `entities`); - used to refer collections in both URL's (e.g. `/v3/entities`) and JSON - objects containing a list of member resources (e.g. `{'entities': [{}, - {}, {}]}`). - - `key`: Usually a singular noun by convention (e.g. `entity`); used to - refer to an individual member of the collection. - - """ - collection_key = None - key = None - - def build_url(self, base_url=None, **kwargs): - """Builds a resource URL for the given kwargs. - - Given an example collection where `collection_key = 'entities'` and - `key = 'entity'`, the following URL's could be generated. - - By default, the URL will represent a collection of entities, e.g.:: - - /entities - - If kwargs contains an `entity_id`, then the URL will represent a - specific member, e.g.:: - - /entities/{entity_id} - - :param base_url: if provided, the generated URL will be appended to it - """ - url = base_url if base_url is not None else '' - - url += '/%s' % self.collection_key - - # do we have a specific entity? - entity_id = kwargs.get('%s_id' % self.key) - if entity_id is not None: - url += '/%s' % entity_id - - return url - - def _filter_kwargs(self, kwargs): - """Drop null values and handle ids.""" - for key, ref in six.iteritems(kwargs.copy()): - if ref is None: - kwargs.pop(key) - else: - if isinstance(ref, Resource): - kwargs.pop(key) - kwargs['%s_id' % key] = getid(ref) - return kwargs - - def create(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._post( - self.build_url(**kwargs), - {self.key: kwargs}, - self.key) - - def get(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._get( - self.build_url(**kwargs), - self.key) - - def head(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - return self._head(self.build_url(**kwargs)) - - def list(self, base_url=None, **kwargs): - """List the collection. - - :param base_url: if provided, the generated URL will be appended to it - """ - kwargs = self._filter_kwargs(kwargs) - - return self._list( - '%(base_url)s%(query)s' % { - 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', - }, - self.collection_key) - - def put(self, base_url=None, **kwargs): - """Update an element. - - :param base_url: if provided, the generated URL will be appended to it - """ - kwargs = self._filter_kwargs(kwargs) - - return self._put(self.build_url(base_url=base_url, **kwargs)) - - def update(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - params = kwargs.copy() - params.pop('%s_id' % self.key) - - return self._patch( - self.build_url(**kwargs), - {self.key: params}, - self.key) - - def delete(self, **kwargs): - kwargs = self._filter_kwargs(kwargs) - - return self._delete( - self.build_url(**kwargs)) - - def find(self, base_url=None, **kwargs): - """Find a single item with attributes matching ``**kwargs``. - - :param base_url: if provided, the generated URL will be appended to it - """ - kwargs = self._filter_kwargs(kwargs) - - rl = self._list( - '%(base_url)s%(query)s' % { - 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', - }, - self.collection_key) - num = len(rl) - - if num == 0: - msg = _("No %(name)s matching %(args)s.") % { - 'name': self.resource_class.__name__, - 'args': kwargs - } - raise exceptions.NotFound(404, msg) - elif num > 1: - raise exceptions.NoUniqueMatch - else: - return rl[0] - - -class Extension(HookableMixin): - """Extension descriptor.""" - - SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') - manager_class = None - - def __init__(self, name, module): - super(Extension, self).__init__() - self.name = name - self.module = module - self._parse_extension_module() - - def _parse_extension_module(self): - self.manager_class = None - for attr_name, attr_value in self.module.__dict__.items(): - if attr_name in self.SUPPORTED_HOOKS: - self.add_hook(attr_name, attr_value) - else: - try: - if issubclass(attr_value, BaseManager): - self.manager_class = attr_value - except TypeError: - pass - - def __repr__(self): - return "" % self.name - - -class Resource(object): - """Base class for OpenStack resources (tenant, user, etc.). - - This is pretty much just a bag for attributes. - """ - - HUMAN_ID = False - NAME_ATTR = 'name' - - def __init__(self, manager, info, loaded=False): - """Populate and bind to a manager. - - :param manager: BaseManager object - :param info: dictionary representing resource attributes - :param loaded: prevent lazy-loading if set to True - """ - self.manager = manager - self._info = info - self._add_details(info) - self._loaded = loaded - - def __repr__(self): - reprkeys = sorted(k - for k in self.__dict__.keys() - if k[0] != '_' and k != 'manager') - info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) - return "<%s %s>" % (self.__class__.__name__, info) - - @property - def human_id(self): - """Human-readable ID which can be used for bash completion. - """ - if self.HUMAN_ID: - name = getattr(self, self.NAME_ATTR, None) - if name is not None: - return strutils.to_slug(name) - return None - - def _add_details(self, info): - for (k, v) in six.iteritems(info): - try: - setattr(self, k, v) - self._info[k] = v - except AttributeError: - # In this case we already defined the attribute on the class - pass - - def __getattr__(self, k): - if k not in self.__dict__: - # NOTE(bcwaldon): disallow lazy-loading if already loaded once - if not self.is_loaded(): - self.get() - return self.__getattr__(k) - - raise AttributeError(k) - else: - return self.__dict__[k] - - def get(self): - """Support for lazy loading details. - - Some clients, such as novaclient have the option to lazy load the - details, details which can be loaded with this function. - """ - # set_loaded() first ... so if we have to bail, we know we tried. - self.set_loaded(True) - if not hasattr(self.manager, 'get'): - return - - new = self.manager.get(self.id) - if new: - self._add_details(new._info) - - def __eq__(self, other): - if not isinstance(other, Resource): - return NotImplemented - # two resources of different types are not equal - if not isinstance(other, self.__class__): - return False - if hasattr(self, 'id') and hasattr(other, 'id'): - return self.id == other.id - return self._info == other._info - - def __ne__(self, other): - return not self == other - - def is_loaded(self): - return self._loaded - - def set_loaded(self, val): - self._loaded = val - - def to_dict(self): - return copy.deepcopy(self._info) diff -Nru python-congressclient-1.5.0/congressclient/openstack/common/apiclient/client.py python-congressclient-1.6.0/congressclient/openstack/common/apiclient/client.py --- python-congressclient-1.5.0/congressclient/openstack/common/apiclient/client.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/openstack/common/apiclient/client.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,345 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 OpenStack Foundation -# Copyright 2011 Piston Cloud Computing, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 Grid Dynamics -# Copyright 2013 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, 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. - -""" -OpenStack Client interface. Handles the REST calls and responses. -""" - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -import logging -import time - -from oslo_utils import importutils - -try: - import simplejson as json -except ImportError: - import json - -import requests - -from congressclient.openstack.common.apiclient import exceptions -from congressclient.openstack.common.gettextutils import _ - - -_logger = logging.getLogger(__name__) - - -class HTTPClient(object): - """This client handles sending HTTP requests to OpenStack servers. - - Features: - - - share authentication information between several clients to different - services (e.g., for compute and image clients); - - reissue authentication request for expired tokens; - - encode/decode JSON bodies; - - raise exceptions on HTTP errors; - - pluggable authentication; - - store authentication information in a keyring; - - store time spent for requests; - - register clients for particular services, so one can use - `http_client.identity` or `http_client.compute`; - - log requests and responses in a format that is easy to copy-and-paste - into terminal and send the same request with curl. - """ - - user_agent = "congressclient.openstack.common.apiclient" - - def __init__(self, - auth_plugin, - region_name=None, - endpoint_type="publicURL", - original_ip=None, - verify=True, - cert=None, - timeout=None, - timings=False, - keyring_saver=None, - debug=False, - user_agent=None, - http=None): - self.auth_plugin = auth_plugin - - self.endpoint_type = endpoint_type - self.region_name = region_name - - self.original_ip = original_ip - self.timeout = timeout - self.verify = verify - self.cert = cert - - self.keyring_saver = keyring_saver - self.debug = debug - self.user_agent = user_agent or self.user_agent - - self.times = [] # [("item", starttime, endtime), ...] - self.timings = timings - - # requests within the same session can reuse TCP connections from pool - self.http = http or requests.Session() - - self.cached_token = None - - def _http_log_req(self, method, url, kwargs): - if not self.debug: - return - - string_parts = [ - "curl -i", - "-X '%s'" % method, - "'%s'" % url, - ] - - for element in kwargs['headers']: - header = "-H '%s: %s'" % (element, kwargs['headers'][element]) - string_parts.append(header) - - _logger.debug("REQ: %s" % " ".join(string_parts)) - if 'data' in kwargs: - _logger.debug("REQ BODY: %s\n" % (kwargs['data'])) - - def _http_log_resp(self, resp): - if not self.debug: - return - _logger.debug( - "RESP: [%s] %s\n", - resp.status_code, - resp.headers) - if resp._content_consumed: - _logger.debug( - "RESP BODY: %s\n", - resp.text) - - def serialize(self, kwargs): - if kwargs.get('json') is not None: - kwargs['headers']['Content-Type'] = 'application/json' - kwargs['data'] = json.dumps(kwargs['json']) - try: - del kwargs['json'] - except KeyError: - pass - - def get_timings(self): - return self.times - - def reset_timings(self): - self.times = [] - - def request(self, method, url, **kwargs): - """Send an http request with the specified characteristics. - - Wrapper around `requests.Session.request` to handle tasks such as - setting headers, JSON encoding/decoding, and error handling. - - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - requests.Session.request (such as `headers`) or `json` - that will be encoded as JSON and used as `data` argument - """ - kwargs.setdefault("headers", kwargs.get("headers", {})) - kwargs["headers"]["User-Agent"] = self.user_agent - if self.original_ip: - kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( - self.original_ip, self.user_agent) - if self.timeout is not None: - kwargs.setdefault("timeout", self.timeout) - kwargs.setdefault("verify", self.verify) - if self.cert is not None: - kwargs.setdefault("cert", self.cert) - self.serialize(kwargs) - - self._http_log_req(method, url, kwargs) - if self.timings: - start_time = time.time() - resp = self.http.request(method, url, **kwargs) - if self.timings: - self.times.append(("%s %s" % (method, url), - start_time, time.time())) - self._http_log_resp(resp) - - if resp.status_code >= 400: - _logger.debug( - "Request returned failure status: %s", - resp.status_code) - raise exceptions.from_response(resp, method, url) - - return resp - - @staticmethod - def concat_url(endpoint, url): - """Concatenate endpoint and final URL. - - E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to - "http://keystone/v2.0/tokens". - - :param endpoint: the base URL - :param url: the final URL - """ - return "%s/%s" % (endpoint.rstrip("/"), url.strip("/")) - - def client_request(self, client, method, url, **kwargs): - """Send an http request using `client`'s endpoint and specified `url`. - - If request was rejected as unauthorized (possibly because the token is - expired), issue one authorization attempt and send the request once - again. - - :param client: instance of BaseClient descendant - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - `HTTPClient.request` - """ - - filter_args = { - "endpoint_type": client.endpoint_type or self.endpoint_type, - "service_type": client.service_type, - } - token, endpoint = (self.cached_token, client.cached_endpoint) - just_authenticated = False - if not (token and endpoint): - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - pass - if not (token and endpoint): - self.authenticate() - just_authenticated = True - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - if not (token and endpoint): - raise exceptions.AuthorizationFailure( - _("Cannot find endpoint or token for request")) - - old_token_endpoint = (token, endpoint) - kwargs.setdefault("headers", {})["X-Auth-Token"] = token - self.cached_token = token - client.cached_endpoint = endpoint - # Perform the request once. If we get Unauthorized, then it - # might be because the auth token expired, so try to - # re-authenticate and try again. If it still fails, bail. - try: - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - except exceptions.Unauthorized as unauth_ex: - if just_authenticated: - raise - self.cached_token = None - client.cached_endpoint = None - self.authenticate() - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - raise unauth_ex - if (not (token and endpoint) or - old_token_endpoint == (token, endpoint)): - raise unauth_ex - self.cached_token = token - client.cached_endpoint = endpoint - kwargs["headers"]["X-Auth-Token"] = token - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - - def add_client(self, base_client_instance): - """Add a new instance of :class:`BaseClient` descendant. - - `self` will store a reference to `base_client_instance`. - """ - service_type = base_client_instance.service_type - if service_type and not hasattr(self, service_type): - setattr(self, service_type, base_client_instance) - - def authenticate(self): - self.auth_plugin.authenticate(self) - # Store the authentication results in the keyring for later requests - if self.keyring_saver: - self.keyring_saver.save(self) - - -class BaseClient(object): - """Top-level object to access the OpenStack API. - - This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient` - will handle a bunch of issues such as authentication. - """ - - service_type = None - endpoint_type = None # "publicURL" will be used - cached_endpoint = None - - def __init__(self, http_client, extensions=None): - self.http_client = http_client - http_client.add_client(self) - - # Add in any extensions... - if extensions: - for extension in extensions: - if extension.manager_class: - setattr(self, extension.name, - extension.manager_class(self)) - - def client_request(self, method, url, **kwargs): - return self.http_client.client_request( - self, method, url, **kwargs) - - def head(self, url, **kwargs): - return self.client_request("HEAD", url, **kwargs) - - def get(self, url, **kwargs): - return self.client_request("GET", url, **kwargs) - - def post(self, url, **kwargs): - return self.client_request("POST", url, **kwargs) - - def put(self, url, **kwargs): - return self.client_request("PUT", url, **kwargs) - - def delete(self, url, **kwargs): - return self.client_request("DELETE", url, **kwargs) - - def patch(self, url, **kwargs): - return self.client_request("PATCH", url, **kwargs) - - @staticmethod - def get_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = _("Invalid %(api_name)s client version '%(version)s'. " - "Must be one of: %(version_map)s") % { - 'api_name': api_name, - 'version': version, - 'version_map': ', '.join(version_map.keys())} - raise exceptions.UnsupportedVersion(msg) - - return importutils.import_class(client_path) diff -Nru python-congressclient-1.5.0/congressclient/openstack/common/apiclient/exceptions.py python-congressclient-1.6.0/congressclient/openstack/common/apiclient/exceptions.py --- python-congressclient-1.5.0/congressclient/openstack/common/apiclient/exceptions.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/openstack/common/apiclient/exceptions.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,466 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 Nebula, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, 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. - -""" -Exception definitions. -""" - -import inspect -import sys - -import six - -from congressclient.openstack.common.gettextutils import _ - - -class ClientException(Exception): - """The base exception class for all exceptions this library raises. - """ - pass - - -class MissingArgs(ClientException): - """Supplied arguments are not sufficient for calling a function.""" - def __init__(self, missing): - self.missing = missing - msg = _("Missing arguments: %s") % ", ".join(missing) - super(MissingArgs, self).__init__(msg) - - -class ValidationError(ClientException): - """Error in validation on API client side.""" - pass - - -class UnsupportedVersion(ClientException): - """User is trying to use an unsupported version of the API.""" - pass - - -class CommandError(ClientException): - """Error in CLI tool.""" - pass - - -class AuthorizationFailure(ClientException): - """Cannot authorize API client.""" - pass - - -class ConnectionRefused(ClientException): - """Cannot connect to API service.""" - pass - - -class AuthPluginOptionsMissing(AuthorizationFailure): - """Auth plugin misses some options.""" - def __init__(self, opt_names): - super(AuthPluginOptionsMissing, self).__init__( - _("Authentication failed. Missing options: %s") % - ", ".join(opt_names)) - self.opt_names = opt_names - - -class AuthSystemNotFound(AuthorizationFailure): - """User has specified an AuthSystem that is not installed.""" - def __init__(self, auth_system): - super(AuthSystemNotFound, self).__init__( - _("AuthSystemNotFound: %s") % repr(auth_system)) - self.auth_system = auth_system - - -class NoUniqueMatch(ClientException): - """Multiple entities found instead of one.""" - pass - - -class EndpointException(ClientException): - """Something is rotten in Service Catalog.""" - pass - - -class EndpointNotFound(EndpointException): - """Could not find requested endpoint in Service Catalog.""" - pass - - -class AmbiguousEndpoints(EndpointException): - """Found more than one matching endpoint in Service Catalog.""" - def __init__(self, endpoints=None): - super(AmbiguousEndpoints, self).__init__( - _("AmbiguousEndpoints: %s") % repr(endpoints)) - self.endpoints = endpoints - - -class HttpError(ClientException): - """The base exception class for all HTTP exceptions. - """ - http_status = 0 - message = _("HTTP Error") - - def __init__(self, message=None, details=None, - response=None, request_id=None, - url=None, method=None, http_status=None): - self.http_status = http_status or self.http_status - self.message = message or self.message - self.details = details - self.request_id = request_id - self.response = response - self.url = url - self.method = method - formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) - if request_id: - formatted_string += " (Request-ID: %s)" % request_id - super(HttpError, self).__init__(formatted_string) - - -class HTTPRedirection(HttpError): - """HTTP Redirection.""" - message = _("HTTP Redirection") - - -class HTTPClientError(HttpError): - """Client-side HTTP error. - - Exception for cases in which the client seems to have erred. - """ - message = _("HTTP Client Error") - - -class HttpServerError(HttpError): - """Server-side HTTP error. - - Exception for cases in which the server is aware that it has - erred or is incapable of performing the request. - """ - message = _("HTTP Server Error") - - -class MultipleChoices(HTTPRedirection): - """HTTP 300 - Multiple Choices. - - Indicates multiple options for the resource that the client may follow. - """ - - http_status = 300 - message = _("Multiple Choices") - - -class BadRequest(HTTPClientError): - """HTTP 400 - Bad Request. - - The request cannot be fulfilled due to bad syntax. - """ - http_status = 400 - message = _("Bad Request") - - -class Unauthorized(HTTPClientError): - """HTTP 401 - Unauthorized. - - Similar to 403 Forbidden, but specifically for use when authentication - is required and has failed or has not yet been provided. - """ - http_status = 401 - message = _("Unauthorized") - - -class PaymentRequired(HTTPClientError): - """HTTP 402 - Payment Required. - - Reserved for future use. - """ - http_status = 402 - message = _("Payment Required") - - -class Forbidden(HTTPClientError): - """HTTP 403 - Forbidden. - - The request was a valid request, but the server is refusing to respond - to it. - """ - http_status = 403 - message = _("Forbidden") - - -class NotFound(HTTPClientError): - """HTTP 404 - Not Found. - - The requested resource could not be found but may be available again - in the future. - """ - http_status = 404 - message = _("Not Found") - - -class MethodNotAllowed(HTTPClientError): - """HTTP 405 - Method Not Allowed. - - A request was made of a resource using a request method not supported - by that resource. - """ - http_status = 405 - message = _("Method Not Allowed") - - -class NotAcceptable(HTTPClientError): - """HTTP 406 - Not Acceptable. - - The requested resource is only capable of generating content not - acceptable according to the Accept headers sent in the request. - """ - http_status = 406 - message = _("Not Acceptable") - - -class ProxyAuthenticationRequired(HTTPClientError): - """HTTP 407 - Proxy Authentication Required. - - The client must first authenticate itself with the proxy. - """ - http_status = 407 - message = _("Proxy Authentication Required") - - -class RequestTimeout(HTTPClientError): - """HTTP 408 - Request Timeout. - - The server timed out waiting for the request. - """ - http_status = 408 - message = _("Request Timeout") - - -class Conflict(HTTPClientError): - """HTTP 409 - Conflict. - - Indicates that the request could not be processed because of conflict - in the request, such as an edit conflict. - """ - http_status = 409 - message = _("Conflict") - - -class Gone(HTTPClientError): - """HTTP 410 - Gone. - - Indicates that the resource requested is no longer available and will - not be available again. - """ - http_status = 410 - message = _("Gone") - - -class LengthRequired(HTTPClientError): - """HTTP 411 - Length Required. - - The request did not specify the length of its content, which is - required by the requested resource. - """ - http_status = 411 - message = _("Length Required") - - -class PreconditionFailed(HTTPClientError): - """HTTP 412 - Precondition Failed. - - The server does not meet one of the preconditions that the requester - put on the request. - """ - http_status = 412 - message = _("Precondition Failed") - - -class RequestEntityTooLarge(HTTPClientError): - """HTTP 413 - Request Entity Too Large. - - The request is larger than the server is willing or able to process. - """ - http_status = 413 - message = _("Request Entity Too Large") - - def __init__(self, *args, **kwargs): - try: - self.retry_after = int(kwargs.pop('retry_after')) - except (KeyError, ValueError): - self.retry_after = 0 - - super(RequestEntityTooLarge, self).__init__(*args, **kwargs) - - -class RequestUriTooLong(HTTPClientError): - """HTTP 414 - Request-URI Too Long. - - The URI provided was too long for the server to process. - """ - http_status = 414 - message = _("Request-URI Too Long") - - -class UnsupportedMediaType(HTTPClientError): - """HTTP 415 - Unsupported Media Type. - - The request entity has a media type which the server or resource does - not support. - """ - http_status = 415 - message = _("Unsupported Media Type") - - -class RequestedRangeNotSatisfiable(HTTPClientError): - """HTTP 416 - Requested Range Not Satisfiable. - - The client has asked for a portion of the file, but the server cannot - supply that portion. - """ - http_status = 416 - message = _("Requested Range Not Satisfiable") - - -class ExpectationFailed(HTTPClientError): - """HTTP 417 - Expectation Failed. - - The server cannot meet the requirements of the Expect request-header field. - """ - http_status = 417 - message = _("Expectation Failed") - - -class UnprocessableEntity(HTTPClientError): - """HTTP 422 - Unprocessable Entity. - - The request was well-formed but was unable to be followed due to semantic - errors. - """ - http_status = 422 - message = _("Unprocessable Entity") - - -class InternalServerError(HttpServerError): - """HTTP 500 - Internal Server Error. - - A generic error message, given when no more specific message is suitable. - """ - http_status = 500 - message = _("Internal Server Error") - - -# NotImplemented is a python keyword. -class HttpNotImplemented(HttpServerError): - """HTTP 501 - Not Implemented. - - The server either does not recognize the request method, or it lacks - the ability to fulfill the request. - """ - http_status = 501 - message = _("Not Implemented") - - -class BadGateway(HttpServerError): - """HTTP 502 - Bad Gateway. - - The server was acting as a gateway or proxy and received an invalid - response from the upstream server. - """ - http_status = 502 - message = _("Bad Gateway") - - -class ServiceUnavailable(HttpServerError): - """HTTP 503 - Service Unavailable. - - The server is currently unavailable. - """ - http_status = 503 - message = _("Service Unavailable") - - -class GatewayTimeout(HttpServerError): - """HTTP 504 - Gateway Timeout. - - The server was acting as a gateway or proxy and did not receive a timely - response from the upstream server. - """ - http_status = 504 - message = _("Gateway Timeout") - - -class HttpVersionNotSupported(HttpServerError): - """HTTP 505 - HttpVersion Not Supported. - - The server does not support the HTTP protocol version used in the request. - """ - http_status = 505 - message = _("HTTP Version Not Supported") - - -# _code_map contains all the classes that have http_status attribute. -_code_map = dict( - (getattr(obj, 'http_status', None), obj) - for name, obj in six.iteritems(vars(sys.modules[__name__])) - if inspect.isclass(obj) and getattr(obj, 'http_status', False) -) - - -def from_response(response, method, url): - """Returns an instance of :class:`HttpError` or subclass based on response. - - :param response: instance of `requests.Response` class - :param method: HTTP method used for request - :param url: URL used for request - """ - - req_id = response.headers.get("x-openstack-request-id") - # NOTE(hdd) true for older versions of nova and cinder - if not req_id: - req_id = response.headers.get("x-compute-request-id") - kwargs = { - "http_status": response.status_code, - "response": response, - "method": method, - "url": url, - "request_id": req_id, - } - if "retry-after" in response.headers: - kwargs["retry_after"] = response.headers["retry-after"] - - content_type = response.headers.get("Content-Type", "") - if content_type.startswith("application/json"): - try: - body = response.json() - except ValueError: - pass - else: - if isinstance(body, dict) and isinstance(body.get("error"), dict): - error = body["error"] - kwargs["message"] = error.get("message") - kwargs["details"] = error.get("details") - elif content_type.startswith("text/"): - kwargs["details"] = response.text - - try: - cls = _code_map[response.status_code] - except KeyError: - if 500 <= response.status_code < 600: - cls = HttpServerError - elif 400 <= response.status_code < 500: - cls = HTTPClientError - else: - cls = HttpError - return cls(**kwargs) diff -Nru python-congressclient-1.5.0/congressclient/openstack/common/apiclient/fake_client.py python-congressclient-1.6.0/congressclient/openstack/common/apiclient/fake_client.py --- python-congressclient-1.5.0/congressclient/openstack/common/apiclient/fake_client.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/openstack/common/apiclient/fake_client.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,178 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, 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. - -""" -A fake server that "responds" to API methods with pre-canned responses. - -All of these responses come from the spec, so if for some reason the spec's -wrong the tests might raise AssertionError. I've indicated in comments the -places where actual behavior differs from the spec. -""" - -# W0102: Dangerous default value %s as argument -# pylint: disable=W0102 - -import json - -import requests -import six -from six.moves.urllib import parse - -from congressclient.openstack.common.apiclient import client - - -def assert_has_keys(dct, required=None, optional=None): - required = required or [] - optional = optional or [] - for k in required: - try: - assert k in dct - except AssertionError: - extra_keys = set(dct.keys()).difference(set(required + optional)) - raise AssertionError("found unexpected keys: %s" % - list(extra_keys)) - - -class TestResponse(requests.Response): - """Wrap requests.Response and provide a convenient initialization. - """ - - def __init__(self, data): - super(TestResponse, self).__init__() - self._content_consumed = True - if isinstance(data, dict): - self.status_code = data.get('status_code', 200) - # Fake the text attribute to streamline Response creation - text = data.get('text', "") - if isinstance(text, (dict, list)): - self._content = json.dumps(text) - default_headers = { - "Content-Type": "application/json", - } - else: - self._content = text - default_headers = {} - if six.PY3 and isinstance(self._content, six.string_types): - self._content = self._content.encode('utf-8', 'strict') - self.headers = data.get('headers') or default_headers - else: - self.status_code = data - - def __eq__(self, other): - return (self.status_code == other.status_code and - self.headers == other.headers and - self._content == other._content) - - def __ne__(self, other): - return not self.__eq__(other) - - -class FakeHTTPClient(client.HTTPClient): - - def __init__(self, *args, **kwargs): - self.callstack = [] - self.fixtures = kwargs.pop("fixtures", None) or {} - if not args and "auth_plugin" not in kwargs: - args = (None, ) - super(FakeHTTPClient, self).__init__(*args, **kwargs) - - def assert_called(self, method, url, body=None, pos=-1): - """Assert than an API method was just called. - """ - expected = (method, url) - called = self.callstack[pos][0:2] - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - assert expected == called, 'Expected %s %s; got %s %s' % \ - (expected + called) - - if body is not None: - if self.callstack[pos][3] != body: - raise AssertionError('%r != %r' % - (self.callstack[pos][3], body)) - - def assert_called_anytime(self, method, url, body=None): - """Assert than an API method was called anytime in the test. - """ - expected = (method, url) - - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - found = False - entry = None - for entry in self.callstack: - if expected == entry[0:2]: - found = True - break - - assert found, 'Expected %s %s; got %s' % \ - (method, url, self.callstack) - if body is not None: - assert entry[3] == body, "%s != %s" % (entry[3], body) - - self.callstack = [] - - def clear_callstack(self): - self.callstack = [] - - def authenticate(self): - pass - - def client_request(self, client, method, url, **kwargs): - # Check that certain things are called correctly - if method in ["GET", "DELETE"]: - assert "json" not in kwargs - - # Note the call - self.callstack.append( - (method, - url, - kwargs.get("headers") or {}, - kwargs.get("json") or kwargs.get("data"))) - try: - fixture = self.fixtures[url][method] - except KeyError: - pass - else: - return TestResponse({"headers": fixture[0], - "text": fixture[1]}) - - # Call the method - args = parse.parse_qsl(parse.urlparse(url)[4]) - kwargs.update(args) - munged_url = url.rsplit('?', 1)[0] - munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') - munged_url = munged_url.replace('-', '_') - - callback = "%s_%s" % (method.lower(), munged_url) - - if not hasattr(self, callback): - raise AssertionError('Called unknown API method: %s %s, ' - 'expected fakes method name: %s' % - (method, url, callback)) - - resp = getattr(self, callback)(**kwargs) - if len(resp) == 3: - status, headers, body = resp - else: - status, body = resp - headers = {} - return TestResponse({ - "status_code": status, - "text": body, - "headers": headers, - }) diff -Nru python-congressclient-1.5.0/congressclient/openstack/common/gettextutils.py python-congressclient-1.6.0/congressclient/openstack/common/gettextutils.py --- python-congressclient-1.5.0/congressclient/openstack/common/gettextutils.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/openstack/common/gettextutils.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,479 +0,0 @@ -# Copyright 2012 Red Hat, Inc. -# Copyright 2013 IBM Corp. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, 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. - -""" -gettext for openstack-common modules. - -Usual usage in an openstack.common module: - - from congressclient.openstack.common.gettextutils import _ -""" - -import copy -import gettext -import locale -from logging import handlers -import os - -from babel import localedata -import six - -_AVAILABLE_LANGUAGES = {} - -# FIXME(dhellmann): Remove this when moving to oslo.i18n. -USE_LAZY = False - - -class TranslatorFactory(object): - """Create translator functions - """ - - def __init__(self, domain, localedir=None): - """Establish a set of translation functions for the domain. - - :param domain: Name of translation domain, - specifying a message catalog. - :type domain: str - :param lazy: Delays translation until a message is emitted. - Defaults to False. - :type lazy: Boolean - :param localedir: Directory with translation catalogs. - :type localedir: str - """ - self.domain = domain - if localedir is None: - localedir = os.environ.get(domain.upper() + '_LOCALEDIR') - self.localedir = localedir - - def _make_translation_func(self, domain=None): - """Return a new translation function ready for use. - - Takes into account whether or not lazy translation is being - done. - - The domain can be specified to override the default from the - factory, but the localedir from the factory is always used - because we assume the log-level translation catalogs are - installed in the same directory as the main application - catalog. - - """ - if domain is None: - domain = self.domain - t = gettext.translation(domain, - localedir=self.localedir, - fallback=True) - # Use the appropriate method of the translation object based - # on the python version. - m = t.gettext if six.PY3 else t.ugettext - - def f(msg): - """oslo.i18n.gettextutils translation function.""" - if USE_LAZY: - return Message(msg, domain=domain) - return m(msg) - return f - - @property - def primary(self): - "The default translation function." - return self._make_translation_func() - - def _make_log_translation_func(self, level): - return self._make_translation_func(self.domain + '-log-' + level) - - @property - def log_info(self): - "Translate info-level log messages." - return self._make_log_translation_func('info') - - @property - def log_warning(self): - "Translate warning-level log messages." - return self._make_log_translation_func('warning') - - @property - def log_error(self): - "Translate error-level log messages." - return self._make_log_translation_func('error') - - @property - def log_critical(self): - "Translate critical-level log messages." - return self._make_log_translation_func('critical') - - -# NOTE(dhellmann): When this module moves out of the incubator into -# oslo.i18n, these global variables can be moved to an integration -# module within each application. - -# Create the global translation functions. -_translators = TranslatorFactory('congressclient') - -# The primary translation function using the well-known name "_" -_ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical - -# NOTE(dhellmann): End of globals that will move to the application's -# integration module. - - -def enable_lazy(): - """Convenience function for configuring _() to use lazy gettext - - Call this at the start of execution to enable the gettextutils._ - function to use lazy gettext functionality. This is useful if - your project is importing _ directly instead of using the - gettextutils.install() way of importing the _ function. - """ - global USE_LAZY - USE_LAZY = True - - -def install(domain): - """Install a _() function using the given translation domain. - - Given a translation domain, install a _() function using gettext's - install() function. - - The main difference from gettext.install() is that we allow - overriding the default localedir (e.g. /usr/share/locale) using - a translation-domain-specific environment variable (e.g. - NOVA_LOCALEDIR). - - Note that to enable lazy translation, enable_lazy must be - called. - - :param domain: the translation domain - """ - from six import moves - tf = TranslatorFactory(domain) - moves.builtins.__dict__['_'] = tf.primary - - -class Message(six.text_type): - """A Message object is a unicode object that can be translated. - - Translation of Message is done explicitly using the translate() method. - For all non-translation intents and purposes, a Message is simply unicode, - and can be treated as such. - """ - - def __new__(cls, msgid, msgtext=None, params=None, - domain='congressclient', *args): - """Create a new Message object. - - In order for translation to work gettext requires a message ID, this - msgid will be used as the base unicode text. It is also possible - for the msgid and the base unicode text to be different by passing - the msgtext parameter. - """ - # If the base msgtext is not given, we use the default translation - # of the msgid (which is in English) just in case the system locale is - # not English, so that the base text will be in that locale by default. - if not msgtext: - msgtext = Message._translate_msgid(msgid, domain) - # We want to initialize the parent unicode with the actual object that - # would have been plain unicode if 'Message' was not enabled. - msg = super(Message, cls).__new__(cls, msgtext) - msg.msgid = msgid - msg.domain = domain - msg.params = params - return msg - - def translate(self, desired_locale=None): - """Translate this message to the desired locale. - - :param desired_locale: The desired locale to translate the message to, - if no locale is provided the message will be - translated to the system's default locale. - - :returns: the translated message in unicode - """ - - translated_message = Message._translate_msgid(self.msgid, - self.domain, - desired_locale) - if self.params is None: - # No need for more translation - return translated_message - - # This Message object may have been formatted with one or more - # Message objects as substitution arguments, given either as a single - # argument, part of a tuple, or as one or more values in a dictionary. - # When translating this Message we need to translate those Messages too - translated_params = _translate_args(self.params, desired_locale) - - translated_message = translated_message % translated_params - - return translated_message - - @staticmethod - def _translate_msgid(msgid, domain, desired_locale=None): - if not desired_locale: - system_locale = locale.getdefaultlocale() - # If the system locale is not available to the runtime use English - if not system_locale[0]: - desired_locale = 'en_US' - else: - desired_locale = system_locale[0] - - locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') - lang = gettext.translation(domain, - localedir=locale_dir, - languages=[desired_locale], - fallback=True) - if six.PY3: - translator = lang.gettext - else: - translator = lang.ugettext - - translated_message = translator(msgid) - return translated_message - - def __mod__(self, other): - # When we mod a Message we want the actual operation to be performed - # by the parent class (i.e. unicode()), the only thing we do here is - # save the original msgid and the parameters in case of a translation - params = self._sanitize_mod_params(other) - unicode_mod = super(Message, self).__mod__(params) - modded = Message(self.msgid, - msgtext=unicode_mod, - params=params, - domain=self.domain) - return modded - - def _sanitize_mod_params(self, other): - """Sanitize the object being modded with this Message. - - - Add support for modding 'None' so translation supports it - - Trim the modded object, which can be a large dictionary, to only - those keys that would actually be used in a translation - - Snapshot the object being modded, in case the message is - translated, it will be used as it was when the Message was created - """ - if other is None: - params = (other,) - elif isinstance(other, dict): - # Merge the dictionaries - # Copy each item in case one does not support deep copy. - params = {} - if isinstance(self.params, dict): - for key, val in self.params.items(): - params[key] = self._copy_param(val) - for key, val in other.items(): - params[key] = self._copy_param(val) - else: - params = self._copy_param(other) - return params - - def _copy_param(self, param): - try: - return copy.deepcopy(param) - except Exception: - # Fallback to casting to unicode this will handle the - # python code-like objects that can't be deep-copied - return six.text_type(param) - - def __add__(self, other): - msg = _('Message objects do not support addition.') - raise TypeError(msg) - - def __radd__(self, other): - return self.__add__(other) - - if six.PY2: - def __str__(self): - # NOTE(luisg): Logging in python 2.6 tries to str() log records, - # and it expects specifically a UnicodeError in order to proceed. - msg = _('Message objects do not support str() because they may ' - 'contain non-ascii characters. ' - 'Please use unicode() or translate() instead.') - raise UnicodeError(msg) - - -def get_available_languages(domain): - """Lists the available languages for the given translation domain. - - :param domain: the domain to get languages for - """ - if domain in _AVAILABLE_LANGUAGES: - return copy.copy(_AVAILABLE_LANGUAGES[domain]) - - localedir = '%s_LOCALEDIR' % domain.upper() - find = lambda x: gettext.find(domain, - localedir=os.environ.get(localedir), - languages=[x]) - - # NOTE(mrodden): en_US should always be available (and first in case - # order matters) since our in-line message strings are en_US - language_list = ['en_US'] - # NOTE(luisg): Babel <1.0 used a function called list(), which was - # renamed to locale_identifiers() in >=1.0, the requirements master list - # requires >=0.9.6, uncapped, so defensively work with both. We can remove - # this check when the master list updates to >=1.0, and update all projects - list_identifiers = (getattr(localedata, 'list', None) or - getattr(localedata, 'locale_identifiers')) - locale_identifiers = list_identifiers() - - for i in locale_identifiers: - if find(i) is not None: - language_list.append(i) - - # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported - # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they - # are perfectly legitimate locales: - # https://github.com/mitsuhiko/babel/issues/37 - # In Babel 1.3 they fixed the bug and they support these locales, but - # they are still not explicitly "listed" by locale_identifiers(). - # That is why we add the locales here explicitly if necessary so that - # they are listed as supported. - aliases = {'zh': 'zh_CN', - 'zh_Hant_HK': 'zh_HK', - 'zh_Hant': 'zh_TW', - 'fil': 'tl_PH'} - for (locale_, alias) in six.iteritems(aliases): - if locale_ in language_list and alias not in language_list: - language_list.append(alias) - - _AVAILABLE_LANGUAGES[domain] = language_list - return copy.copy(language_list) - - -def translate(obj, desired_locale=None): - """Gets the translated unicode representation of the given object. - - If the object is not translatable it is returned as-is. - If the locale is None the object is translated to the system locale. - - :param obj: the object to translate - :param desired_locale: the locale to translate the message to, if None the - default system locale will be used - :returns: the translated object in unicode, or the original object if - it could not be translated - """ - message = obj - if not isinstance(message, Message): - # If the object to translate is not already translatable, - # let's first get its unicode representation - message = six.text_type(obj) - if isinstance(message, Message): - # Even after unicoding() we still need to check if we are - # running with translatable unicode before translating - return message.translate(desired_locale) - return obj - - -def _translate_args(args, desired_locale=None): - """Translates all the translatable elements of the given arguments object. - - This method is used for translating the translatable values in method - arguments which include values of tuples or dictionaries. - If the object is not a tuple or a dictionary the object itself is - translated if it is translatable. - - If the locale is None the object is translated to the system locale. - - :param args: the args to translate - :param desired_locale: the locale to translate the args to, if None the - default system locale will be used - :returns: a new args object with the translated contents of the original - """ - if isinstance(args, tuple): - return tuple(translate(v, desired_locale) for v in args) - if isinstance(args, dict): - translated_dict = {} - for (k, v) in six.iteritems(args): - translated_v = translate(v, desired_locale) - translated_dict[k] = translated_v - return translated_dict - return translate(args, desired_locale) - - -class TranslationHandler(handlers.MemoryHandler): - """Handler that translates records before logging them. - - The TranslationHandler takes a locale and a target logging.Handler object - to forward LogRecord objects to after translating them. This handler - depends on Message objects being logged, instead of regular strings. - - The handler can be configured declaratively in the logging.conf as follows: - - [handlers] - keys = translatedlog, translator - - [handler_translatedlog] - class = handlers.WatchedFileHandler - args = ('/var/log/api-localized.log',) - formatter = context - - [handler_translator] - class = openstack.common.log.TranslationHandler - target = translatedlog - args = ('zh_CN',) - - If the specified locale is not available in the system, the handler will - log in the default locale. - """ - - def __init__(self, locale=None, target=None): - """Initialize a TranslationHandler - - :param locale: locale to use for translating messages - :param target: logging.Handler object to forward - LogRecord objects to after translation - """ - # NOTE(luisg): In order to allow this handler to be a wrapper for - # other handlers, such as a FileHandler, and still be able to - # configure it using logging.conf, this handler has to extend - # MemoryHandler because only the MemoryHandlers' logging.conf - # parsing is implemented such that it accepts a target handler. - handlers.MemoryHandler.__init__(self, capacity=0, target=target) - self.locale = locale - - def setFormatter(self, fmt): - self.target.setFormatter(fmt) - - def emit(self, record): - # We save the message from the original record to restore it - # after translation, so other handlers are not affected by this - original_msg = record.msg - original_args = record.args - - try: - self._translate_and_log_record(record) - finally: - record.msg = original_msg - record.args = original_args - - def _translate_and_log_record(self, record): - record.msg = translate(record.msg, self.locale) - - # In addition to translating the message, we also need to translate - # arguments that were passed to the log method that were not part - # of the main message e.g., log.info(_('Some message %s'), this_one)) - record.args = _translate_args(record.args, self.locale) - - self.target.emit(record) diff -Nru python-congressclient-1.5.0/congressclient/openstack/common/__init__.py python-congressclient-1.6.0/congressclient/openstack/common/__init__.py --- python-congressclient-1.5.0/congressclient/openstack/common/__init__.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/openstack/common/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, 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. - -import six - - -six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) diff -Nru python-congressclient-1.5.0/congressclient/osc/osc_plugin.py python-congressclient-1.6.0/congressclient/osc/osc_plugin.py --- python-congressclient-1.5.0/congressclient/osc/osc_plugin.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/osc/osc_plugin.py 2017-01-24 03:25:31.000000000 +0000 @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +"""OpenStackClient plugin for Governance service.""" + import logging from congressclient.common import utils diff -Nru python-congressclient-1.5.0/congressclient/osc/v1/datasource.py python-congressclient-1.6.0/congressclient/osc/v1/datasource.py --- python-congressclient-1.5.0/congressclient/osc/v1/datasource.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/osc/v1/datasource.py 2017-01-24 03:25:31.000000000 +0000 @@ -62,10 +62,8 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.congressclient - results = client.list_datasources() - datasource_id = utils.get_resource_id_from_name( - parsed_args.datasource_name, results) - data = client.list_datasource_tables(datasource_id)['results'] + name_or_id = parsed_args.datasource_name + data = client.list_datasource_tables(name_or_id)['results'] columns = ['id'] formatters = {'DatasourceTables': utils.format_list} return (columns, @@ -90,11 +88,7 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.congressclient - - results = client.list_datasources() - datasource_id = utils.get_resource_id_from_name( - parsed_args.datasource_name, results) - + datasource_id = parsed_args.datasource_name data = client.list_datasource_status(datasource_id) return zip(*sorted(six.iteritems(data))) @@ -119,11 +113,7 @@ parsed_args.max_width = 40 client = self.app.client_manager.congressclient - - results = client.list_datasources() - datasource_id = utils.get_resource_id_from_name( - parsed_args.datasource_name, results) - + datasource_id = parsed_args.datasource_name data = client.list_datasource_actions(datasource_id) formatters = {'args': utils.format_long_dict_list} newdata = [{'action': x['name'], @@ -153,9 +143,7 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.congressclient - results = client.list_datasources() - datasource_id = utils.get_resource_id_from_name( - parsed_args.datasource_name, results) + datasource_id = parsed_args.datasource_name data = client.show_datasource_schema(datasource_id) formatters = {'columns': utils.format_long_dict_list} newdata = [{'table': x['table_id'], @@ -188,9 +176,7 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.congressclient - results = client.list_datasources() - datasource_id = utils.get_resource_id_from_name( - parsed_args.datasource_name, results) + datasource_id = parsed_args.datasource_name data = client.show_datasource_table_schema( datasource_id, parsed_args.table_name) @@ -220,10 +206,7 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.congressclient - - results = client.list_datasources() - datasource_id = utils.get_resource_id_from_name( - parsed_args.datasource_name, results) + datasource_id = parsed_args.datasource_name results = client.list_datasource_rows(datasource_id, parsed_args.table)['results'] if results: @@ -317,10 +300,16 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.congressclient - results = client.list_datasources() - datasource_id = utils.get_resource_id_from_name( - parsed_args.datasource, results) - client.delete_datasource(datasource_id) + try: + datasource_id = parsed_args.datasource + client.delete_datasource(datasource_id) + except Exception: + # for backwards compatibility with pre-Ocata congress server, + # try old method of explicit conversion from name to UUID + results = client.list_datasources() + datasource_id = utils.get_resource_id_from_name( + parsed_args.datasource, results) + client.delete_datasource(datasource_id) class UpdateDatasourceRow(command.Command): diff -Nru python-congressclient-1.5.0/congressclient/tests/base.py python-congressclient-1.6.0/congressclient/tests/base.py --- python-congressclient-1.5.0/congressclient/tests/base.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/tests/base.py 2017-01-24 03:25:31.000000000 +0000 @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # Copyright 2010-2011 OpenStack Foundation # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # diff -Nru python-congressclient-1.5.0/congressclient/tests/test_base.py python-congressclient-1.6.0/congressclient/tests/test_base.py --- python-congressclient-1.5.0/congressclient/tests/test_base.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/tests/test_base.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, 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. - -from congressclient.openstack.common.apiclient import base as common_base -from congressclient.tests import utils - - -class BaseTest(utils.TestCase): - - def test_resource_repr(self): - r = common_base.Resource(None, dict(foo="bar", baz="spam")) - self.assertEqual(repr(r), "") - - def test_eq(self): - # Two resources of the same type with the same id: equal - r1 = common_base.Resource(None, {'id': 1, 'name': 'hi'}) - r2 = common_base.Resource(None, {'id': 1, 'name': 'hello'}) - self.assertEqual(r1, r2) - - # Two resources with no ID: equal if their info is equal - r1 = common_base.Resource(None, {'name': 'joe', 'age': 12}) - r2 = common_base.Resource(None, {'name': 'joe', 'age': 12}) - self.assertEqual(r1, r2) - - # Two resources of different types: never equal - r1 = common_base.Resource(None, {'id': 1}) - self.assertNotEqual(r1, object()) - self.assertNotEqual(r1, {'id': 1}) diff -Nru python-congressclient-1.5.0/congressclient/tests/v1/test_datasource.py python-congressclient-1.6.0/congressclient/tests/v1/test_datasource.py --- python-congressclient-1.5.0/congressclient/tests/v1/test_datasource.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/tests/v1/test_datasource.py 2017-01-24 03:25:31.000000000 +0000 @@ -96,16 +96,13 @@ {"id": "networks"}] } lister = mock.Mock(return_value=response) - self.app.client_manager.congressclient.list_datasources = mock.Mock() self.app.client_manager.congressclient.list_datasource_tables = lister cmd = datasource.ListDatasourceTables(self.app, self.namespace) parsed_args = self.check_parser(cmd, arglist, verifylist) - with mock.patch.object(utils, "get_resource_id_from_name", - return_value="id"): - result = cmd.take_action(parsed_args) - lister.assert_called_with("id") + result = cmd.take_action(parsed_args) + lister.assert_called_with(datasource_name) self.assertEqual(['id'], result[0]) @@ -122,15 +119,12 @@ 'last_error': "None"} lister = mock.Mock(return_value=response) self.app.client_manager.congressclient.list_datasource_status = lister - self.app.client_manager.congressclient.list_datasources = mock.Mock() cmd = datasource.ShowDatasourceStatus(self.app, self.namespace) parsed_args = self.check_parser(cmd, arglist, verifylist) - with mock.patch.object(utils, "get_resource_id_from_name", - return_value="id"): - result = list(cmd.take_action(parsed_args)) + result = list(cmd.take_action(parsed_args)) - lister.assert_called_with("id") + lister.assert_called_with(datasource_name) self.assertEqual([('last_error', 'last_updated'), ('None', 'now')], result) @@ -155,15 +149,12 @@ } lister = mock.Mock(return_value=response) self.app.client_manager.congressclient.list_datasource_actions = lister - self.app.client_manager.congressclient.list_datasources = mock.Mock() cmd = datasource.ShowDatasourceActions(self.app, self.namespace) parsed_args = self.check_parser(cmd, arglist, verifylist) - with mock.patch.object(utils, "get_resource_id_from_name", - return_value="id"): - result = cmd.take_action(parsed_args) + result = cmd.take_action(parsed_args) - lister.assert_called_once_with("id") + lister.assert_called_once_with(datasource_name) self.assertEqual(['action', 'args', 'description'], result[0]) @@ -189,15 +180,12 @@ } lister = mock.Mock(return_value=response) self.app.client_manager.congressclient.show_datasource_schema = lister - self.app.client_manager.congressclient.list_datasources = mock.Mock() cmd = datasource.ShowDatasourceSchema(self.app, self.namespace) parsed_args = self.check_parser(cmd, arglist, verifylist) - with mock.patch.object(utils, "get_resource_id_from_name", - return_value="id"): - result = cmd.take_action(parsed_args) + result = cmd.take_action(parsed_args) - lister.assert_called_with("id") + lister.assert_called_with(datasource_name) self.assertEqual(['table', 'columns'], result[0]) @@ -224,11 +212,9 @@ cmd = datasource.ShowDatasourceTableSchema(self.app, self.namespace) parsed_args = self.check_parser(cmd, arglist, verifylist) - with mock.patch.object(utils, "get_resource_id_from_name", - return_value="id"): - result = cmd.take_action(parsed_args) + result = cmd.take_action(parsed_args) - lister.assert_called_with("id", table_name) + lister.assert_called_with(datasource_name, table_name) self.assertEqual(['name', 'description'], result[0]) @@ -256,18 +242,15 @@ client = self.app.client_manager.congressclient lister = mock.Mock(return_value=response) - self.app.client_manager.congressclient.list_datasources = mock.Mock() client.list_datasource_rows = lister schema_lister = mock.Mock(return_value=schema_response) client.show_datasource_table_schema = schema_lister cmd = datasource.ListDatasourceRows(self.app, self.namespace) parsed_args = self.check_parser(cmd, arglist, verifylist) - with mock.patch.object(utils, "get_resource_id_from_name", - return_value="id"): - result = cmd.take_action(parsed_args) + result = cmd.take_action(parsed_args) - lister.assert_called_with('id', table_name) + lister.assert_called_with(datasource_name, table_name) self.assertEqual(['ID', 'name'], result[0]) @@ -352,13 +335,10 @@ mocker = mock.Mock(return_value=None) self.app.client_manager.congressclient.delete_datasource = mocker - self.app.client_manager.congressclient.list_datasources = mock.Mock() cmd = datasource.DeleteDatasource(self.app, self.namespace) parsed_args = self.check_parser(cmd, arglist, verifylist) - with mock.patch.object(utils, "get_resource_id_from_name", - return_value="id"): - result = cmd.take_action(parsed_args) - mocker.assert_called_with("id") + result = cmd.take_action(parsed_args) + mocker.assert_called_with(driver) self.assertIsNone(result) diff -Nru python-congressclient-1.5.0/congressclient/v1/client.py python-congressclient-1.6.0/congressclient/v1/client.py --- python-congressclient-1.5.0/congressclient/v1/client.py 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/congressclient/v1/client.py 2017-01-24 03:25:31.000000000 +0000 @@ -127,6 +127,15 @@ resp, body = self.httpclient.get(self.datasources) return body + def show_datasource(self, datasource_name): + """Get a single datasource + + Intended for use by Horizon. Not available in CLI + """ + resp, body = self.httpclient.get(self.datasource_path % + (datasource_name)) + return body + def list_datasource_tables(self, datasource_name): resp, body = self.httpclient.get(self.datasource_tables % (datasource_name)) diff -Nru python-congressclient-1.5.0/.coveragerc python-congressclient-1.6.0/.coveragerc --- python-congressclient-1.5.0/.coveragerc 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/.coveragerc 2017-01-24 03:25:31.000000000 +0000 @@ -1,7 +1,7 @@ [run] branch = True source = congressclient -omit = congressclient/tests/*,congressclient/openstack/* +omit = congressclient/tests/* [report] ignore_errors = True diff -Nru python-congressclient-1.5.0/debian/changelog python-congressclient-1.6.0/debian/changelog --- python-congressclient-1.5.0/debian/changelog 2016-09-02 18:04:31.000000000 +0000 +++ python-congressclient-1.6.0/debian/changelog 2017-01-24 16:59:27.000000000 +0000 @@ -1,8 +1,20 @@ -python-congressclient (1.5.0-0ubuntu1~cloud0) xenial-newton; urgency=medium +python-congressclient (1.6.0-0ubuntu1~cloud0) xenial-ocata; urgency=medium * New upstream release for the Ubuntu Cloud Archive. - -- Openstack Ubuntu Testing Bot Fri, 02 Sep 2016 18:04:31 +0000 + -- Openstack Ubuntu Testing Bot Tue, 24 Jan 2017 16:59:27 +0000 + +python-congressclient (1.6.0-0ubuntu1) zesty; urgency=medium + + [ Corey Bryant ] + * d/gbp.conf: Update gbp configuration file. + * d/control: Update Vcs-* links and maintainers. + + [ Chuck Short ] + * New upstream release. + * d/control: Align (Build-)Depends with upstream. + + -- Chuck Short Tue, 24 Jan 2017 11:31:27 -0500 python-congressclient (1.5.0-0ubuntu1) yakkety; urgency=medium diff -Nru python-congressclient-1.5.0/debian/control python-congressclient-1.6.0/debian/control --- python-congressclient-1.5.0/debian/control 2016-09-02 13:32:35.000000000 +0000 +++ python-congressclient-1.6.0/debian/control 2017-01-24 16:31:27.000000000 +0000 @@ -17,46 +17,51 @@ python3-all, python3-pbr (>= 1.8), python3-setuptools, -Build-Depends-Indep: python-babel, - python-cliff (>= 1.15.0), - python-coverage, +Build-Depends-Indep: python-babel (>= 2.3.4), + python-cliff (>= 2.3.0), + python-coverage (>= 4.0), python-fixtures (>= 3.0.0), - python-hacking, - python-keystoneauth1 (>= 2.10.0), + python-hacking (>= 0.10), + python-keystoneauth1 (>= 2.18.0), python-mock (>= 2.0), python-openstackclient, python-oslo.i18n (>= 2.1.0), - python-oslo.log (>= 1.14.0), + python-oslo.log (>= 3.11.0), python-oslo.serialization (>= 1.10.0), - python-oslo.utils (>= 3.16.0), - python-oslosphinx (>= 2.5.0), + python-oslo.utils (>= 3.18.0), + python-oslosphinx (>= 4.7.0), python-requests (>= 2.10.0), python-six (>= 1.9.0), - python-testscenarios, + python-sphinx (>= 1.2.1), + python-testrepository (>= 0.0.18), + python-testscenarios (>= 0.4), python-testtools (>= 1.4.0), - python-webob, - python3-babel, - python3-cliff (>= 1.15.0), - python3-coverage, + python-webob (>= 1.6.0), + python3-babel (>= 2.3.4), + python3-cliff (>= 2.3.0), + python3-coverage (>= 4.0), python3-fixtures (>= 3.0.0), - python3-hacking, - python3-keystoneauth1 (>= 2.10.0), + python3-hacking (>= 0.10), + python3-keystoneauth1 (>= 2.18.0), python3-mock (>= 2.0), python3-openstackclient, python3-oslo.i18n (>= 2.1.0), - python3-oslo.log (>= 1.14.0), + python3-oslo.log (>= 3.11.0), python3-oslo.serialization (>= 1.10.0), - python3-oslo.utils (>= 3.16.0), + python3-oslo.utils (>= 3.18.0), + python3-oslosphinx (>= 4.7.0), python3-requests (>= 2.10.0), python3-six (>= 1.9.0), - python3-testscenarios, + python3-sphinx (>= 1.2.1), + python3-testrepository (>= 0.0.18), + python3-testscenarios (>= 0.4), python3-testtools (>= 1.4.0), - python3-webob, + python3-webob (>= 1.6.0), subunit, - testrepository, + testrepository Standards-Version: 3.9.8 -Vcs-Browser: https://anonscm.debian.org/cgit/openstack/python-congressclient.git/ -Vcs-Git: https://anonscm.debian.org/git/openstack/python-congressclient.git +Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-congressclient +Vcs-Git: git://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-congressclient Homepage: https://github.com/stackforge/python-congressclient Package: python-congressclient @@ -73,7 +78,7 @@ python-requests (>= 2.10.0), python-six (>= 1.9.0), ${misc:Depends}, - ${python:Depends}, + ${python:Depends} Description: client for the open policy framework for the cloud - Python 2.x Congress is an open policy framework for the cloud. With Congress, a cloud operator can declare, monitor, enforce, and audit "policy" in a heterogeneous @@ -100,7 +105,7 @@ python3-requests (>= 2.10.0), python3-six (>= 1.9.0), ${misc:Depends}, - ${python3:Depends}, + ${python3:Depends} Description: client for the open policy framework for the cloud - Python 3.x Congress is an open policy framework for the cloud. With Congress, a cloud operator can declare, monitor, enforce, and audit "policy" in a heterogeneous diff -Nru python-congressclient-1.5.0/debian/gbp.conf python-congressclient-1.6.0/debian/gbp.conf --- python-congressclient-1.5.0/debian/gbp.conf 2016-08-16 19:44:52.000000000 +0000 +++ python-congressclient-1.6.0/debian/gbp.conf 2017-01-24 16:31:27.000000000 +0000 @@ -1,8 +1,7 @@ [DEFAULT] -upstream-branch = master -debian-branch = debian/newton +debian-branch = master upstream-tag = %(version)s -compression = xz +pristine-tar = True [buildpackage] -export-dir = ../build-area/ +export-dir = ../build-area diff -Nru python-congressclient-1.5.0/openstack-common.conf python-congressclient-1.6.0/openstack-common.conf --- python-congressclient-1.5.0/openstack-common.conf 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/openstack-common.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -[DEFAULT] - -# The list of modules to copy from oslo-incubator.git - -# The base module to hold the copy of openstack.common -base=congressclient \ No newline at end of file diff -Nru python-congressclient-1.5.0/README.rst python-congressclient-1.6.0/README.rst --- python-congressclient-1.5.0/README.rst 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/README.rst 2017-01-24 03:25:31.000000000 +0000 @@ -1,3 +1,12 @@ +======================== +Team and repository tags +======================== + +.. image:: http://governance.openstack.org/badges/python-congressclient.svg + :target: http://governance.openstack.org/reference/tags/index.html + +.. Change things from this point on + =============================== python-congressclient =============================== @@ -5,7 +14,7 @@ Client for Congress * Free software: Apache license -* Documentation: http://docs.openstack.org/developer/python-congressclient +* Documentation: https://github.com/openstack/python-congressclient/blob/master/README.rst * Source: http://git.openstack.org/cgit/openstack/python-congressclient * Bugs: http://bugs.launchpad.net/python-congressclient diff -Nru python-congressclient-1.5.0/requirements.txt python-congressclient-1.6.0/requirements.txt --- python-congressclient-1.5.0/requirements.txt 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/requirements.txt 2017-01-24 03:25:31.000000000 +0000 @@ -1,13 +1,13 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.6 # Apache-2.0 +pbr>=1.8 # Apache-2.0 Babel>=2.3.4 # BSD -cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 -keystoneauth1>=2.10.0 # Apache-2.0 +cliff>=2.3.0 # Apache-2.0 +keystoneauth1>=2.18.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.log>=1.14.0 # Apache-2.0 +oslo.log>=3.11.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 -oslo.utils>=3.16.0 # Apache-2.0 -requests>=2.10.0 # Apache-2.0 +oslo.utils>=3.18.0 # Apache-2.0 +requests!=2.12.2,>=2.10.0 # Apache-2.0 six>=1.9.0 # MIT diff -Nru python-congressclient-1.5.0/setup.cfg python-congressclient-1.6.0/setup.cfg --- python-congressclient-1.5.0/setup.cfg 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/setup.cfg 2017-01-24 03:25:31.000000000 +0000 @@ -5,7 +5,7 @@ README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org -home-page = http://www.openstack.org/ +home-page = http://git.openstack.org/cgit/openstack/python-congressclient classifier = Environment :: OpenStack Intended Audience :: Information Technology diff -Nru python-congressclient-1.5.0/test-requirements.txt python-congressclient-1.6.0/test-requirements.txt --- python-congressclient-1.5.0/test-requirements.txt 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/test-requirements.txt 2017-01-24 03:25:31.000000000 +0000 @@ -3,13 +3,13 @@ # process, which may cause wedges in the gate later. hacking<0.10,>=0.9.2 -coverage>=3.6 # Apache-2.0 +coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD python-subunit>=0.0.18 # Apache-2.0/BSD -sphinx!=1.3b1,<1.3,>=1.2.1 # BSD -oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 +sphinx!=1.3b1,<1.4,>=1.2.1 # BSD +oslosphinx>=4.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT mock>=2.0 # BSD -WebOb>=1.2.3 # MIT +WebOb>=1.6.0 # MIT diff -Nru python-congressclient-1.5.0/tools/tox_install.sh python-congressclient-1.6.0/tools/tox_install.sh --- python-congressclient-1.5.0/tools/tox_install.sh 1970-01-01 00:00:00.000000000 +0000 +++ python-congressclient-1.6.0/tools/tox_install.sh 2017-01-24 03:25:31.000000000 +0000 @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Client constraint file contains this client version pin that is in conflict +# with installing the client from source. We should remove the version pin in +# the constraints file before applying it for from-source installation. + +CONSTRAINTS_FILE="$1" +shift 1 + +set -e + +# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get +# published to logs.openstack.org for easy debugging. +localfile="$VIRTUAL_ENV/log/upper-constraints.txt" + +if [[ "$CONSTRAINTS_FILE" != http* ]]; then + CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE" +fi +# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep +curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile" + +pip install -c"$localfile" openstack-requirements + +# This is the main purpose of the script: Allow local installation of +# the current repo. It is listed in constraints file and thus any +# install will be constrained and we need to unconstrain it. +edit-constraints "$localfile" -- "$CLIENT_NAME" + +pip install -c"$localfile" -U "$@" +exit $? diff -Nru python-congressclient-1.5.0/tox.ini python-congressclient-1.6.0/tox.ini --- python-congressclient-1.5.0/tox.ini 2016-09-02 13:28:30.000000000 +0000 +++ python-congressclient-1.6.0/tox.ini 2017-01-24 03:25:31.000000000 +0000 @@ -1,14 +1,16 @@ [tox] -minversion = 1.6 +minversion = 2.0 envlist = py34,py27,pep8 skipsdist = True [testenv] usedevelop = True -install_command = pip install -U {opts} {packages} +install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} whitelist_externals = find setenv = VIRTUAL_ENV={envdir} + BRANCH_NAME=master + CLIENT_NAME=python-congressclient deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = @@ -27,7 +29,10 @@ [testenv:docs] commands = python setup.py build_sphinx +[hacking] +import_exceptions = congressclient.i18n + [flake8] show-source = True builtins = _ -exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build +exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build