Fix and enable Gating on H404

Enable gating on the Hacking H404 check - docstring
should start with a summary.

Change-Id: I80612a15bd11f689e9e9f4dc2ff812138630ddbd
This commit is contained in:
Dirk Mueller
2013-08-04 23:10:16 +02:00
committed by Morgan Fainberg
parent 0e11cf03cf
commit dc5c33a9e5
13 changed files with 81 additions and 145 deletions

View File

@@ -34,9 +34,8 @@ except NameError:
def getid(obj): def getid(obj):
""" """Abstracts the common pattern of allowing both an object or an object's
Abstracts the common pattern of allowing both an object or an object's ID ID (UUID) as a parameter when dealing with relationships.
(UUID) as a parameter when dealing with relationships.
""" """
# Try to return the object's UUID first, if we have a UUID. # Try to return the object's UUID first, if we have a UUID.
@@ -75,9 +74,8 @@ def filter_kwargs(f):
class Manager(object): class Manager(object):
""" """Managers interact with a particular type of API (servers, flavors,
Managers interact with a particular type of API (servers, flavors, images, images, etc.) and provide CRUD operations for them.
etc.) and provide CRUD operations for them.
""" """
resource_class = None resource_class = None
@@ -138,8 +136,7 @@ class Manager(object):
class ManagerWithFind(Manager): class ManagerWithFind(Manager):
""" """Like a `Manager`, but with additional `find()`/`findall()` methods.
Like a `Manager`, but with additional `find()`/`findall()` methods.
""" """
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
@@ -149,8 +146,7 @@ class ManagerWithFind(Manager):
pass pass
def find(self, **kwargs): def find(self, **kwargs):
""" """Find a single item with attributes matching ``**kwargs``.
Find a single item with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on This isn't very efficient: it loads the entire list then filters on
the Python side. the Python side.
@@ -167,8 +163,7 @@ class ManagerWithFind(Manager):
return rl[0] return rl[0]
def findall(self, **kwargs): def findall(self, **kwargs):
""" """Find all items with attributes matching ``**kwargs``.
Find all items with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on This isn't very efficient: it loads the entire list then filters on
the Python side. the Python side.
@@ -287,9 +282,7 @@ class CrudManager(Manager):
@filter_kwargs @filter_kwargs
def find(self, **kwargs): def find(self, **kwargs):
""" """Find a single item with attributes matching ``**kwargs``."""
Find a single item with attributes matching ``**kwargs``.
"""
url = self.build_url(dict_args_in_out=kwargs) url = self.build_url(dict_args_in_out=kwargs)
rl = self._list( rl = self._list(
@@ -310,8 +303,7 @@ class CrudManager(Manager):
class Resource(object): class Resource(object):
""" """A resource represents a particular instance of an object (tenant, user,
A resource represents a particular instance of an object (tenant, user,
etc). This is pretty much just a bag for attributes. etc). This is pretty much just a bag for attributes.
:param manager: Manager object :param manager: Manager object

View File

@@ -28,10 +28,9 @@ import six
class Ec2Signer(object): class Ec2Signer(object):
""" """Utility class which adds allows a request to be signed with an AWS style
Utility class which adds allows a request to be signed with an AWS style
signature, which can then be used for authentication via the keystone ec2 signature, which can then be used for authentication via the keystone ec2
authentication extension authentication extension.
""" """
def __init__(self, secret_key): def __init__(self, secret_key):
@@ -41,9 +40,9 @@ class Ec2Signer(object):
self.hmac_256 = hmac.new(self.secret_key, digestmod=hashlib.sha256) self.hmac_256 = hmac.new(self.secret_key, digestmod=hashlib.sha256)
def _v4_creds(self, credentials): def _v4_creds(self, credentials):
""" """Detect if the credentials are for a v4 signed request, since AWS
Detect if the credentials are for a v4 signed request, since AWS
removed the SignatureVersion field from the v4 request spec... removed the SignatureVersion field from the v4 request spec...
This expects a dict of the request headers to be passed in the This expects a dict of the request headers to be passed in the
credentials dict, since the recommended way to pass v4 creds is credentials dict, since the recommended way to pass v4 creds is
via the 'Authorization' header via the 'Authorization' header
@@ -127,9 +126,8 @@ class Ec2Signer(object):
@staticmethod @staticmethod
def _canonical_qs(params): def _canonical_qs(params):
""" """Construct a sorted, correctly encoded query string as required for
Construct a sorted, correctly encoded query string as required for _calc_signature_2 and _calc_signature_4.
_calc_signature_2 and _calc_signature_4
""" """
keys = params.keys() keys = params.keys()
keys.sort() keys.sort()
@@ -164,8 +162,7 @@ class Ec2Signer(object):
hashlib.sha256).digest() hashlib.sha256).digest()
def signature_key(datestamp, region_name, service_name): def signature_key(datestamp, region_name, service_name):
""" """Signature key derivation, see
Signature key derivation, see
http://docs.aws.amazon.com/general/latest/gr/ http://docs.aws.amazon.com/general/latest/gr/
signature-v4-examples.html#signature-v4-examples-python signature-v4-examples.html#signature-v4-examples-python
""" """
@@ -177,8 +174,9 @@ class Ec2Signer(object):
return k_signing return k_signing
def auth_param(param_name): def auth_param(param_name):
""" """Get specified auth parameter.
Get specified auth parameter, provided via one of:
Provided via one of:
- the Authorization header - the Authorization header
- the X-Amz-* query parameters - the X-Amz-* query parameters
""" """
@@ -191,8 +189,8 @@ class Ec2Signer(object):
return param_str return param_str
def date_param(): def date_param():
""" """Get the X-Amz-Date' value, which can be either a header
Get the X-Amz-Date' value, which can be either a header or paramter or parameter.
Note AWS supports parsing the Date header also, but this is not Note AWS supports parsing the Date header also, but this is not
currently supported here as it will require some format mangling currently supported here as it will require some format mangling

View File

@@ -42,9 +42,8 @@ class NoUniqueMatch(Exception):
class ClientException(Exception): class ClientException(Exception):
""" """The base exception class for all exceptions this library raises."""
The base exception class for all exceptions this library raises.
"""
def __init__(self, code, message=None, details=None): def __init__(self, code, message=None, details=None):
self.code = code self.code = code
self.message = message or self.__class__.message self.message = message or self.__class__.message
@@ -55,24 +54,21 @@ class ClientException(Exception):
class BadRequest(ClientException): class BadRequest(ClientException):
""" """HTTP 400 - Bad request: you sent some malformed data."""
HTTP 400 - Bad request: you sent some malformed data.
"""
http_status = 400 http_status = 400
message = "Bad request" message = "Bad request"
class Unauthorized(ClientException): class Unauthorized(ClientException):
""" """HTTP 401 - Unauthorized: bad credentials."""
HTTP 401 - Unauthorized: bad credentials.
"""
http_status = 401 http_status = 401
message = "Unauthorized" message = "Unauthorized"
class Forbidden(ClientException): class Forbidden(ClientException):
""" """HTTP 403 - Forbidden: your credentials do not allow access to this
HTTP 403 - Forbidden: your credentials don't give you access to this
resource. resource.
""" """
http_status = 403 http_status = 403
@@ -80,32 +76,26 @@ class Forbidden(ClientException):
class NotFound(ClientException): class NotFound(ClientException):
""" """HTTP 404 - Not found."""
HTTP 404 - Not found
"""
http_status = 404 http_status = 404
message = "Not found" message = "Not found"
class MethodNotAllowed(ClientException): class MethodNotAllowed(ClientException):
""" """HTTP 405 - Method not allowed."""
HTTP 405 - Method not allowed
"""
http_status = 405 http_status = 405
message = "Method not allowed" message = "Method not allowed"
class Conflict(ClientException): class Conflict(ClientException):
""" """HTTP 409 - Conflict."""
HTTP 409 - Conflict
"""
http_status = 409 http_status = 409
message = "Conflict" message = "Conflict"
class OverLimit(ClientException): class OverLimit(ClientException):
""" """HTTP 413 - Over limit: you're over the API limits for this time
HTTP 413 - Over limit: you're over the API limits for this time period. period.
""" """
http_status = 413 http_status = 413
message = "Over limit" message = "Over limit"
@@ -113,17 +103,15 @@ class OverLimit(ClientException):
# NotImplemented is a python keyword. # NotImplemented is a python keyword.
class HTTPNotImplemented(ClientException): class HTTPNotImplemented(ClientException):
""" """HTTP 501 - Not Implemented: the server does not support this
HTTP 501 - Not Implemented: the server does not support this operation. operation.
""" """
http_status = 501 http_status = 501
message = "Not Implemented" message = "Not Implemented"
class ServiceUnavailable(ClientException): class ServiceUnavailable(ClientException):
""" """HTTP 503 - Service Unavailable: The server is currently unavailable."""
HTTP 503 - Service Unavailable: The server is currently unavailable.
"""
http_status = 503 http_status = 503
message = "Service Unavailable" message = "Service Unavailable"
@@ -146,9 +134,8 @@ _code_map = dict((c.http_status, c) for c in [BadRequest,
def from_response(response, body=None): def from_response(response, body=None):
""" """Return an instance of a ClientException or subclass
Return an instance of an ClientException or subclass based on a requests response.
based on an requests response.
Usage:: Usage::

View File

@@ -442,8 +442,8 @@ class OpenStackIdentityShell(object):
return shell_v2_0.CLIENT_CLASS return shell_v2_0.CLIENT_CLASS
def do_bash_completion(self, args): def do_bash_completion(self, args):
""" """Prints all of the commands and options to stdout.
Prints all of the commands and options to stdout.
The keystone.bash_completion script doesn't have to hard code them. The keystone.bash_completion script doesn't have to hard code them.
""" """
commands = set() commands = set()
@@ -460,9 +460,7 @@ class OpenStackIdentityShell(object):
@utils.arg('command', metavar='<subcommand>', nargs='?', @utils.arg('command', metavar='<subcommand>', nargs='?',
help='Display help for <subcommand>') help='Display help for <subcommand>')
def do_help(self, args): def do_help(self, args):
""" """Display help about this program or one of its subcommands."""
Display help about this program or one of its subcommands.
"""
if getattr(args, 'command', None): if getattr(args, 'command', None):
if args.command in self.subcommands: if args.command in self.subcommands:
self.subcommands[args.command].print_help() self.subcommands[args.command].print_help()

View File

@@ -113,10 +113,10 @@ def unauthenticated(f):
def isunauthenticated(f): def isunauthenticated(f):
""" """Checks to see if the function is marked as not requiring authentication
Checks to see if the function is marked as not requiring authentication with the @unauthenticated decorator.
with the @unauthenticated decorator. Returns True if decorator is
set to True, False otherwise. Returns True if decorator is set to True, False otherwise.
""" """
return getattr(f, 'unauthenticated', False) return getattr(f, 'unauthenticated', False)
@@ -135,9 +135,8 @@ def hash_signed_token(signed_text):
def prompt_for_password(): def prompt_for_password():
""" """Prompt user for password if not provided so the password
Prompt user for password if not provided so the password doesn't show up in the bash history.
doesn't show up in the bash history.
""" """
if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()): if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()):
# nothing to do # nothing to do

View File

@@ -28,8 +28,8 @@ class CredentialsManager(base.ManagerWithFind):
resource_class = EC2 resource_class = EC2
def create(self, user_id, tenant_id): def create(self, user_id, tenant_id):
""" """Create a new access/secret pair for the user/tenant pair.
Create a new access/secret pair for the user/tenant pair
:rtype: object of type :class:`EC2` :rtype: object of type :class:`EC2`
""" """
@@ -39,24 +39,22 @@ class CredentialsManager(base.ManagerWithFind):
params, "credential") params, "credential")
def list(self, user_id): def list(self, user_id):
""" """Get a list of access/secret pairs for a user_id.
Get a list of access/secret pairs for a user_id
:rtype: list of :class:`EC2` :rtype: list of :class:`EC2`
""" """
return self._list("/users/%s/credentials/OS-EC2" % user_id, return self._list("/users/%s/credentials/OS-EC2" % user_id,
"credentials") "credentials")
def get(self, user_id, access): def get(self, user_id, access):
""" """Get the access/secret pair for a given access key.
Get the access/secret pair for a given access key
:rtype: object of type :class:`EC2` :rtype: object of type :class:`EC2`
""" """
return self._get("/users/%s/credentials/OS-EC2/%s" % return self._get("/users/%s/credentials/OS-EC2/%s" %
(user_id, base.getid(access)), "credential") (user_id, base.getid(access)), "credential")
def delete(self, user_id, access): def delete(self, user_id, access):
""" """Delete an access/secret pair for a user."""
Delete an access/secret pair for a user
"""
return self._delete("/users/%s/credentials/OS-EC2/%s" % return self._delete("/users/%s/credentials/OS-EC2/%s" %
(user_id, base.getid(access))) (user_id, base.getid(access)))

View File

@@ -34,22 +34,16 @@ class RoleManager(base.ManagerWithFind):
return self._get("/OS-KSADM/roles/%s" % base.getid(role), "role") return self._get("/OS-KSADM/roles/%s" % base.getid(role), "role")
def create(self, name): def create(self, name):
""" """Create a role."""
Create a role.
"""
params = {"role": {"name": name}} params = {"role": {"name": name}}
return self._create('/OS-KSADM/roles', params, "role") return self._create('/OS-KSADM/roles', params, "role")
def delete(self, role): def delete(self, role):
""" """Delete a role."""
Delete a role.
"""
return self._delete("/OS-KSADM/roles/%s" % base.getid(role)) return self._delete("/OS-KSADM/roles/%s" % base.getid(role))
def list(self): def list(self):
""" """List all available roles."""
List all available roles.
"""
return self._list("/OS-KSADM/roles", "roles") return self._list("/OS-KSADM/roles", "roles")
def roles_for_user(self, user, tenant=None): def roles_for_user(self, user, tenant=None):

View File

@@ -75,10 +75,7 @@ class TenantManager(base.ManagerWithFind):
return self._get("/tenants/%s" % tenant_id, "tenant") return self._get("/tenants/%s" % tenant_id, "tenant")
def create(self, tenant_name, description=None, enabled=True, **kwargs): def create(self, tenant_name, description=None, enabled=True, **kwargs):
""" """Create a new tenant."""
Create a new tenant.
"""
params = {"tenant": {"name": tenant_name, params = {"tenant": {"name": tenant_name,
"description": description, "description": description,
"enabled": enabled}} "enabled": enabled}}
@@ -91,8 +88,7 @@ class TenantManager(base.ManagerWithFind):
return self._create('/tenants', params, "tenant") return self._create('/tenants', params, "tenant")
def list(self, limit=None, marker=None): def list(self, limit=None, marker=None):
""" """Get a list of tenants.
Get a list of tenants.
:param integer limit: maximum number to return. (optional) :param integer limit: maximum number to return. (optional)
:param string marker: use when specifying a limit and making :param string marker: use when specifying a limit and making
@@ -125,9 +121,7 @@ class TenantManager(base.ManagerWithFind):
def update(self, tenant_id, tenant_name=None, description=None, def update(self, tenant_id, tenant_name=None, description=None,
enabled=None, **kwargs): enabled=None, **kwargs):
""" """Update a tenant with a new name and description."""
Update a tenant with a new name and description.
"""
body = {"tenant": {'id': tenant_id}} body = {"tenant": {'id': tenant_id}}
if tenant_name is not None: if tenant_name is not None:
body['tenant']['name'] = tenant_name body['tenant']['name'] = tenant_name
@@ -145,9 +139,7 @@ class TenantManager(base.ManagerWithFind):
return self._create("/tenants/%s" % tenant_id, body, "tenant") return self._create("/tenants/%s" % tenant_id, body, "tenant")
def delete(self, tenant): def delete(self, tenant):
""" """Delete a tenant."""
Delete a tenant.
"""
return self._delete("/tenants/%s" % (base.getid(tenant))) return self._delete("/tenants/%s" % (base.getid(tenant)))
def list_users(self, tenant): def list_users(self, tenant):

View File

@@ -39,8 +39,7 @@ class UserManager(base.ManagerWithFind):
return self._get("/users/%s" % base.getid(user), "user") return self._get("/users/%s" % base.getid(user), "user")
def update(self, user, **kwargs): def update(self, user, **kwargs):
""" """Update user data.
Update user data.
Supported arguments include ``name``, ``email``, and ``enabled``. Supported arguments include ``name``, ``email``, and ``enabled``.
""" """
@@ -52,9 +51,7 @@ class UserManager(base.ManagerWithFind):
return self._update(url, params, "user") return self._update(url, params, "user")
def update_enabled(self, user, enabled): def update_enabled(self, user, enabled):
""" """Update enabled-ness."""
Update enabled-ness
"""
params = {"user": {"id": base.getid(user), params = {"user": {"id": base.getid(user),
"enabled": enabled}} "enabled": enabled}}
@@ -62,9 +59,7 @@ class UserManager(base.ManagerWithFind):
"user") "user")
def update_password(self, user, password): def update_password(self, user, password):
""" """Update password."""
Update password
"""
params = {"user": {"id": base.getid(user), params = {"user": {"id": base.getid(user),
"password": password}} "password": password}}
@@ -72,9 +67,7 @@ class UserManager(base.ManagerWithFind):
params, "user") params, "user")
def update_own_password(self, origpasswd, passwd): def update_own_password(self, origpasswd, passwd):
""" """Update password."""
Update password
"""
params = {"user": {"password": passwd, params = {"user": {"password": passwd,
"original_password": origpasswd}} "original_password": origpasswd}}
@@ -84,9 +77,7 @@ class UserManager(base.ManagerWithFind):
management=False) management=False)
def update_tenant(self, user, tenant): def update_tenant(self, user, tenant):
""" """Update default tenant."""
Update default tenant.
"""
params = {"user": {"id": base.getid(user), params = {"user": {"id": base.getid(user),
"tenantId": base.getid(tenant)}} "tenantId": base.getid(tenant)}}
@@ -96,9 +87,7 @@ class UserManager(base.ManagerWithFind):
params, "user") params, "user")
def create(self, name, password, email, tenant_id=None, enabled=True): def create(self, name, password, email, tenant_id=None, enabled=True):
""" """Create a user."""
Create a user.
"""
# FIXME(ja): email should be optional, keystone currently requires it # FIXME(ja): email should be optional, keystone currently requires it
params = {"user": {"name": name, params = {"user": {"name": name,
"password": password, "password": password,
@@ -108,14 +97,11 @@ class UserManager(base.ManagerWithFind):
return self._create('/users', params, "user") return self._create('/users', params, "user")
def delete(self, user): def delete(self, user):
""" """Delete a user."""
Delete a user.
"""
return self._delete("/users/%s" % base.getid(user)) return self._delete("/users/%s" % base.getid(user))
def list(self, tenant_id=None, limit=None, marker=None): def list(self, tenant_id=None, limit=None, marker=None):
""" """Get a list of users (optionally limited to a tenant).
Get a list of users (optionally limited to a tenant)
:rtype: list of :class:`User` :rtype: list of :class:`User`
""" """

View File

@@ -23,9 +23,7 @@ def assert_has_keys(dict, required=[], optional=[]):
class FakeClient(object): class FakeClient(object):
def assert_called(self, method, url, body=None, pos=-1): def assert_called(self, method, url, body=None, pos=-1):
""" """Assert than an API method was just called."""
Assert than an API method was just called.
"""
expected = (method, url) expected = (method, url)
called = self.callstack[pos][0:2] called = self.callstack[pos][0:2]
@@ -38,9 +36,7 @@ class FakeClient(object):
assert self.callstack[pos][2] == body assert self.callstack[pos][2] == body
def assert_called_anytime(self, method, url, body=None): def assert_called_anytime(self, method, url, body=None):
""" """Assert than an API method was called anytime in the test."""
Assert than an API method was called anytime in the test.
"""
expected = (method, url) expected = (method, url)
assert self.callstack, ("Expected %s %s but no calls were made." % assert self.callstack, ("Expected %s %s but no calls were made." %

View File

@@ -105,8 +105,9 @@ class Ec2SignerTest(testtools.TestCase):
self.assertEqual(signature, expected) self.assertEqual(signature, expected)
def test_generate_v4(self): def test_generate_v4(self):
""" """Test v4 generator with data from AWS docs example.
Test v4 generator with data from AWS docs example, see:
see:
http://docs.aws.amazon.com/general/latest/gr/ http://docs.aws.amazon.com/general/latest/gr/
sigv4-create-canonical-request.html sigv4-create-canonical-request.html
and and
@@ -145,9 +146,7 @@ class Ec2SignerTest(testtools.TestCase):
self.assertEqual(signature, expected) self.assertEqual(signature, expected)
def test_generate_v4_port(self): def test_generate_v4_port(self):
""" """Test v4 generator with host:port format."""
Test v4 generator with host:port format
"""
# Create a new signer object with the AWS example key # Create a new signer object with the AWS example key
secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
signer = Ec2Signer(secret) signer = Ec2Signer(secret)
@@ -181,10 +180,9 @@ class Ec2SignerTest(testtools.TestCase):
self.assertEqual(signature, expected) self.assertEqual(signature, expected)
def test_generate_v4_port_strip(self): def test_generate_v4_port_strip(self):
""" """Test v4 generator with host:port format, but for an old
Test v4 generator with host:port format, but for an old
(<2.9.3) version of boto, where the port should be stripped (<2.9.3) version of boto, where the port should be stripped
to match boto behavior to match boto behavior.
""" """
# Create a new signer object with the AWS example key # Create a new signer object with the AWS example key
secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
@@ -220,9 +218,8 @@ class Ec2SignerTest(testtools.TestCase):
self.assertEqual(expected, signature) self.assertEqual(expected, signature)
def test_generate_v4_port_nostrip(self): def test_generate_v4_port_nostrip(self):
""" """Test v4 generator with host:port format, but for an new
Test v4 generator with host:port format, but for an new (>=2.9.3) version of boto, where the port should not be stripped.
(>=2.9.3) version of boto, where the port should not be stripped
""" """
# Create a new signer object with the AWS example key # Create a new signer object with the AWS example key
secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' secret = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'

View File

@@ -54,8 +54,7 @@ class KeyringTest(utils.TestCase):
keyring.set_keyring(MemoryKeyring()) keyring.set_keyring(MemoryKeyring())
def test_no_keyring_key(self): def test_no_keyring_key(self):
""" """Ensure that we get no value back if we don't have use_keyring
Ensure that we get no value back if we don't have use_keyring
set in the client. set in the client.
""" """
cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD, cl = httpclient.HTTPClient(username=USERNAME, password=PASSWORD,

View File

@@ -34,6 +34,6 @@ downloadcache = ~/cache/pip
# H302: import only modules # H302: import only modules
# H304: no relative imports # H304: no relative imports
# H404: multi line docstring should start with a summary # H404: multi line docstring should start with a summary
ignore = F811,F821,F841,H102,H302,H304,H404 ignore = F811,F821,F841,H102,H302,H304
show-source = True show-source = True
exclude = .venv,.tox,dist,doc,*egg,build exclude = .venv,.tox,dist,doc,*egg,build