dbf.py is a pure Python library that provides access to dBase III, Foxpro, and
Visual Foxpro database tables.

It does not currently support index files, auto-incrementing fields, nor
Varchar fields.

Unicode is returned for Character/Memo fields (unless they are marked as
binary or nocptrans).

datetime.date/None is returned for Date (empty values are None)

True/False/None is returned for Logical ('?', and any other unknown value,
is None).  Note:  when using tests like `if record.some_logical:` both False
and None will fail.

int/float/None is returned for Numeric (depending on decimals == 0; empty
values, and '*' values are None (Visual Foxpro stores '*' when value is
too big to fit in field (this module raises an Exception instead))).

In all cases returns Null if a Null was stored (Nulls are not supported in
dBase III tables).


Helper classes include
  - Logical: a tri-state boolean that mimics bool() where possible
    Note: when using tests like `if record.some_logical:` if the value is
          Unknown a TypeError is raised.
  - Char: auto-trims trailing-whitespace, and uses trailing-whitespace 
          insenstive compares
  - Index: in-memory index of table
  - List: combination set- and list-like structure that leaves records
          on disk until needed


sample table & data:

  sample = dbf.table('/temp/sample'; "name C(30); age N(3,0); wisdom M")

  record = sample.append()
  record['name'] = 'Ethan'
  record['age'] = 37
  record['wisdom'] = 'Python rules!'

  record = {'name':'Allen', 'age':51, 'wisdom':'code smarter, not harder'}
  sample.append(record)

  sample.append()
  record = sample[-1]
  record.name = 'Alexis'
  record.age = 29
  record.wisdom = 'take a break!  refresh the little grey cells!'

retrieving data to store it somewhere else:
  source = dbf.table('/some/path/to/file.dbf')
  for record in source:
    data = dbf.scatter(record, as_type=dict)   # creates dictionary {fieldname:value, fieldname:value, ...}
    data = list(record)             # creates list of values in field order
    data = tuple(record)            # creates a tuple of values in field order
    # note that records support indexing: record[1] == record.age == record['age']
    # do something with the data


-------
Summary
-------

Python package for reading/writing dBase III and VFP 6 tables and memos

Goals:  programming style with databases
    - table = dbf.Table('table name' [, 'fielddesc[; fielddesc[; ....]]]')
        - fielddesc examples:  name C(30); age N(3,0); wisdom M; marriage D
    - record = [ table[int] | table.[ current_record | next_record | previous_record ]() ]
    - record.field | record['field'] accesses the field

NOTE:   Of the VFP data types, auto-increment and variable-length character fields are not implemented.
        Record fields are not individually writable unless using `with` or the Process function:

            with record:
                record.some_field = some_data

            for record in Process(table):
                record.some_field = some_data

        with `with` the record is written to disk in `__exit__`; with `Process` the record is written
        to disk at the start of the next loop.

Examples:

    Create a test table:
        table = dbf.Table('temptable', 'name C(30); age N(3,0); birth D')

    Populate it:
        for datum in (
                ('John Doe', 31, dbf.Date(1979, 9,13)),
                ('Ethan Furman', 102, dbf.Date(1909, 4, 1)),
                ('Jane Smith', 57, dbf.Date(1954, 7, 2)),
                ('John Adams', 44, dbf.Date(1967, 1, 9)),
                ):
            table.append(datum)

    Export to csv:
        dbf.export(filename='filename', header=False)

    Iterate over it:
        for record in table:
            print "%s was born on %s, so s/he is %d years of age" % (record.name, record.birth, record.age)

    Create a new table from a csv file (all character fields now):
        table = dbf.from_csv('filename.csv') # this has field names of f0, f1, f2, etc
    or
        table = dbf.from_csv('filename.csv', field_names="name age birth".split())


    table = dbf.Table('temptable')  #reopen original file

    Sort it:
        name_index = table.create_index(lambda rec: rec.name)
        for record in name_index:
            print record.name

    Search using the sort:
        first = name_index.index(match=('John ',), partial=True) # or IndexError
    or
        first = name_index.find(match=('John ',), partial=True)  # or -1

    Delete a record:
        dbf.delete(table[1])

    Check if a record has been marked as deleted:
        record = table[1] # for example
        if dbf.is_deleted(record):
            print "table.pack() will physically remove this record! (and all other deleted records)"

    Ignore deleted records:
        active = table.create_index(lambda rec: dbf.recno(rec) if not dbf.is_deleted(rec) else dbf.DoNotIndex)
        for record in active:
            # no deleted records here

    Specify data to write with write:
        dbf.write(record, age=39)

    Access record fields via attribute access:
        for record in table:
            print record.name

    or dictionary-style access:
        for record in table:
            print record['age']

    and let's not forget index-style access:
        for record in table:
            print record[2]

    can even use slices if you like:
        table = dbf.from_csv('filename.csv', field_names="name age birth".split())
        for record in table:
            print record[:2]            # prints first two fields
            print record['name':'age']  # also prints first two fields

    Primitive SQL (work in progress):
        records = table.pql("select * where name[0] == 'J'")
        for rec in records:
            print rec, '\n'

Field Types  -->  Python data types
  Dbf
    Character       unicode
    Date            datetime.date
    Logical         boolean or None
    Memo            unicode (same as character)
    Numeric         if N(x, 0) int; if N(x, 1+) float
  FP
    Float           same as Numeric
    General         binary data
    Photo           binary data
  VFP
    Currency        Decimal
    Double          float
    Integer         int
    DateTime        datetime.datetime
  Note: if any of the above are empty (nothing ever stored in that field) None is returned
