Change History
==============

Version 0.13.0 -
---------------
- Modified all Table import methods to directly accept a string containing
  the data to be imported. If the input is a multiline string, then it
  is assumed to contain the actual data to be imported. If the input is a
  string with no newlines, then it is treated as a filename, and the file
  is opened for reading. The input can also still be specified as a
  file-like object, such as would be the case if reading a file with
  an encoding other than the default 'UTF-8'. This capability further
  simplifies notebook integration and test and experimentation.

- Renamed format() to formatted_table().

- Introduced new format() method to generate a list of strings, one per
  row in the table. format() is called passing a single string as an
  argument, to be used as a str.format() template for converting each
  row to a string.

Version 0.12.0 -
---------------
- Modified Table.select() to accept '*' and '-xxx'-style names, to
  indicate '*' as 'all fields', and '-xxx' as "not including xxx". This
  simplifies selecting "all fields except xxx".

- PivotTable.summary_counts() is renamed to as_table(). summary_counts()
  is deprecated and will be dropped in a future release.

- Added Table.as_html() and PivotTable.as_table().as_html() methods
  to support integration with Jupyter Notebook. Also included sample ipynb
  file. as_html() takes a list of fields to be included in the table,
  following the same syntax as Table.select(). This API is still
  experimental, and may change before 1.0.0 release.

- Added Table.all.xxx attribute accessor, to yield out all the values of a
  particular attribute in the table as a sequence.

- Added FixedWidthReader class to simplify import of data from files with
  fixed width columns. See examples in HowToUseLittleTable.txt.

- Fixed bug where dict.iteritems() was used in some cases, all
  now converted to using items().

- Updated xxx_import methods to return self, so that tables can be
  declared and loaded in a single statement:
  
        data_table = Table().csv_import('data_file.csv')

- Added optional row_class argument argument to all xxx_import methods
  to designate a user class to use when constructing the rows to be imported
  to the table. The default is DataObject, but any class that supports 
  `Class(**attributes)` construction (including namedtuples and 
  SimpleNamespace) can be given. If the desired class does not support
  this kind of initialization, a factory method can be given instead.

- Deleted deprecated Table.run() and Table.addfield methods. run()
  did nothing more than return self; addfield has been replaced by
  add_field.

- Also deleted deprecated attribute access to table indexes. Indexed
  access is now done as:
  
        employee = emp_data.by.emp_id['12345']
        qa_dept_employees = emp_data.by.department_name['QA']


Version 0.11 -
--------------
- Performance enhancement in insert(), also speeds up pivot() and other
  related methods.
  
- Fixed bug in fetching keys if keys evaluate to a falsey value (such as 0).
  Would also manifest as omitting columns from pivot tables for falsey 
  key values.


Version 0.10 - 
-------------
- Deprecated access to indexed fields using '<tablename>.<fieldname>', as
  this obscures the fact that the fields are attributes of the table's objects,
  not of the table itself. Instead, index access will be done using the 'by'
  index accessor, introduced in version 0.6 (see comments below in notes
  for release 0.9). Indexes-as-table-attributes will be completely removed 
  in release 1.0.

- Deprecated Table.run(). Will be removed in release 1.0.

- Added Table.info() method, to give summary information about a table's
  name, columns, indexes, size, etc.

- Extended interface to Table.csv_import, to accept passthru of 
  additional named arguments (such as 'delimiter' or 'fieldnames') to the
  DictReader constructor used to read the import data.

- Extended interface to Table.csv_export and json_export, to support
  addition of 'encoding' argument (default='UTF-8') for the output file.
  (Python 3 only)

- Added set item support to DataObject, to support "obj['a'] = 100" style
  assignments. Note that DataObjects are only semi-mutable: a given key or 
  attribute can only be assigned once, not overwritten.

- Added more list-like access to Table, including del table[index], 
  del table[start:end:step] and pop().

- Added 'key' argument to Table.unique, to support passing a callable to 
  unique for special cases for defining what makes an object 'unique'. 
  Default is the prior behavior, which is a tuple of all of the values of
  the object.

- Added exceptions to DataObject when attempting to modify an existing
  attribute. New attributes are still supported, but existing attributes
  cannot be overwritten. (Applies to both attribute and indexed assignments.)
  Formerly, these assignments would simply fail silently.

- Using OrderedDict when supported, to preserve field order in JSON output.

- Miscellaneous documentation cleanup.

Version 0.9 - 
-------------
- Python 3 compatibility.

- (feature previously released in version 0.6 but not documented)
  Added 'by' index accessor on tables, to help distinguish that the index 
  attributes are not attributes of the table itself, but of the objects 
  in the table:

    # using unique index 'sku' on catalog table:
    print(catalog.by.sku["ANVIL-001"].descr)
    
    # using non-unique index 'state' on stations table:
    stations.create_index("state")
    for az_stn in stations.by.state['AZ']:
        print(az_stn)

  Updated inline examples to use '<table>.by.<index_name>' syntax.

Version 0.8 -
-------------
- Added json_import and json_export methods to Table, with same interface
  as csv_import and csv_export. The import file should contain a JSON
  object string per row, or a succession of JSON objects (can be pretty-
  printed), but *not* a single JSON list of objects.

- Included pivot_demo.py as part of the source distribution.

Version 0.7 -
-------------
- Added support for '+=' operator, for in-place union. Unlike '+', does
  not return a new Table, but instead updates the LHS table in place.
  
- Renamed addfield to add_field to be consistent with other two-word 
  method names in the Table interface.  addfield is still retained for
  compatibility (just calls add_field with called args); but is deprecated
  and will be removed in a future version.

- Added unique() method on Table, to return a new Table with duplicate
  entries removed. To support comparison of DataObjects that might be in
  the table, DataObjects now support __eq__ and hash methods.

- Changed interface to Table.select(). Formerly was called as

    table.select('field1','field2','field3')

  But now the fields are specified as either a single space-delimited
  string or a list of strings.

    table.select('field1 field2 field3')

- The special '_orderby' argument to Table.where() is deprecated, since
  following the where() call with sort() is so straightforward.

- The special '_unique' argument to Table.select() is deprecated, since
  following the select() call with unique() is so straightforward.


Version 0.6 -
-------------
- Modified __getitem__ so that retrievals of slices return new Tables
  instead of just lists of objects (essentially adding sliced indexing
  as another chained accessor).

- Added count_fn to the dump_counts method of PivotTable, so that a
  summarizing function other than mere counting can be used for each cell
  in the pivot table.  Here is an example of summarizing population by
  state and elevation, where each record has attributes state, elevation
  (reduced to nearest 1000'), and population:
  
    piv = places.pivot('state elevation')
    piv.dump_counts(count_fn=lambda recs:sum(r.population for r in recs))

- Added sort(), initial version contributed by Adam Sah. sort will take
  a key function, or a string containing comma-separated attribute names.
  Attributes can be qualified with "desc" to indicated sort to be done
  in descending order.

- Modified insert() and compute() to return self, for chaining support.

- Renamed maxrecs parameters to 'limit', to be more similar to the same
  concept in SQL.
  
- Merged query and where into a single consolidated function named 'where'
  for selecting matching records from a table.

- Add union function to add records of two tables. '+' between two tables
  will perform union; '+' between join terms, or a table and a join term,
  will perform join. Returns a new Table.

- join() verifies that all named attributes exist in one of the source
  tables

- join() will automatically create indexes for join columns if indexes
  do not already exist


Version 0.5 - 
-------------
- Added groupby() method to Table (thanks, Adam Sah!) to generate tables 
  of computed values keyed by an existing or computed attribute.

- Added optional name fields to clone and copy_template.

- Modified create_index and insert_many to return self, so that a 
  simple table creation and indexing could be done in a single chained 
  statement (also suggested by Adam Sah, thanks!)

- Fixed possible bug in PivotTable.values - now values are returned
  in the order of matching keys returned by keys().

- Fixed bugs in Table.pivot(), see pivot_demo.py


Version 0.4 -
-------------
- Added compute() method to Table to support global update of all objects 
  in the table, to add or modify a given attribute on each object.  compute()
  takes a function that computes the new or modified attribute value, taking 
  the entire object as its single input argument.  Can override DataObject's 
  write-only functionality (for example, when converting attributes entered 
  as a string to an int or float).  Also accepts a default value to use, in 
  case the computing function raises an exception.  Useful when creating a 
  new attribute that would be computed based on other values in the object.
  
- Added transforms argument to csv_import, to simplify the conversion of 
  string data values to int, float, or other non-string type.  The transforms 
  argument is a dict mapping attribute names to conversion functions, each 
  function taking the as-imported string value and return a new transformed 
  value.
  

Version 0.3 -
(renamed project from dulce to littletable)
-------------
- Improved exception message when duplicate or None value is given for a 
  unique index attribute.
  
- Support for namedtuples and __slots__-defining objects supplied by Colin 
  McPhail, thanks!

- Added Table.pivot() method, to return a pivot table on one or more 
  attributes.  New PivotTable class includes methods for extracting data in 
  both Table and tabular formats.

- Added more details to the docstring for Table.join, to more completely 
  describe the sequence of steps to join 2 or more Tables, using join(), or 
  join_to and '+'. Also, removed table_name argument in Table.join, to 
  simplify this call (the resulting table can be easily renamed using Table 
  call form).

- Python 3 compatibility changes, importing simplejson or json, as 
  appropriate - thanks again to Colin for the Python compatibility testing.

- Renamed _TableJoin to JoinTerm, and added documentation on how it is used 
  to build up table joins using '+' addition notation.


Version 0.2 -
-------------
- Fixed typo in module docstring, "wishlists" should be "wishitems" (as shown 
  correctly in dulce_demo.py). Also fixed typo in docs for Table.create_index, 
  caused by my lack of epydoc-fu.  Plus some general cleanup of the 
  docstrings.  Thanks, Colin McPhail for reporting these!

- Changed _orderby and _orderbydesc flags to just _orderby, taking a single 
  string containing a comma-separated list of attribute names (this is 
  necessary since **kwargs does not preserve arguments order).
  
- Changed Table attribute "name" to "table_name", so as not to collide with 
  a user-defined index on an attribute named "name".

- Added support for unique indexes to allow or disallow null key values.  If 
  null keys are allowed, then all records with a null key are stored in an 
  internal list.

- Added some join performance pickup, using the table with the smaller number 
  of keys as the outer loop in finding matching records.
  
- Added query performance pickup, when using multiple indexed attributes in 
  query.


Version 0.1 - 16 October 2010
-----------------------------
Initial prototype release
