Home

Advertisement

Previous Entry | Next Entry

My Challenges and Their Resolutions

  • Jun. 13th, 2008 at 9:20 AM
Setting up DrProject on Windows
Problem: I need to use a screen magnifier which has drivers only for Windows.

Solution: So in the first week of my job I spent time modifying the DrProject install and documenting instructions on installing DrProject on Windows. Here's a more permanent link.

Tools for Developing DrProject
Problem: DrProject is larger than most software code bases I've worked with and some big-boy tools were in order.

Solution: Here are the tools I use:
  • Eclipse as my IDE, and I've set it to convert tabs to 4-spaces (Windows -> Preferences -> General -> Editors -> Text Editors)
  • Python 2.5, the bindings for SVN, Java 6, and Cygwin
  • IPython and the corresponding readline tool for debugging, testing out Python code theories, running parts of the DrProject code under a controlled environment, and many other necessary debugging features
  • nose (attainable by easy_install nose) for testing and profiling
  • SVN plugin for Eclipse (http://subclipse.tigris.org/update_1.2.x)
  • PyDev plugin for Python in Eclipse (http://pydev.sourceforge.net/updates/)
Shortcuts
Problem: Even with the given tools, you need ways to do things quickly.

Solution: I'm working on the ticketing system for DrProject so I've added the following line to my ~/.bashrc in Cygwin so I'm where I want to be as soon as I open Cygwin:

cd /DrProject/drproject/ticket/tests

I've set up alias in ~/.bashrc as follows:

alias tst='nosetests'
    (-v for verbose output which is often useful)
    (follow tst with the file name to run a particular test file only)
    (follow the filename with :<ClassName>.<test_function> to run a particular test)
alias profile='tst --with-profile --profile-stats-file=<output filename>'
    (here's how to analyze the data in the output file)
alias setup='/setup.sh'
alias init='/init.sh'
alias go='/go.sh'
    (these aliases point to shell scripts which record the CWD so I can setup DrProject from any directory)
    (here is an example of the contents of one of these scripts; this could be done in other ways)
    #!/bin/sh
  DIR=`pwd`
  cd /DrProject/
  python setup.py develop
  cd $DIR
alias flake='c:/cygwin/Pyflakes/bin/pyflakes'

I find it very useful to fold and unfold functions and classes in Eclipse, which can be done using CTRL+9 and CTRL+0

I also have a macro for IPython to set up a fixture so I can immediately start using the DrProject code there. Using TAB for completion is very useful and can shed light on available functions. "?" following a a class name or function name will give the docstring and "??" will give the code if available. Here's my macro as an exmaple:

from datetime import *
from drproject import *
from drproject.api import *
from drproject.db.util import flush
from drproject.scripting import *
from drproject.ticket.model import *
from drproject.ticket.api import *
from drproject.ticket.types import *
from drproject.ticket.roadmap import *
from elixir import *
from sqlalchemy import *

env = Environment()
env.configure('/cygwin/DrProject/hacking/drp_root/')
setup_all()

p = Project.query.first()

t1 = TicketSystem().add_ticket(p, 'me1')
t2 = TicketSystem().add_ticket(p, 'me2')
t3 = TicketSystem().add_ticket(p, 'me3')
t4 = TicketSystem().add_ticket(p, 'me4')
t5 = TicketSystem().add_ticket(p, 'me5')
t6 = TicketSystem().add_ticket(p, 'me6')
t7 = TicketSystem().add_ticket(p, 'me7')
t8 = TicketSystem().add_ticket(p, 'me8')
t9 = TicketSystem().add_ticket(p, 'me9')

It can be created using %macro <macro name> <IPython line numbers with the instructions to include; can be given sequentially with spaces between them or as a range sing -; e.g. 1 2 3 4-10>

Then you can save the macro using store <macro name>, and edit it using notepad by ed <macro name>; don't forget to store it again when you change it.

Managing Imports
Problem: Imports can sometimes cause problems

Solution: Try to avoid from X import * because you want to be explicit in what imports you're using, and importing everything may lead to cyclic imports and other problems. Use PyFlakes to help with determining which imports are being used, which are unused, etc.

If you get an error like "cannot import X", you may have a cyclic import. If this cannot be avoided, you can move the import from one of the files down from the top of the file into the function that's using it.

When using Elixir, you can create ManyToOne, OneToMany, etc relationships. When you do this, you need to pass in the name of the class which represents the table being referenced. E.g. f = ManyToOne('Y', primary_key=True). Even though "Y" is a string here, that class still needs to be imported.

Merging Trunk into your Branch
Problem: SVN's built-in merging is retarded.

Solution: Use the svnmerge script.

Libraries that DrProject Uses
Problem: DrProject uses a lot of libraries and third-party stuff; learning to use all this is very necessary since these tools make life a lot easier.

Solution: DrProject uses at least the following:
  • the Python tutorial
  • SQLAlchemy for interacting with the database in an object oriented way
  • Elixir for creating the database schema using Python classes
  • Dojo as a layer on top of JavaScript; here are some demos
  • Kid to send Python objects back and forth between Python and HTML files and resolve Python code in HTML
Making Tests Run Faster
Problem: Some tests were just too slow.

Solution: In query.py, all I needed to do was create the database once since I wasn't doing any updates (just queries). But the setUp() function gets run for each test in unittest.TestCase. You canreplace the setUp() function signature with this:

    @classmethod
    def setup_class(self)
       ...

Eclipse will no longer be able to run the tests correctly, but you can use nose to run them and it'll run the setup only once per class (not per test method). There is a similar function for module level setup.

Defining Default behaviour in Elixir
Problem: Elixir allows many options (including table names) using the using_options and using_table_options functions. E.g.

class MilestoneChange(Entity):
    milestone = ManyToOne('Milestone', primary_key=True)
    time = elixir.Field(DateTime, default=datetime.now, primary_key=True)
    ...

    using_options(tablename='milestone_change', order_by=['time'])

But some options persist for each class and you don't want to declare them each time.

Solution: You can import and use (at the top of the file outside each class) the function options_defaults. E.g.

    options_defaults['shortnames']=True
    options_defaults['inheritance']='multi'

Database Polymorphism
Problem: Sometimes you want classes which extend Elixir's Entity class (and hence are part of the database schema) to use polymorphism, inheritance, all that good stuff.

Solution: Simply extend the parent class and remember to set the 'multi' option default. The parent class will automatically be given a a row called 'row_type' which it will use to differentiate between between its descendants.

You can have many layers of inheritance. E.g.

class Field(Entity):
    details = ManyToOne('FieldDescription')
    ticket = ManyToOne('Ticket')

    def parse(self, val):
       ...

    def html(self):
       ...

    def __unicode__(self):
       ...

class PrimitiveField(Field):
    pass

class ComplexField(Field):
    def get_filter_col(self):
       ...

class WikiField(PrimitiveField):
    value = elixir.Field(UnicodeText)

    def parse(self, val):
        ...

    def html(self):
       ...

    def __unicode__(self):
       ...

class TicketField(ComplexField):
    value = ManyToOne('Ticket')
    filter_col = 'id'

    def parse(self, val):
        ...

    def __unicode__(self):
       ...

    def get_filter_col(self):
       ...

Querying Polymorphic Types
Problem: If I had a variable f of type Field and I knew that it was more specifically of type WikiField, I couldn't just assume that; the queries wouldn't work.

Solution: When dealing with polymorphism, use Alchemy's of_type(), with_parent(), and any() functions. Alchemy will do a join with the appropriate tables. E.g.

Assume the Ticket class has a row called "fields" of type Field and I want THE WikiField called X with value Y:
        Ticket.fields.of_type(WikiField).any(and_(WikiField.name==X, value==Y))

If I want ANY field of type WikiField with value Y:
        Ticket.fields.of_type(WikiField).any(value==Y)

Getting Specific Module Members
Problem: I wanted all classes that extended Field in a dictionary that maps the name of the class to the class itself; e.g. 'WikiField' => WikiField.

Solution:
from inspect import isclass
def get_field_classes(cls, members=locals()):
    return dict((c.__name__, c) for c in filter(isclass, members.values())
                if issubclass(c, cls) and c is not cls)

Notice the named parameter "members" assigned locals(). This is done because locals() within the function is different from locals() outside of it.

Merging Dictionaries
Problem: Sometimes I have two Python dictionaries which I want to combine into one big one.

Solution: I use the following idiom to merge dict a and b

c = dict(a.items() + b.items())

Shadowing
Problem: I wanted to call a class Field but Elixir already uses that name.

Solution: So I called my class Field and imported elixir. I then used elixir.Field for elixir's version.

Accessing Table Properties
Problem: I want to access information about a database table.

Solution:
  • list of column names: <Table>.c.keys()
  • a particular column object: <Table>.c.<column name>
  • the value of a column for a particular row: <table_instance>.<column name>
  • the table name: <Table>.table.name
  • the table properties: <Table>.table
  • pretty list of columns: <Table>._descriptor._columns
  • primary keys: <Table>._descriptor.primary_keys
  • natural ordering of the table: <Table>._descriptor.order_by
  • foreign keys: <Table>._descriptor.relationships
  • foreign key table object: <Table>._descriptor.relationships[<#X>].target
Tables with Non-Primitive Values (Foreign Keys)
Problem: If I wanted to dynamically get values from tables, it was fine so long as the `value` field I was looking for was a primitive data type. But if it was a foreign key, I didn't know which of the columns of the foreign table to return (or do whatever with).

Solution: I added a variable (table column) to the class that held the reference. It was a string which represented the column of the foreign table to use. Its parent class had a function which resolved this string and rturned the appropriate foreign column object.

 I also added a __unicode__ function to the classes which returned the desired column from the foreign table. So unicode(X) where X is of type MilestoneField will return the name of the milestone which X is referencing.

Comments

( 2 comments — Leave a comment )
[info]blargz wrote:
Jun. 16th, 2008 02:20 am (UTC)
Re: Merging dictionaries
There's actually an easier way when it's just two dicts:

c = dict(a, **b)

This avoids creating intermediate lists.

$ python -m timeit -s "f = lambda n: dict(zip(range(n), range(n))); x = f(5); y = f(16)" "z = dict(x.items() + y.items())"
100000 loops, best of 3: 7.23 usec per loop
$ python -m timeit -s "f = lambda n: dict(zip(range(n), range(n))); x = f(5); y = f(16)" "z = dict(x, **y)"
100000 loops, best of 3: 2.22 usec per loop
And check out the massive speed boost!
[info]nickjamil wrote:
Jun. 16th, 2008 02:17 pm (UTC)
Re: Merging dictionaries
Thanks !
( 2 comments — Leave a comment )

Profile

[info]nickjamil
nickjamil

Latest Month

July 2008
S M T W T F S
  12345
6789101112
13141516171819
20212223242526
2728293031  
Powered by LiveJournal.com
Designed by Taichi Kaminogoya