Productivity Sync Just another WordPress weblog

January 26, 2014

Crazy (or Cool?) Python idiom using __getattr__ hackery

Filed under: Uncategorized — admin @ 11:36 am

I was exploring the python-git package and how the version differences between the version of python-git you get using apt-get install python-git on Ubuntu 12.04 and 13.10 seems to break a program I use.  In my investigation I was trying to sort out how the library worked.  I found a new python idiom that I’m having a hard time accepting as a “good thing” or a path to chaos.

The Specifics:

  • ubuntu 12.04 will put version 0.1.6 of the library in your python lib.  The project is hosted at:”git@gitorious.org:git-python/git-python.git”  With version “9a4b1d4 Bumped version 0.1.6”
  • ubuntu 13.04 will put version 0.3.2-rc1 from github remote : “https://github.com/gitpython-developers/GitPython.git” With version “0e9eef4 Bumped version to 0.3.2 RC1”
  • It looks like the development moved to gitbub after 96c7ac239a2c9e303233e58daee02101cc4ebf3d — 0.3.1-beta2.

The API change that broke a program I was using between 0.1.6 and 0.3.2-rc1 was “Repo.commits_between” it was removed.  After some tracing through the 0.1.6 version of the code I find commits_between calls a function “commit.find_all” and then looking at its implementation I see it call repo.git.rev_list(…) function.  And through some grepping I see that there is in fact NO DEFINITION of rev_list in the python code!   This was a head scratcher.

After some more digging I see the repo.git is an object defined by cmd.py of class Git.  and it has a _call_process member that gets invoked as a side effect of __getattr__ and uses a “dashify” trick to change any attempts to call a git command using the convention of using underscores instead of dashes to map to calling the popen-ing the correct program.

Basically with this trick *every* git API is available as a python call.  So repo.git.rev_list maps to a popen call too “git-rev-list”

WRT the loss of “commits_between” function in the library, as this function was a one-liner calling another one-liner it can be replaced with a one-liner  “reverse( Repo.git.rev_list(sha1,sha2))”  FWIW I do agree with the removal of the trivial one liner abstractions in the older git-python version.  Still, I feel abused by the lack of introspection and confusion this implementation has provided.

I don’t know if I should be irritated by this idiom of creating an indeterminate number of callables implicitly or be impressed by the crazy shit I could do with it.

It seems that __getattr__ is the last in a series of python look ups when the interpreter evaluated a statement.  So if there is no existing match it eventually falls through to __getattr__ and at this point you can do almost anything.

Notes:

  • commits_between was replaced by iter_commits on version 0.2 (says the project changes.rst file)
  • The tutorial.rst calls out how to use git directly and calls out what I just learned in my reverse engineering.  Guess I could have read the tutorial first but, what’s the fun in that?  I wouldn’t have learned how it works to provide the direct access to git commands if I stopped there.

 

Powered by WordPress