3
\$\begingroup\$

I have started working on my first open source project making an LDAP library in Python. I have coded quite a bit and am starting to think about a couple of the details related to design.

I have been considering what to do is in regard to the protocol part of the library.

Here is a sample of what I have for an encode/outbound protocol operation:

class BindRequest:
    """ BindRequest ::= [APPLICATION 0] SEQUENCE {
            version INTEGER (1 .. 127),
            name LDAPDN,
            authentication AuthenticationChoice }
    """
    APPID = 0x00
    version = 3
    name = ""
    authentication = ""

    def __init__(self, version, name, authentication):
        self.version = version
        self.name = name
        self.authentication = authentication

    def encode(self):
        return ber_encode(APPLICATION, CONSTRUCTED, self.APPID,
                ber_encode(UNIVERSAL, PRIMITIVE, INTEGER, self.version)
                + LDAPDN().encode(self.name)
                + AuthenticationChoice().encode(self.authentication))

What I am stuck thinking about is if the encode/decode (if appropriate) should have the parameters.... if for example encode had them then the init probably does not need to exist and I would instead just use the passed in parameters. This would remove the need for the class/instance variables and I would only create them on a decode/inbound operation.

Currently it works like this:

bind = BindRequest(version, myname, mypassword)
encoded = bind.encode()

If I was to remove the init:

encoded = BindRequest().encode(version, myname, mypassword)

If I did remove the init and the state then I may be better to just do this:

def BindRequest_encode(version, name, authentication):
    return ber_encode..........etc
encoded = BindRequest_encode(version, myname, mypassword)

I started with classes since the decode would break an encoded message into the appropriate instance variables and I could just attach a class to the appropriate places... if I remove classes then I would have to pass back something else like dictionaries or named tuples.

Here is the full code if more context is needed to understand the question: ldaplib

I would guess this is pretty much an opinion question but what seems like the best way to proceed and why? I appreciate any feedback.

Update

After spending several hours experimenting, I think my best bet would be to have classes that hold any constants that the class needs but not the "field" data of the protocol. I am finding that both encoding and decoding seem best to return python objects such as strings/numbers/lists/dicts. This would avoid using any internal state in the classes other than constants/etc that are required for the class to know. I arrived at this because of how some of the protocol is naturally a list or string like this:

# Is just a list of uri
class Referral:
   """ Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI """

# A uri is just a simple string
class URI(LDAPString):
    """ URI ::= LDAPString -- limited to characters permitted in
        -- URIs
    ""

# This has a string and a list as fields
class PartialAttribute:
    """ PartialAttribute ::= SEQUENCE {
            type AttributeDescription,
            vals SET OF value AttributeValue }

In these examples if I want to reference the string I would need to make a field to hold it, same with the list. In the bottom example it holds both a string and a list. If I just return regular python objects then I can make it like this which I believe is going to make the rest of this project much easier:

{'type': 'cn', 'vals': ['name', 'etc']}

Then when each "flows" up through the protocol layers for example if something had a PartialAttribute then it would have a field that would contain that dictionary value. This is basically in favor of the syntax like this....

encoded = BindRequest().encode(version, myname, myauthentication)

This being the case, the init and instance variables are unnecessary and it seems reasonable to use the class to hold constants and for the benefit of inheriting from other types if the encode/decode are the same.

I think I may have just answered my own question. Anything jump out as "Are you crazy...don't do that!" to anyone?

\$\endgroup\$
1
\$\begingroup\$

A class represents a group of things with common behaviour and persistent instance data. But in the fragment of code you present here, there is no thing. Your example is:

bind = BindRequest(version, myname, mypassword)
encoded = bind.encode()

but do you ever use the bind object again? It doesn't seem as if you do. So why does BindRequest need to be a class at all? It looks to me as though a function is the appropriate implementation choice.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for the reply Gareth. That is the crux of my question and why I have been thinking about just making it functions vs keeping it as classes. The place where having classes really helps is where some types are the same as others... for example a URI is an LDAPString or a SearchResultDone is an LDAPResult but with only a different appid. \$\endgroup\$ – clutton Jan 1 '14 at 18:53
  • \$\begingroup\$ So I guess the question really is: Is it considered bad form to use classes only for grouping functions/constants together and for subclassing given that they would not have instance variables in most cases? \$\endgroup\$ – clutton Jan 1 '14 at 19:34
  • \$\begingroup\$ Yes, it is considered bad form to use classes only for grouping things. Python is not Java: not everything needs to be a class. If you just want to group some functions, use a module. \$\endgroup\$ – Gareth Rees Jan 2 '14 at 15:07
  • \$\begingroup\$ Thanks for the feedback. If I ditch the instance data and don't plan on creation of the classes (kind of hard to say all the use cases yet) I will likely refactor into functions. \$\endgroup\$ – clutton Jan 2 '14 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.