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?