tail -f findings.out

git tip: Ignoring modifications to tracked files

Problem

You have a file already tracked in your git repository, but you don’t want future modifications to it to be tracked.

A perfect example of this is the DB config file for Rails projects (config/database.yml). You’ll probably want to track this to keep the production and staging configuration stored and consistent. But it’s quite likely that individual development configurations will be different. Having a commit for each developer adding their own local configuration and thereby polluting it for others when they push is just silly.

Solution

1
git update-index --assume-unchanged FILENAME

The fun details

I had initially thought that git-ignore was the thing to use for this. That was wrong: git-ignore’s purpose is to allow ignoring of untracked files. In the case of the Rails DB conf example above, the file is already tracked. It has the instance information that needs to be shared and now we want to add local development details. Since it’s tracked, any modifications are going to be picked up by git-status. When you do a commit including changes to tracked files listed in .gitignore, the changes do indeed get pushed, which isn’t what we want.

When looking around for solutions to this, I saw “git rm –cached FILENAME” suggested as a way to stop tracking currently tracked files. And it does this, but it also deletes the file in the commit, which isn’t what we want either.

git update-index allows you to alter your staging area more manually than usual. With it you can perform a wide range of operations not otherwise possible through standard commands. Continuing with the Rails DB conf example, here’s what the workflow would look like:

  1. config/database.yml with permanent instance info is added, committed, and pushed
  2. Application code is pulled down by new developer
  3. Local modifications made to database.yml for new developer’s local DB setup
  4. git update-index –assume-unchanged config/database.yml

Thereafter, the changes to conf/database.yml won’t appear in status or get committed. You can continue making and committing other changes, going about business as usual. But any changes to config/database.yml won’t ever appear.

In case you decide you do want to start tracking changes on a file previously ignored, just run:

1
git update-index --no-assume-unchanged FILENAME

Thereafter changes to FILENAME will appear in git-status.

Gotcha

The annoying part about this solution is that it has to be run against every new checkout of the repo with the files whose modifications should not be tracked. The command sets a particular bit per checkout, and it’s not passed on to new checkouts.

Just as I was finishing up some fact checking for this write-up, I came across Jesper Rønn-Jensen’s post on this exact topic. If only it had come up in my initial searches! Here’s hoping the next searcher has better luck.

Tags: , , ,
February 7, 2010 - 11:13 PM No Comments

A better way to search for methods of Python objects

Python’s introspection abilities are quite extensive and useful. They are also well-documented, so I won’t go into the basics here. Check out this article if you need a good overview. N.B.: discussion and code below applies to both methods and attributes. I will simply refer to “methods” for simplicity.

Beyond simply listing the methods of an object, however, I often find that I want to search through them for something in particular. And eyeballing the output of dir(obj) is only efficient in the simplest of cases. hasattr(obj, “method”) won’t get you far either, as you need to match the “method” name exactly. What if you just have a good guess about the name of a method based on what you need to do? What if you want to know everything you can do with directories in the os module or ISO related methods in datetime.date? I haven’t found anything to help with this sort of problem yet. Approaches like writing a loop to do the search every time or perusing the pertinent API docs are too circuitous for such quick questions. So let’s make a tool to do this more easily!

We start with knowing some object and some string containing all or part of the method(s) we’re interested in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def mf(obj, term):
    """
    Searches through the methods and attributes defined for obj,
    looks for those containing the term passed.
    Returns all matches or a warning if none found.
    """

    meths = dir(obj)
    match_meths = []
    for meth in meths:
        if meth.rfind(term) != -1:
            match_meths.append(meth)
    if match_meths:
        print match_meths
    else:
        print "No matches!"

This simply iterates over the methods and attributes defined for the object passed and looks for the term passed within the name of each. rfind returns the highest index of the substring passed in the string it’s called on and returns -1 if no match is found. Once the matches are collected they are printed out. An example:

1
2
3
4
>>>from method_finder import mf
>>>import os
>>>mf(os, "dir")
['chdir', 'curdir', 'fchdir', 'listdir', 'makedirs', 'mkdir', 'pardir', 'removedirs', 'rmdir']

For convenience let’s make it available on the Python interactive prompt at every load without any extra effort. This is easy enough using a .pythonstartup file. This file can be used to load various useful items like tab completion for interactive Python sessions. If you haven’t used it before, you’ll need to add the following to your .bashrc:

1
export PYTHONSTARTUP="$HOME/.pythonstartup"

Then in your home directory create a .pythonstartup file containing something like the following:

1
2
3
4
5
6
7
8
9
10
11
import os
# Adds mf(obj, "str") allowing search for methods matching 'str' on obj
# as well as obinfo(obj) returning lots of info on obj
util_loc = "/home/shuckins/code/code_homerepo/python-programming/utilities"
if os.path.isdir(util_loc):
    import sys
    sys.path.append(util_loc)
    from utils import mf, obinfo
    sys.path.remove(util_loc)
    del sys
del os

You’ll need to change the path specified to wherever you placed utils.py containing the mf() function of course. You can download mine here if you’d like. The del statements are to clean up any trace of this operation once you get to the interactive interpreter. This way you get the added functions without mucking up your namespace.

This also loads in an obinfo(obj) function that I included in the same utils.py file. This is based on the interrogate() function written by Patrick O’Brien in the introspection article mentioned above. My version just adds a check for objects without docstrings and prints more of the docstring:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def obinfo(obj):
    """
    Print useful information about object.

    From http://www.ibm.com/developerworks/library/l-pyint.html
    """

    if hasattr(obj, '__name__'):
        print "NAME:    ", obj.__name__
    if hasattr(obj, '__class__'):
        print "CLASS:   ", obj.__class__.__name__
    print "ID:      ", id(obj)
    print "TYPE:    ", type(obj)
    print "VALUE:   ", repr(obj)
    print "CALLABLE:",
    if callable(obj):
        print "Yes"
    else:
        print "No"
    if hasattr(obj, '__doc__'):
        doc = getattr(obj, '__doc__')
        doc = doc.strip()
        topfive = doc.split('\n')[0:4]
        print "DOC:     ", "\n".join(topfive)
    else:
        print "No docstring. Yell at the author."

It’s fairly useful:

1
2
3
4
5
6
7
8
>>> obinfo(["some", "list"].count)
NAME:     count
CLASS:    builtin_function_or_method
ID:       139922087930696
TYPE:     <type 'builtin_function_or_method'>
VALUE:    <built-in method count of list object at 0x7f422658ef80>
CALLABLE: Yes
DOC:      L.count(value) -> integer -- return number of occurrences of value
Tags: , ,
November 8, 2009 - 5:08 PM Comments (4)

Catching warnings from the MySQLdb module

The MySQLdb Python module implements the Python DB API for MySQL. I’ve written about its use before. MySQL issues warning messages in a number of circumstances and PEP 249 (which specifies the Python DB API) describes a Warning error message to be included.

One issue I ran into recently was how to catch warnings thrown by this module when running queries. Oftentimes tutorials or forum discussions that cover warnings in the context of MySQLdb describe how to filter them (they can clog up script output). But in a recent case I wanted to grab and check the warning, logging a dependent result. I had hoped this clean implementation would work in a method used for all calls to the MySQL DB:

1
2
3
4
5
6
7
8
9
10
11
try:
    cursor.execute(query)
except MySQLdb.Error, e:
    raise e
except MySQLdb.Warning, e:
    raise e
finally:
    data = cursor.fetchall()
    rows_returned = cursor.rowcount
    cursor.close()
    db.close()

But the warnings just went right through. Instead I needed the warnings module’s assistance:

1
2
3
4
5
6
7
8
9
10
11
12
import warnings
with warnings.catch_warnings():
    warnings.simplefilter('error', MySQLdb.Warning)
    try:
        cursor.execute(query)
    except MySQLdb.Error, e:
        raise e
    finally:
        data = cursor.fetchall()
        rows_returned = cursor.rowcount
        cursor.close()
        db.close()

This catches the warnings and raises them as errors, although their class is still correct, allowing a clean implementation to call the above code (wrapped into a method called do_query):

1
2
3
4
5
try:
    self.do_query(make_cool_table)
    logger.info("Created cool_table table.")
except MySQLdb.Warning:
    logger.info("cool_table already exists.")
Tags: , ,
October 25, 2009 - 10:53 PM No Comments

An improved ruby debugger invocation

I’d been wanting to write a post for some time on improvements I’ve found useful in using Ruby’s debugger. Friend Trevor Rosen beat me to the proverbial punch, however. Give his post a read first, I’ll wait.

Now on top of his suggestions I have one more refinement to add. I’ve defined a snippet in vim for whenever I need to use the debugger. You should be able to define the same in whatever editor you use (and if your editor can’t do snippets, get a real one). When in an .rb file, “debug” + Tab becomes:

1
require 'ruby-debug'; Debugger.settings[:autoeval] = true; debugger; rubys_debugger = "annoying"

In addition to the format Trevor suggested (which calls the necessary module, turns on auto evaluation, and calls the debugger itself) this adds a simple assignment after the debugger is called. I often find this necessary because I want to debug right at the very end of a given suite, a conditional, a method, etc. But if you invoke the debugger as the last statement in such a situation, it won’t actually be called. Here’s an example file:

1
2
3
4
5
6
7
8
9
#!/usr/bin/env ruby

def troubled_func(var1)
  puts "Entering troubled function..."
  secret_num = rand(10) * var1
  require 'ruby-debug'; Debugger.settings[:autoeval] = true; debugger
end

troubled_func(5)

When you run this, you won’t get a debugger console. All the output you get is “Entering troubled function…” and the script exits. If instead you add the assignment I list above after debugger is called, you will get to the debugger console. Sad, but true. (You can select a less offensive assignment if you wish. I settled on it in disgust.)

Aside from this, the Ruby debugger is a useful and beautiful thing. So add the snippet and forget about it.

Tags: ,
September 26, 2009 - 8:17 PM No Comments

Advanced ordering of MySQL results

MySQL’s “order by” keyword allows the order in which result rows are returned to be defined. This is quite useful in a wide variety of circumstances. You can specify any number of columns whose values you want to order the results by, as well as what sorting order to use (desc or asc; asc is default). However, you may also run into situations in which you want certain groups within the resultset displayed in a certain order based on particular values in the results, not just according to alphanumeric sorting. This is easy to achieve by use of the equal operator or the field keyword within the order by clause.

Some examples are in order. I’ll be using the world database. The following shows country names and a language spoken therein for European countries that speak Arabic, Dutch, and/or Greek:

1
2
3
4
SELECT a.Name, b.LANGUAGE FROM Country a
JOIN CountryLanguage b ON (a.Code = b.CountryCode)
WHERE a.Continent = "Europe"
AND b.LANGUAGE IN ("Arabic", "Dutch", "Greek");

This returns:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-------------+----------+
| Name        | Language |
+-------------+----------+
| Netherlands | Arabic   |
| Netherlands | Dutch    |
| Albania     | Greek    |
| Belgium     | Arabic   |
| Belgium     | Dutch    |
| Gibraltar   | Arabic   |
| Greece      | Greek    |
| France      | Arabic   |
| Sweden      | Arabic   |
| Germany     | Greek    |
| Denmark     | Arabic   |
+-------------+----------+

But what if we want Dutch speaking countries first? We can’t use order by Language desc or asc since Dutch results are in the middle of either sorting. Instead:

1
2
3
4
5
SELECT a.Name, b.LANGUAGE FROM Country a
JOIN CountryLanguage b ON (a.Code = b.CountryCode)
WHERE a.Continent = "Europe"
AND b.LANGUAGE IN ("Arabic", "Dutch", "Greek")
ORDER BY LANGUAGE="Dutch" DESC, LANGUAGE;

This orders the results with records having Language = “Dutch” first, followed by all the others ordered by their Language value. And this is the desired result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-------------+----------+
| Name        | Language |
+-------------+----------+
| Netherlands | Dutch    |
| Belgium     | Dutch    |
| France      | Arabic   |
| Gibraltar   | Arabic   |
| Netherlands | Arabic   |
| Denmark     | Arabic   |
| Belgium     | Arabic   |
| Sweden      | Arabic   |
| Albania     | Greek    |
| Germany     | Greek    |
| Greece      | Greek    |
+-------------+----------+

You can specify as many values in the order by clause as you want, each also allowing an asc or desc modifier. If you have a number of values in one field you want to specify, the field keyword can save you some typing:

1
2
3
4
5
SELECT a.Name, b.LANGUAGE FROM Country a
JOIN CountryLanguage b ON (a.Code = b.CountryCode)
WHERE a.Continent = "Europe"
AND b.LANGUAGE IN ("Arabic", "Dutch", "Greek", "Fries")
ORDER BY FIELD(LANGUAGE, "Greek", "Dutch", "Fries", "Arabic");

Returning the customized ordering:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+-------------+----------+
| Name        | Language |
+-------------+----------+
| Albania     | Greek    |
| Germany     | Greek    |
| Greece      | Greek    |
| Netherlands | Dutch    |
| Belgium     | Dutch    |
| Netherlands | Fries    |
| Belgium     | Arabic   |
| Gibraltar   | Arabic   |
| France      | Arabic   |
| Netherlands | Arabic   |
| Denmark     | Arabic   |
| Sweden      | Arabic   |
+-------------+----------+
Tags: ,
September 7, 2009 - 12:56 PM No Comments

« Older Entries

Twitter links powered by Tweet This v1.6.1, a WordPress plugin for Twitter.