[Freeipa-devel] DN patch and documentation
Petr Viktorin
pviktori at redhat.com
Fri Jul 27 15:18:31 UTC 2012
The nitpicks continue. None of the things in this mail are holding up
the patch, consider it more of a discussion about good practices. (I'm
assuming you're interested.)
ipaserver/rpcserver.py:
elif isinstance(val, (list, tuple)):
- new_list = []
- n = len(val)
- i = 0
- while i < n:
- v = val[i]
- if isinstance(v, str):
- new_list.append({'__base64__' : base64.b64encode(v)})
- else:
- new_list.append(json_encode_binary(v))
- i += 1
+ new_list = [json_encode_binary(v) for v in val]
del val
return new_list
You might as well remove the `del val`s.
ipapython/dn.py:
-Concatenation and In-Place Addition:
+Concatenation, In-Place Addition, Insetion:
s/Insetion/Insertion/
In the giant comment in ipapython/dn.py:
+by reference" (mutable objects can be modifed inside the
unbalanced parenthesis
+and B are the same object Python says no [2]_. For those familiar with Lisp
missing reference
Your example here only applies to interactive sessions of current
versions of CPython, i.e. the Python implementation we use; other
Pythons are free to use same or different objects for equal strs/ints as
they choose. Even in CPython literals happen to become the same object
if they appear in the same compilation unit:
>>> 'abc' == 'abc'
True
But in general I think lengthy explanations of Python's behavior shuld
go in a Python tutorial. The docstring should say what the code does and
how to use it, and comments can summarize design/implementation
decisions with simple justification (possibly with links to
tutorials/documentation -- your prose could make good blog posts).
By the way, are you aware that the text in dn.py is not a docstring,
because it's not the first statement in the file?
+verses using a hash value for dict keys or sets is critical to
s/verses/versus/
Good job on the DN tests!
On 07/26/2012 09:59 PM, John Dennis wrote:
> On 07/11/2012 03:59 AM, Petr Viktorin wrote:
>> I went over the changes to ipaserver/plugins/ldap2.py; now I can start
>> the testing.
>>
>> I must ask about the wrapped _ext methods. I assume python-ldap exposes
>> them only to mimic the C interface, which has them because C doesn't
>> have default arguments. Would we lose anything if the class only defined
>> the _ext methods, and aliased the non-ext variants to them? e.g.
>>
>> def add_ext(self, dn, modlist, serverctrls=None, clientctrls=None):
>> assert isinstance(dn, DN)
>> dn = str(dn)
>> modlist = self.encode(modlist)
>> return self.conn.add_ext(dn, modlist, serverctrls, clientctrls)
>> add = add_ext
>>
>> The non-_ext variants are deprecated in the underlying C library, anyway.
>
>
> With the current implementation you are correct. There would be no
> functional difference if we only utilized the _ext variants. However
> that means employing knowledge from the wrong side of an API boundary.
> From our perspective python-ldap exposes certain methods. In particular
> we use the simpler methods because that is sufficient to meet our needs.
> I think we should code to the defined API. If python-ldap ever changes
> or we use an alternate Python ldap library the porting effort would be
> much cleaner if we don't try to outsmart ourselves.
Fair enough.
>> Lines 203, 825:
>> os.environ['KRB5CCNAME'] = ccache_file
>> Should KRB5CCNAME be unset/restored once the code's done with it.
>
> Agreed, once again I didn't write this but saw the problem when I moved
> the code block and made a note to fix it in the future. This particular
> place is fixed now. However I think we've got other places that do
> similar things. This is another example of the tendency to cut-n-paste
> blocks of duplicate code which drives me nuts. But how much unrelated
> code does one change in any patch? I did however add a FIXME comment to
> get that code cleaned up. In the past I would change any problem I saw
> when I touched an area of the code but I got a lot of complaints about
> changing things not directly related to the intent of the patch :-(
Also fair. Sorry for different standards, I know it's frustrating to
have the line drawn in a different place each time.
>> Line 107 & 127:
>> return utf8_codec.decode(val)[0]
>> return utf8_codec.encode(unicode(val))[0]
>> I believe the following would be more readable:
>> return val.decode('utf-8')
>> return unicode(val).encode('utf-8')
>
> Yes, your suggestion is easier to read, but it's more efficient to
> lookup the codec once rather than everytime you decode.
I wouldn't worry about that; lookups are pretty fast -- I'd say
comparable to creating an unnecessary tuple. Of course it's hard to know
without profiling.
>> Line 354:
>> _SCHEMA_OVERRIDE = {
>> We have a case-insensitive dict class, why not use it here?
>
> Excellent suggestion, it's been changed.
Please also change the use:
attr_lower = attr.lower()
syntax = self._SCHEMA_OVERRIDE.get(attr_lower)
to
syntax = self._SCHEMA_OVERRIDE[attr]
>> Lines 388, 750:
>> def is_dn_syntax(self, attr):
>> """
>> Check the schema to see if the attribute uses DN syntax.
>> uses_dn_syntax or has_dn_syntax would be a better name.
>
> Good suggestion. I made the change.
>
> Actually, I wish LDAP attributes were a class, that way the correct
> comparisons could be automatically applied by Python based on the LDAP
> syntax of the particular attribute and things like "is_dn" and
> "is_multivalued" could be a properties of the attribute instance, etc.
> We are using an object oriented language aren't we?
All problems can be solved by another level of abstraction.
(except for the problem of too many layers of abstraction)
Sounds like a ticket for the deferred bucket :)
>> Line 406:
>> if isinstance(val, bool):
>> if val:
>> val = 'TRUE'
>> else:
>> val = 'FALSE'
>> return self.encode(val)
>> Encoding an ASCII string should be a no-op, why bother doing it?
>
> Not sure what you mean by no-op. Do you mean str(val) would return
> either 'True' or 'False'?
>
> In any event we are dependent on the string representation of a boolean
> being all uppper-case. Not something I invented.
I meant the `return self.encode(val)`, with val being either 'TRUE' or
'FALSE'. The following should suffice:
if isinstance(val, bool):
if val:
return 'TRUE'
else:
return 'FALSE'
Sorry for not being clear.
>> Line 421:
>> dct = dict()
>> for (k, v) in val.iteritems():
>> dct[self.encode(k)] = self.encode(v)
>> return dct
>> Consider:
>> dct = dict((self.encode(k), self.encode(v)) for k, v in
>> val.iteritems())
>
> Why do the work of building a list of tuples when it's not necessary?
It's a generator expression, it doesn't build a list.
>> Line 437:
>> if type(target_type) is type and isinstance(original_value,
>> target_type):
>> Don't use `type(x) is expected_type` for type checking! Use
>> isinstance(target_type, type) so you also catch type's subclasses.
>
> Good point, fixed.
>
> Or
>> better yet, handle the TypeError that isinstance will raise if you don't
>> give it a class.
>> To be honest, I don't think it's worth it to do all this convoluted
>> checking just to make sure you're not copying unnecessarily. Do you have
>> a reason I can't see?
>
> Because some constructors are expensive and it's good to pay attention
> to efficiency.
It is, but I'm not convinced this is the right place. Complicating the
code (especially in a fairly obscure method with no dosctring) has its
own drawbacks.
If constructors are too expensive it might be better to optimize those
-- for example sharing immutable AVAs/RDNs when copying DNs would
probably give a similar speedup here, as well as helping other parts of
the code.
>> Line 594, 604, 613:
>> ipa_result = self.convert_result(ldap_result)
>> return ipa_result
>> Just return the result directly, no need for the extra variable.
>
> It was done that way for two reasons. During development it gave me a
> value to print for debugging purposes and I thought it made the code
> clearer by indicating what is an IPA value. I don't think the extra
> variable introduces any significant inefficiencies (in C it would be
> optimized out, not sure Python's byte compiler does the same).
It doesn't, but the slowdown is negligible.
Never mind, this is an extremely minor point.
>> Line 1422:
>> self.handle_errors(e, **{})
>> Wouldn't the following work better?
>> self.handle_errors(e, *[{}]*0)
>
> I never liked the way handle_errors was written. The use of the kwds
> args is odd, convoluted and hard to understand.
>
> To be honest I can't parse
>
> *[{}]*0
Sorry, lame attempt at humor.
The **{} doesn't do anything at all, you can remove it.
The keyword arguments in handle_errors are unused, so you can remove
that as well if you want.
--
Petr³
More information about the Freeipa-devel
mailing list