diff options
author | Pavel Zuna <pzuna@redhat.com> | 2009-04-23 14:51:59 +0200 |
---|---|---|
committer | Rob Crittenden <rcritten@redhat.com> | 2009-04-23 10:29:14 -0400 |
commit | 7d0bd4b8951ef7894668ad3c63607769e208c9d0 (patch) | |
tree | 25cfb046e3f16814d66465c72ce5a0cbe00a00fd /ipalib/errors2.py | |
parent | 596d410471672eac0e429c53d2f28ff6ea43d867 (diff) | |
download | freeipa.git-7d0bd4b8951ef7894668ad3c63607769e208c9d0.tar.gz freeipa.git-7d0bd4b8951ef7894668ad3c63607769e208c9d0.tar.xz freeipa.git-7d0bd4b8951ef7894668ad3c63607769e208c9d0.zip |
Rename errors2.py to errors.py. Modify all affected files.
Diffstat (limited to 'ipalib/errors2.py')
-rw-r--r-- | ipalib/errors2.py | 1077 |
1 files changed, 0 insertions, 1077 deletions
diff --git a/ipalib/errors2.py b/ipalib/errors2.py deleted file mode 100644 index f626f359..00000000 --- a/ipalib/errors2.py +++ /dev/null @@ -1,1077 +0,0 @@ -# Authors: -# Jason Gerard DeRose <jderose@redhat.com> -# -# Copyright (C) 2008 Red Hat -# see file 'COPYING' for use and warranty inmsgion -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; version 2 only -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -""" -Custom exception classes (some which are RPC transparent). - -`PrivateError` and its subclasses are custom IPA excetions that will *never* be -forwarded in a Remote Procedure Call (RPC) response. - -On the other hand, `PublicError` and its subclasses can be forwarded in an RPC -response. These public errors each carry a unique integer error code as well as -a gettext translated error message (translated at the time the exception is -raised). The purpose of the public errors is to relay information about -*expected* user errors, service availability errors, and so on. They should -*never* be used for *unexpected* programmatic or run-time errors. - -For security reasons it is *extremely* important that arbitrary exceptions *not* -be forwarded in an RPC response. Unexpected exceptions can easily contain -compromising information in their error messages. Any time the server catches -any exception that isn't a `PublicError` subclass, it should raise an -`InternalError`, which itself always has the same, static error message (and -therefore cannot be populated with information about the true exception). - -The public errors are arranging into five main blocks of error code ranges: - - ============= ======================================== - Error codes Exceptions - ============= ======================================== - 1000 - 1999 `AuthenticationError` and its subclasses - 2000 - 2999 `AuthorizationError` and its subclasses - 3000 - 3999 `InvocationError` and its subclasses - 4000 - 4999 `ExecutionError` and its subclasses - 5000 - 5999 `GenericError` and its subclasses - ============= ======================================== - -Within these five blocks some sub-ranges are already allocated for certain types -of error messages, while others are reserved for future use. Here are the -current block assignments: - - - **900-5999** `PublicError` and its subclasses - - - **901 - 907** Assigned to special top-level public errors - - - **908 - 999** *Reserved for future use* - - - **1000 - 1999** `AuthenticationError` and its subclasses - - - **1001 - 1099** Open for general authentication errors - - - **1100 - 1199** `KerberosError` and its subclasses - - - **1200 - 1999** *Reserved for future use* - - - **2000 - 2999** `AuthorizationError` and its subclasses - - - **2001 - 2099** Open for general authorization errors - - - **2100 - 2199** `ACIError` and its subclasses - - - **2200 - 2999** *Reserved for future use* - - - **3000 - 3999** `InvocationError` and its subclasses - - - **3001 - 3099** Open for general invocation errors - - - **3100 - 3199** *Reserved for future use* - - - **4000 - 4999** `ExecutionError` and its subclasses - - - **4001 - 4099** Open for general execution errors - - - **4100 - 4199** `BuiltinError` and its subclasses - - - **4200 - 4299** `LDAPError` and its subclasses - - - **4300 - 4999** *Reserved for future use* - - - **5000 - 5999** `GenericError` and its subclasses - - - **5001 - 5099** Open for generic errors - - - **5100 - 5999** *Reserved for future use* -""" - -from inspect import isclass -from request import ugettext, ungettext -from constants import TYPE_ERROR - - -class PrivateError(StandardError): - """ - Base class for exceptions that are *never* forwarded in an RPC response. - """ - - format = '' - - def __init__(self, **kw): - self.message = self.format % kw - for (key, value) in kw.iteritems(): - assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % ( - self.__class__.__name__, key, value, - ) - setattr(self, key, value) - StandardError.__init__(self, self.message) - - -class SubprocessError(PrivateError): - """ - Raised when ``subprocess.call()`` returns a non-zero exit status. - - This custom exception is needed because Python 2.4 doesn't have the - ``subprocess.CalledProcessError`` exception (which was added in Python 2.5). - - For example: - - >>> raise SubprocessError(returncode=2, argv=('ls', '-lh', '/no-foo/')) - Traceback (most recent call last): - ... - SubprocessError: return code 2 from ('ls', '-lh', '/no-foo/') - - The exit code of the sub-process is available via the ``returncode`` - instance attribute. For example: - - >>> e = SubprocessError(returncode=1, argv=('/bin/false',)) - >>> e.returncode - 1 - >>> e.argv # argv is also available - ('/bin/false',) - """ - - format = 'return code %(returncode)d from %(argv)r' - - -class PluginSubclassError(PrivateError): - """ - Raised when a plugin doesn't subclass from an allowed base. - - For example: - - >>> raise PluginSubclassError(plugin='bad', bases=('base1', 'base2')) - Traceback (most recent call last): - ... - PluginSubclassError: 'bad' not subclass of any base in ('base1', 'base2') - - """ - - format = '%(plugin)r not subclass of any base in %(bases)r' - - -class PluginDuplicateError(PrivateError): - """ - Raised when the same plugin class is registered more than once. - - For example: - - >>> raise PluginDuplicateError(plugin='my_plugin') - Traceback (most recent call last): - ... - PluginDuplicateError: 'my_plugin' was already registered - """ - - format = '%(plugin)r was already registered' - - -class PluginOverrideError(PrivateError): - """ - Raised when a plugin overrides another without using ``override=True``. - - For example: - - >>> raise PluginOverrideError(base='Command', name='env', plugin='my_env') - Traceback (most recent call last): - ... - PluginOverrideError: unexpected override of Command.env with 'my_env' - """ - - format = 'unexpected override of %(base)s.%(name)s with %(plugin)r' - - -class PluginMissingOverrideError(PrivateError): - """ - Raised when a plugin overrides another that has not been registered. - - For example: - - >>> raise PluginMissingOverrideError(base='Command', name='env', plugin='my_env') - Traceback (most recent call last): - ... - PluginMissingOverrideError: Command.env not registered, cannot override with 'my_env' - """ - - format = '%(base)s.%(name)s not registered, cannot override with %(plugin)r' - - -class SkipPluginModule(PrivateError): - """ - Raised to abort the loading of a plugin module. - """ - - format = '%(reason)s' - - -class PluginsPackageError(PrivateError): - """ - Raised when ``package.plugins`` is a module instead of a sub-package. - """ - - format = '%(name)s must be sub-package, not module: %(file)r' - - -############################################################################## -# Public errors: - -__messages = [] - -def _(message): - __messages.append(message) - return message - - -class PublicError(StandardError): - """ - **900** Base class for exceptions that can be forwarded in an RPC response. - """ - - errno = 900 - format = None - - def __init__(self, format=None, message=None, **kw): - name = self.__class__.__name__ - if self.format is not None and format is not None: - raise ValueError( - 'non-generic %r needs format=None; got format=%r' % ( - name, format) - ) - if message is None: - if self.format is None: - if format is None: - raise ValueError( - '%s.format is None yet format=None, message=None' % name - ) - self.format = format - self.forwarded = False - self.message = self.format % kw - self.strerror = ugettext(self.format) % kw - else: - if type(message) is not unicode: - raise TypeError( - TYPE_ERROR % ('message', unicode, message, type(message)) - ) - self.forwarded = True - self.message = message - self.strerror = message - for (key, value) in kw.iteritems(): - assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % ( - name, key, value, - ) - setattr(self, key, value) - StandardError.__init__(self, self.message) - - -class VersionError(PublicError): - """ - **901** Raised when client and server versions are incompatible. - - For example: - - >>> raise VersionError(cver='2.0', sver='2.1', server='https://localhost') - Traceback (most recent call last): - ... - VersionError: 2.0 client incompatible with 2.1 server at 'https://localhost' - - """ - - errno = 901 - format = _('%(cver)s client incompatible with %(sver)s server at %(server)r') - - -class UnknownError(PublicError): - """ - **902** Raised when client does not know error it caught from server. - - For example: - - >>> raise UnknownError(code=57, server='localhost', error=u'a new error') - ... - Traceback (most recent call last): - ... - UnknownError: unknown error 57 from localhost: a new error - - """ - - errno = 902 - format = _('unknown error %(code)d from %(server)s: %(error)s') - - -class InternalError(PublicError): - """ - **903** Raised to conceal a non-public exception. - - For example: - - >>> raise InternalError() - Traceback (most recent call last): - ... - InternalError: an internal error has occured - """ - - errno = 903 - format = _('an internal error has occured') - - def __init__(self, message=None): - """ - Security issue: ignore any information given to constructor. - """ - PublicError.__init__(self) - - -class ServerInternalError(PublicError): - """ - **904** Raised when client catches an `InternalError` from server. - - For example: - - >>> raise ServerInternalError(server='https://localhost') - Traceback (most recent call last): - ... - ServerInternalError: an internal error has occured on server at 'https://localhost' - """ - - errno = 904 - format = _('an internal error has occured on server at %(server)r') - - -class CommandError(PublicError): - """ - **905** Raised when an unknown command is called. - - For example: - - >>> raise CommandError(name='foobar') - Traceback (most recent call last): - ... - CommandError: unknown command 'foobar' - """ - - errno = 905 - format = _('unknown command %(name)r') - - -class ServerCommandError(PublicError): - """ - **906** Raised when client catches a `CommandError` from server. - - For example: - - >>> e = CommandError(name='foobar') - >>> raise ServerCommandError(error=e.message, server='https://localhost') - Traceback (most recent call last): - ... - ServerCommandError: error on server 'https://localhost': unknown command 'foobar' - """ - - errno = 906 - format = _('error on server %(server)r: %(error)s') - - -class NetworkError(PublicError): - """ - **907** Raised when a network connection cannot be created. - - For example: - - >>> raise NetworkError(uri='ldap://localhost:389', error='Connection refused') - Traceback (most recent call last): - ... - NetworkError: cannot connect to 'ldap://localhost:389': Connection refused - """ - - errno = 907 - format = _('cannot connect to %(uri)r: %(error)s') - - -class ServerNetworkError(PublicError): - """ - **908** Raised when client catches a `NetworkError` from server. - """ - - errno = 908 - format = _('error on server %(server)r: %(error)s') - - - -############################################################################## -# 1000 - 1999: Authentication errors -class AuthenticationError(PublicError): - """ - **1000** Base class for authentication errors (*1000 - 1999*). - """ - - errno = 1000 - - -class KerberosError(AuthenticationError): - """ - **1100** Base class for Kerberos authentication errors (*1100 - 1199*). - """ - - errno = 1100 - - -class CCacheError(KerberosError): - """ - **1101** Raised when sever does not recieve Kerberose credentials. - - For example: - - >>> raise CCacheError() - Traceback (most recent call last): - ... - CCacheError: did not receive Kerberos credentials - - """ - - errno = 1101 - format = _('did not receive Kerberos credentials') - - -class ServiceError(KerberosError): - """ - **1102** Raised when service is not found in Kerberos DB. - - For example: - - >>> raise ServiceError(service='HTTP@localhost') - Traceback (most recent call last): - ... - ServiceError: Service 'HTTP@localhost' not found in Kerberos database - """ - - errno = 1102 - format = _('Service %(service)r not found in Kerberos database') - - -class NoCCacheError(KerberosError): - """ - **1103** Raised when a client attempts to use Kerberos without a ccache. - - For example: - - >>> raise NoCCacheError() - Traceback (most recent call last): - ... - NoCCacheError: No credentials cache found - """ - - errno = 1103 - format = _('No credentials cache found') - - -class TicketExpired(KerberosError): - """ - **1104** Raised when a client attempts to use an expired ticket - - For example: - - >>> raise TicketExpired() - Traceback (most recent call last): - ... - TicketExpired: Ticket expired - """ - - errno = 1104 - format = _('Ticket expired') - - -class BadCCachePerms(KerberosError): - """ - **1105** Raised when a client has bad permissions on their ccache - - For example: - - >>> raise BadCCachePerms() - Traceback (most recent call last): - ... - BadCCachePerms: Credentials cache permissions incorrect - """ - - errno = 1105 - format = _('Credentials cache permissions incorrect') - - -class BadCCacheFormat(KerberosError): - """ - **1106** Raised when a client has a misformated ccache - - For example: - - >>> raise BadCCacheFormat() - Traceback (most recent call last): - ... - BadCCacheFormat: Bad format in credentials cache - """ - - errno = 1106 - format = _('Bad format in credentials cache') - - -class CannotResolveKDC(KerberosError): - """ - **1107** Raised when the KDC can't be resolved - - For example: - - >>> raise CannotResolveKDC() - Traceback (most recent call last): - ... - CannotResolveKDC: Cannot resolve KDC for requested realm - """ - - errno = 1107 - format = _('Cannot resolve KDC for requested realm') - - -############################################################################## -# 2000 - 2999: Authorization errors -class AuthorizationError(PublicError): - """ - **2000** Base class for authorization errors (*2000 - 2999*). - """ - - errno = 2000 - - -class ACIError(AuthorizationError): - """ - **2100** Base class for ACI authorization errors (*2100 - 2199*). - """ - - errno = 2100 - format = _('Insufficient access: %(info)r') - - - -############################################################################## -# 3000 - 3999: Invocation errors - -class InvocationError(PublicError): - """ - **3000** Base class for command invocation errors (*3000 - 3999*). - """ - - errno = 3000 - - -class EncodingError(InvocationError): - """ - **3001** Raised when received text is incorrectly encoded. - """ - - errno = 3001 - - -class BinaryEncodingError(InvocationError): - """ - **3002** Raised when received binary data is incorrectly encoded. - """ - - errno = 3002 - - -class ZeroArgumentError(InvocationError): - """ - **3003** Raised when a command is called with arguments but takes none. - - For example: - - >>> raise ZeroArgumentError(name='ping') - Traceback (most recent call last): - ... - ZeroArgumentError: command 'ping' takes no arguments - """ - - errno = 3003 - format = _('command %(name)r takes no arguments') - - -class MaxArgumentError(InvocationError): - """ - **3004** Raised when a command is called with too many arguments. - - For example: - - >>> raise MaxArgumentError(name='user_add', count=2) - Traceback (most recent call last): - ... - MaxArgumentError: command 'user_add' takes at most 2 arguments - """ - - errno = 3004 - - def __init__(self, message=None, **kw): - if message is None: - format = ungettext( - 'command %(name)r takes at most %(count)d argument', - 'command %(name)r takes at most %(count)d arguments', - kw['count'] - ) - else: - format = None - InvocationError.__init__(self, format, message, **kw) - - -class OptionError(InvocationError): - """ - **3005** Raised when a command is called with unknown options. - """ - - errno = 3005 - - -class OverlapError(InvocationError): - """ - **3006** Raised when arguments and options overlap. - - For example: - - >>> raise OverlapError(names=['givenname', 'login']) - Traceback (most recent call last): - ... - OverlapError: overlapping arguments and options: ['givenname', 'login'] - """ - - errno = 3006 - format = _('overlapping arguments and options: %(names)r') - - -class RequirementError(InvocationError): - """ - **3007** Raised when a required parameter is not provided. - - For example: - - >>> raise RequirementError(name='givenname') - Traceback (most recent call last): - ... - RequirementError: 'givenname' is required - """ - - errno = 3007 - format = _('%(name)r is required') - - -class ConversionError(InvocationError): - """ - **3008** Raised when parameter value can't be converted to correct type. - - For example: - - >>> raise ConversionError(name='age', error='must be an integer') - Traceback (most recent call last): - ... - ConversionError: invalid 'age': must be an integer - """ - - errno = 3008 - format = _('invalid %(name)r: %(error)s') - - -class ValidationError(InvocationError): - """ - **3009** Raised when a parameter value fails a validation rule. - - For example: - - >>> raise ValidationError(name='sn', error='can be at most 128 characters') - Traceback (most recent call last): - ... - ValidationError: invalid 'sn': can be at most 128 characters - """ - - errno = 3009 - format = _('invalid %(name)r: %(error)s') - - -class NoSuchNamespaceError(InvocationError): - """ - **3010** Raised when an unknown namespace is requested. - - For example: - - >>> raise NoSuchNamespaceError(name='Plugins') - Traceback (most recent call last): - ... - NoSuchNamespaceError: api has no such namespace: Plugins - """ - - errno = 3010 - format = _('api has no such namespace: %(name)r') - - -############################################################################## -# 4000 - 4999: Execution errors - -class ExecutionError(PublicError): - """ - **4000** Base class for execution errors (*4000 - 4999*). - """ - - errno = 4000 - -class NotFound(ExecutionError): - """ - **4001** Raised when an entry is not found. - - For example: - - >>> raise NotFound() - Traceback (most recent call last): - ... - NotFound: entry not found - - """ - - errno = 4001 - format = _('entry not found') - -class DuplicateEntry(ExecutionError): - """ - **4002** Raised when an entry already exists. - - For example: - - >>> raise DuplicateEntry - Traceback (most recent call last): - ... - DuplicateEntry: This entry already exists - - """ - - errno = 4002 - format = _('This entry already exists') - -class HostService(ExecutionError): - """ - **4003** Raised when a host service principal is requested - - For example: - - >>> raise HostService - Traceback (most recent call last): - ... - HostService: You must enroll a host in order to create a host service - - """ - - errno = 4003 - format = _('You must enroll a host in order to create a host service') - -class MalformedServicePrincipal(ExecutionError): - """ - **4004** Raised when a service principal is not of the form: service/fully-qualified host name - - For example: - - >>> raise MalformedServicePrincipal - Traceback (most recent call last): - ... - MalformedServicePrincipal: Service principal is not of the form: service/fully-qualified host name - - """ - - errno = 4004 - format = _('Service principal is not of the form: service/fully-qualified host name') - -class RealmMismatch(ExecutionError): - """ - **4005** Raised when the requested realm does not match the IPA realm - - For example: - - >>> raise RealmMismatch - Traceback (most recent call last): - ... - RealmMismatch: The realm for the principal does not match the realm for this IPA server - - """ - - errno = 4005 - format = _('The realm for the principal does not match the realm for this IPA server') - -class RequiresRoot(ExecutionError): - """ - **4006** Raised when a command requires the unix super-user to run - - For example: - - >>> raise RequiresRoot - Traceback (most recent call last): - ... - RequiresRoot: This command requires root access - - """ - - errno = 4006 - format = _('This command requires root access') - -class AlreadyPosixGroup(ExecutionError): - """ - **4007** Raised when a group is already a posix group - - For example: - - >>> raise AlreadyPosixGroup - Traceback (most recent call last): - ... - AlreadyPosixGroup: This is already a posix group - - """ - - errno = 4007 - format = _('This is already a posix group') - -class MalformedUserPrincipal(ExecutionError): - """ - **4008** Raised when a user principal is not of the form: user@REALM - - For example: - - >>> raise MalformedUserPrincipal(principal=jsmith@@EXAMPLE.COM) - Traceback (most recent call last): - ... - MalformedUserPrincipal: Principal is not of the form user@REALM: jsmith@@EXAMPLE.COM - - """ - - errno = 4008 - format = _('Principal is not of the form user@REALM: %(principal)r') - -class AlreadyActive(ExecutionError): - """ - **4009** Raised when an entry is made active that is already active - - For example: - - >>> raise AlreadyActive() - Traceback (most recent call last): - ... - AlreadyActive: This entry is already unlocked - - """ - - errno = 4009 - format = _('This entry is already unlocked') - -class AlreadyInactive(ExecutionError): - """ - **4010** Raised when an entry is made inactive that is already inactive - - For example: - - >>> raise AlreadyInactive() - Traceback (most recent call last): - ... - AlreadyInactive: This entry is already locked - - """ - - errno = 4010 - format = _('This entry is already locked') - -class HasNSAccountLock(ExecutionError): - """ - **4011** Raised when an entry has the nsAccountLock attribute set - - For example: - - >>> raise HasNSAccountLock() - Traceback (most recent call last): - ... - HasNSAccountLock: This entry has nsAccountLock set, it cannot be locked or unlocked - - """ - - errno = 4011 - format = _('This entry has nsAccountLock set, it cannot be locked or unlocked') - -class NotGroupMember(ExecutionError): - """ - **4012** Raised when a non-member is attempted to be removed from a group - - For example: - - >>> raise NotGroupMember() - Traceback (most recent call last): - ... - NotGroupMember: This entry is not a member of the group - - """ - - errno = 4012 - format = _('This entry is not a member of the group') - -class RecursiveGroup(ExecutionError): - """ - **4013** Raised when a group is added as a member of itself - - For example: - - >>> raise RecursiveGroup() - Traceback (most recent call last): - ... - RecursiveGroup: A group may not be a member of itself - - """ - - errno = 4013 - format = _('A group may not be a member of itself') - -class AlreadyGroupMember(ExecutionError): - """ - **4014** Raised when a member is attempted to be re-added to a group - - For example: - - >>> raise AlreadyGroupMember() - Traceback (most recent call last): - ... - AlreadyGroupMember: This entry is already a member of the group - - """ - - errno = 4014 - format = _('This entry is already a member of the group') - -class BuiltinError(ExecutionError): - """ - **4100** Base class for builtin execution errors (*4100 - 4199*). - """ - - errno = 4100 - - -class HelpError(BuiltinError): - """ - **4101** Raised when requesting help for an unknown topic. - - For example: - - >>> raise HelpError(topic='newfeature') - Traceback (most recent call last): - ... - HelpError: no command nor help topic 'newfeature' - """ - - errno = 4101 - format = _('no command nor help topic %(topic)r') - - -class LDAPError(ExecutionError): - """ - **4200** Base class for LDAP execution errors (*4200 - 4299*). - """ - - errno = 4200 - - -class MidairCollision(ExecutionError): - """ - **4201** Raised when a change collides with another change - - For example: - - >>> raise MidairCollision() - Traceback (most recent call last): - ... - MidairCollision: change collided with another change - """ - - errno = 4201 - format = _('change collided with another change') - - -class EmptyModlist(ExecutionError): - """ - **4202** Raised when an LDAP update makes no changes - - For example: - - >>> raise EmptyModlist() - Traceback (most recent call last): - ... - EmptyModlist: no modifications to be performed - """ - - errno = 4202 - format = _('no modifications to be performed') - - -class DatabaseError(ExecutionError): - """ - **4203** Raised when an LDAP error is not otherwise handled - - For example: - - >>> raise DatabaseError(desc="Can't contact LDAP server", info="") - Traceback (most recent call last): - ... - DatabaseError: Can't contact LDAP server: - """ - - errno = 4203 - format = _('%(desc)r:%(info)r') - - -class LimitsExceeded(ExecutionError): - """ - **4204** Raised when search limits are exceeded. - - For example: - - >>> raise LimitsExceeded() - Traceback (most recent call last): - ... - LimitsExceeded: limits exceeded for this query - """ - - errno = 4204 - format = _('limits exceeded for this query') - - -############################################################################## -# 5000 - 5999: Generic errors - -class GenericError(PublicError): - """ - **5000** Base class for errors that don't fit elsewhere (*5000 - 5999*). - """ - - errno = 5000 - - - -def __errors_iter(): - """ - Iterate through all the `PublicError` subclasses. - """ - for (key, value) in globals().items(): - if key.startswith('_') or not isclass(value): - continue - if issubclass(value, PublicError): - yield value - -public_errors = tuple( - sorted(__errors_iter(), key=lambda E: E.errno) -) - -if __name__ == '__main__': - for klass in public_errors: - print '%d\t%s' % (klass.code, klass.__name__) - print '(%d public errors)' % len(public_errors) |