Convenient access to spreadsheet-style game data files. CSV is the only included implementation, but writing new readers is trivial.

Here’s a simple example:

def f_char(val):
  "Ensure *val* is a one-character string"
  if isinstance(val, str) and len(val) == 1:
    return val
    raise ValueError("Bad character value: {!r}".format(val))

# Imagine you have a directory of CSVs with monster definitions.
# One of them, gnomes.csv, might look like this:
#  ID,            Strength,   Color,    Char
#  gnome_easy,    1,          #ff0000,  g
#  gnome_medium,  2,          #ffff00,  g
#  gnome_hard,    3,          #ff00ff,  g
#  gnome_huge,    4,          #880000,  G

# To read them, create a :py:class:`DataStore`, passing a class name
# for items and an ordered mapping of field name to value:
monster_types = DataStore('MonsterType', (
  ('id', str),
  ('strength', int), # int('5') = 5
  ('color', str),
  ('char', f_char),  # see helper at top of example

# You can load all the CSVs at once, like this:
monster_types.add_sources_with_glob('data/monsters/*.csv', CSVReader)

# Now you can access any monster type easily:
# > MonsterType(id='gnome_easy', strength=1, color='#ff0000', char='g')
# > same thing

# And if you edit your data files, you can reload them at runtime to
# immediately pick up changes:


Limitations and details:

  • The first field in the mapping must be a unique identifier, unique across all readers.
  • Identifiers may overlap built-in Python method names or start with numbers. To access these values by attribute, prefix them with r_. So a value with id 5 could be accessed by data_store.r_5. Or you could just use dict-style lookup, like data_store['5']. To avoid these issues, use Python-identifier-legal uppercase string identifiers.
  • You may pass default values to the DataStore. These only matter if your data may be missing keys (i.e. if your reader emits dict``s), so you can just pass ``None if you want.
class clubsandwich.datastore.CSVReader(path, skip_first_line=True)

Returns the lines from a single CSV file. If your first line is just column labels, you can skip it (this is the default behavior).


Path to the file


See Reader.identifier


If True (default), always skip the first line of the file.


Iterator of CSV values. Values are lists.

class clubsandwich.datastore.DataStore(type_name, fields, defaults=None)

Collection of data sources for convenient access. See example in module docs for a basic guide.

Parameters:reader (Reader) –

Add a source that reads from the given reader.

add_sources_with_glob(pattern, reader_factory)
  • pattern (str) – Glob pattern (docs)
  • reader_factory (function) – reader_factory(path) -> Reader

Add multiple sources for paths that match the given pattern (glob). For example, if you wanted to read all CSV files in your “monsters” directory, you’d do this:

data_store.add_sources_with_glob('data/monsters/*.csv', CSVReader)

Iterator of all items


Iterator of all item identifiers


Reload all sources. Does not pick up new files if you used a glob. To handle that case, call unload() and then re-add all your sources.


Remove all sources.

class clubsandwich.datastore.Reader

Abstract base class for a reader. You can subclass this for your own readers if you want, but duck typing is probably less verbose.


String identifier for this reader. Only used to provide helpful exception messages.


Iterator of sequences or dicts from the file or data structure. Like ('a', 'b', 'c') or {'f1': 'a', 'f2': 'b', 'f3': 'c'}.

class clubsandwich.datastore.Source(reader, row_class, fields, defaults=None)

Stores and indexes values from a reader. You shouldn’t need to worry about this class much; it’s created automatically for you, and sources are accessed in aggregate via a DataStore.


Some Reader subclass or duck-typed class.


Class to instantiate for each row


Sequence of pairs mapping field name to value factory


Default values for the fields, or None if you’re quite sure all values will be specified


List of items read from the source


Iterator of all keys in this source


Run the reader again; replace all stored items

exception clubsandwich.datastore.ValidationException

Thrown when a row does not match the mapping in your DataStore instance