user4981459
user4981459

Reputation:

Python inheritance causes illegal attribute error

I have below one class vuln.py

from reportlab.graphics.shapes import Drawing, String, Rect

class vuln(Drawing):
    def __init__(self, width=300, height=150, report_type=None, *args, **kw):
        Drawing.__init__(self, width, height, *args, **kw)
        self.report_type = report_type

    def print_report(self):
        print self.report_type

and calling program rep.py

import vuln

obj = vuln.vuln(report_type="abc")
obj.print_report()

After executing this it gives error,

Traceback (most recent call last):
  File "rep.py", line 3, in <module>
    obj = vuln.vuln(report_type="abc")
  File "/data/support/vuln.py", line 5, in __init__
    self.report_type = report_type
  File "/usr/lib64/python2.6/site-packages/reportlab/graphics/shapes.py", line 359, in __setattr__
    validateSetattr(self,attr,value)    #from reportlab.lib.attrmap
  File "/usr/lib64/python2.6/site-packages/reportlab/lib/attrmap.py", line 118, in validateSetattr
    raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__)
AttributeError: Illegal attribute 'report_type' in class vuln

Please help to know what the error is all about.

Upvotes: 4

Views: 619

Answers (2)

Long story short, their developers are causing you this pain and doing things they shouldn't be doing in python. Luckily it is open source. The API you are using dynamically checks the attributes of classes that inherit from Shape, or as they say in /reportlab/graphics/shapes.py, line 359:

if shapeChecking:
        """This adds the ability to check every attribute assignment as it is made.
        It slows down shapes but is a big help when developing. It does not
        get defined if rl_config.shapeChecking = 0"""
        def __setattr__(self, attr, value):
            """By default we verify.  This could be off
            in some parallel base classes."""
            validateSetattr(self,attr,value)    #from reportlab.lib.attrmap

Keep digging through the code and on line 99 of the attrmap source code you can see what is causing the issue:

def validateSetattr(obj,name,value):
    '''validate setattr(obj,name,value)'''
    if rl_config.shapeChecking:
        map = obj._attrMap
        if map and name[0]!= '_':
            #we always allow the inherited values; they cannot
            #be checked until draw time.
            if isinstance(value, DerivedValue):
                #let it through
                pass
            else:            
                try:
                    validate = map[name].validate
                    if not validate(value):
                        raise AttributeError("Illegal assignment of '%s' to '%s' in class %s" % (value, name, obj.__class__.__name__))
                except KeyError:
                    raise AttributeError("Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__))
    obj.__dict__[name] = value

Note that they don't check attributes that start with _ or __ which pythonists use to indicate "private" variables; as such, you can fix your code as follows:

from reportlab.graphics.shapes import Drawing, String, Rect

class vuln(Drawing):
    def __init__(self, width=300, height=150, report_type=None, *args, **kw):
        Drawing.__init__(self, width, height, *args, **kw)
        self._report_type = report_type

    def print_report(self):
        print self._report_type

Then everything should work as normal.

Upvotes: 3

Green Cloak Guy
Green Cloak Guy

Reputation: 24691

Your stack trace provides a bit of a clue, and searching for attrmap in this documentation (see page 35) provides more information. The most useful bit of info actually came from calling help() on the Drawing class:

 ...
 |  Methods inherited from reportlab.graphics.shapes.Shape:
 |  
 |  __setattr__(self, attr, value)
 |      By default we verify.  This could be off
 |      in some parallel base classes.
 ...

This seems to tie back into the documentation.

The underlying problem here is that, for whatever reason, the reportlab objects feel the need to verify that paramaters are "expected". Looking at the source code via the inspect module yields this:

>>> print(inspect.getsource(Drawing.__setattr__))
        def __setattr__(self, attr, value):
            """By default we verify.  This could be off
            in some parallel base classes."""
            validateSetattr(self,attr,value)    #from reportlab.lib.attrmap

>>> print(inspect.getsource(reportlab.lib.attrmap.validateSetattr))
def validateSetattr(obj,name,value):
    '''validate setattr(obj,name,value)'''
    if rl_config.shapeChecking:
        map = obj._attrMap
        if map and name[0]!= '_':
            #we always allow the inherited values; they cannot
            #be checked until draw time.
            if isinstance(value, DerivedValue):
                #let it through
                pass
            else:            
                try:
                    validate = map[name].validate
                    if not validate(value):
                        raise AttributeError("Illegal assignment of '%s' to '%s' in class %s" % (value, name, obj.__class__.__name__))
                except KeyError:
                    raise AttributeError("Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__))
    obj.__dict__[name] = value

From this we can conclude that there are solutions. First, you can override __setattr__() to not call validateSetattr() - just add the following method to your class:

def __setattr__(self, name, value):
    # use the `object` class's __setattr__ instead of the superclass's
    # thus avoiding calling validateSetattr()
    object.__setattr__(self, name, value) 

Or alternatively you can do what @ErrorSyntacticalRemorse said above, and add a _ to the beginning of your variable names. Or you can try to monkey with the self._attrMap variable, but I'm actually not sure how to do that in a way that will work with the function.

Upvotes: 0

Related Questions