views:

308

answers:

4

I am trying to use a little inheritance in a Python program I am working on. I have a base class, User, which implements all of the functionality of a user. I am adding the concept of an unapproved user, which is just like a user, with the addition of a single method.

The User class has some methods that return a User object. This will not work when I subclass, since I will end up having an UnapprovedUser return a User, preventing me from calling this method, among other things.

class User(object):
    base_dn = 'ou=Users,dc=example,dc=org'

    @classmethod
    def get(cls, uid):
        ldap_data = LdapUtil.get(uid + ',' + self.base_dn)
        return User._from_ldap(ldap_data)

class UnapprovedUser(User):
    base_dn = 'ou=UnapprovedUsers,dc=example,dc=org'

    def approve(self):
        new_dn = '' # the new DN
        LdapUtil.move(self.dn, new_dn)

The get() and _from_ldap() methods are the same for both classes, though the get() method in UnapprovedUser needs to return an UnapprovedUser object, not a User.

How can I cast one of the instances of User that I get from User.get() into an UnapprovedUser?

I want to do something like:

class UnapprovedUser(User):
    # continued from before

    @classmethod
    def get(cls, uid):
        user = super(UnapprovedUser, cls).get(uid)
        return (UnapprovedUser) user # invalid syntax

so that I can wrap the method from the parent and simply cast the returned value to the correct class. Then again, doing it that way could lead to the parent using their value for self.base_dn, which would break everything.

+2  A: 

Python is a dynamically-typed language, so the concept of "casting" doesn't exist. If the object is already an UnapprovedUser, then you can already call all methods that exist in that class, without having to cast.

Chris Jester-Young
The problem is that I'm getting a User returned to me, but I need it to be an UnapprovedUser so that I can call that method. I don't want to duplicate code if I don't have to.
Chris Lieb
"Casting" is a bit of an ambiguous term (hence the n different kinds of casts in C++), but there certainly is something akin to at least one form of casting in Python: type conversion. eg: I could imagine someone saying that str(x) "casts" x into a str (though I think most people would go for the more explicit "converts" rather than "casts").
Laurence Gonsalves
+6  A: 

Rather than "casting", I think you really want to create an UnapprovedUser rather than a User when invoking UnapprovedUser.get(). To do that:

Change User.get to actually use the cls argument that's passed-in:

@classmethod
def get(cls, uid):
    ldap_data = LdapUtil.get(uid + ',' + self.base_dn)
    return cls._from_ldap(ldap_data)

You'll need to do something similar in _from_ldap. You didn't list the code for _from_ldap, but I assume that at some point it does something like:

result = User(... blah ...)

You want to replace this with:

result = cls(... blah ...)

Remember: in Python a class object is a callable that constructs instances of that class. So you can use the cls parameter of a classmethod to construct instances of the class used to call the classmethod.

Laurence Gonsalves
Exactly what I was looking for. Thanks. I guess I never noticed that you could do that with the cls parameter to a class method.
Chris Lieb
A: 

In a class method, the class is passed in in the cls parameter. So instead of User.something do cls.something. Done!

That said, I'm not sure I would do this with two types of user. I'm not 100% sure what you mean with "Approved" here, I it seems to me to be one of two things.

  1. It may mean the user isn't really logged in yet. In that case I'd have a special Anonymous User instance for not logged in users. Since you are moving the DN when approving, this seems more likely to be what you are doing.

  2. It may mean that the user hasn't been approved as a full member or something. This is just a special case of permission handling, and you are probably going to end up wanting to have more permissions later. I'd instead add support for giving the user roles, and making "Approved" a role.

If you mean something else with approved, feel free to ignore this. :-)

Lennart Regebro
I have a simple LDAP directory structure that I'm going to store users in. ou=Users is used by 'Approved' users. Applications that look at the directory for login information only look in ou=Users.I also have a way for a user to request access to these applications, where they require approval from an admin before they can log in. In this case, they are created in ou=UnapprovedUsers, and moved to ou=Users using the `approve()` method.So, no extra levels will be needed.
Chris Lieb
OK, then the principle make sense. (Although of course, having the approve method on the standard User doesn't hurt, so you might not need two different classes).
Lennart Regebro
A: 
  • super(UnapprovedUser, self) is wrong it should be super(UnapprovedUser, cls) because in class method you do not have self available

  • I will reiterate your question , in base class you are creating user and somehow you want to return derived class e.g.


   class User(object):

       @classmethod
       def get(cls, uid):
           return User()

   class UnapprovedUser(User):

       @classmethod
       def get(cls, uid):
           user = super(UnapprovedUser, cls).get(uid)
           return user # invalid syntax

   print UnapprovedUser.get("XXX")

it prints User object instead of UnapprovedUser object here you want UnapprovedUser.get to return UnapprovedUser, for that you can create a factory function which wil return appropriate user and than fill it with ldap


    class User(object):

        @classmethod
        def get(cls, uid):
            return cls.getMe()

        @classmethod
        def getMe(cls):
            return cls()

    class UnapprovedUser(User):

        @classmethod
        def get(cls, uid):
            user = super(UnapprovedUser, cls).get(uid)
            return user 

    print UnapprovedUser.get("XXX")

it prints UnapprovedUser object

Anurag Uniyal