views:

193

answers:

5

Let's say I have a base class defined as follows:

class Form(object):
    class Meta:
        model = None
        method = 'POST'

Now a developer comes a long and defines his subclass like:

class SubForm(Form):
    class Meta:
        model = 'User'

Now suddenly the method attribute is lost. How can I "get it back" without forcing the user to inherit his meta class from mine? Can I dynamically add a base class to Form.Meta in the initializer, or in a metaclass's __new__ func?

A: 

Maybe I could omit the default Meta class inside Form and use a default dict instead?

meta_defaults = {'model':None, 'method':'POST'}
meta_vars = meta_defaults
meta_vars.update(Form.Meta.__dict__)
Mark
Unfortunately the metaclass will be looking for the `Meta` inner class in order to set options in the outer class. Magic, whee!
Ignacio Vazquez-Abrams
+1  A: 

You can check for method attribute in the init method of a parent object and update it if needed. Of course this will work only if the programmer you are protecting your code from will call it in his constructor.

class Form(object):
    def __init__(self):
            if not getattr(self.Meta,'method',False):
                    self.Meta.method='POST'
    class Meta:
       model = None
       method = 'POST'

class SubForm(Form):
    class Meta:
       model = 'User'
Frost.baka
The way it's designed, they shouldn't be writing *any* methods, so the initializer shouldn't get overridden. If it does... well, then their app just won't work ;) This suggestion defeats the purpose of having the Meta class in the Form object at all. I think it would be prettier to store all the default attributes in a dict, and then... either just call `update` as I've suggested, or do what you did, but iter the keys and update as necessary.
Mark
This approach places your attributes into the standard storage: the Meta class. If you combine both approaches maybe it will be protective and standard.
Frost.baka
But you're setting the default attribute twice!! Once in `init` and once in `Meta`. That's awful! To keep them in the Meta, you just have to ensure that the Meta class exists, i.e., `class Meta: pass`, and then put all the actual defaults in `init`.
Mark
+5  A: 

As long as they won't override your __init__, or it will be called (ie by super), you can monkey-patch the Meta inner class:

class Form(object):
    class Meta:
        model = None
        method = "POST"

    def __init__(self, *args, **kwargs):
        if self.__class__ != Form:
            self.Meta.__bases__ += (Form.Meta,)
        # other __init__ code here.

class SubForm(Form):
    class Meta:
        model = 'User'
Matthew Schinckel
There we go... that's what I asked :D
Mark
Oh..what happens if you add the same base twice? Anything bad? It's possible that they leave Meta blank or something.
Mark
You could check that (a) there is a Meta inner class, and that Form.Meta is not already in the class hierarchy. I'll update my answer when I'm on a real keyboard.
Matthew Schinckel
Oops...should have given you the full bounty. It expired.
Mark
+4  A: 

Do you really need Meta to be defined that way? If you only need to access it as form.Meta.method, why wouldn't you just use a dotdict?

class dotdict(dict):
    def __getattr__(self, attr):
        return self.get(attr, None)
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

Then you can do this:

class Form(object):
    def __init__(self):
        self.Meta = dotdict()
        self.Meta.model = None
        self.Meta.method = 'POST'

class SubForm(Form):
    def __init__(self):
        Form.__init__(self)
        self.Meta.model = 'User'
zvonimir
I really prefer the meta syntax in the subform....what goes in in the form doesn't really matter tho.
Mark
A: 

Maybe you could use a metaclass like this:

class _Meta:
    model = None
    method = "Post"

class MetaForm(type):

    def __init__(cls, name, bases, dct):
        super(MetaForm, cls).__init__(name, bases, dct)
        if hasattr(cls, 'Meta'):            
            meta = getattr(cls, 'Meta')                
            for k,v in _Meta.__dict__.items():
                check = meta.__dict__.get(k)
                if not check:
                    meta.__dict__[k] = v    
        else:
            setattr(cls, "Meta", _Meta)        


class Form(object):
    __metaclass__ = MetaForm

class SubForm(Form):
    class Meta:
        model = 'User'

class Sub2Form(Form):
    pass

sub_form = SubForm()        
sub2_form = Sub2Form()    

print sub_form.Meta.method # prints "Post"
print sub2_form.Meta.model # prints None

The code is really simple and maybe you need to suit it to your needs.

zovision
`sub2_form = Sub2Form()` maybe?
Stobor
Yes, thank you. Corrected it.
zovision