clubsandwich.datastore

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
  else:
    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:
print(monster_types.gnome_easy)
# > MonsterType(id='gnome_easy', strength=1, color='#ff0000', char='g')
print(monster_types['gnome_easy'])
# > same thing

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

monster_types.reload()

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

Path to the file

identifier

See Reader.identifier

skip_first_line

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

read()

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.

add_source(reader)
Parameters:reader (Reader) –

Add a source that reads from the given reader.

add_sources_with_glob(pattern, reader_factory)
Parameters:
  • 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)
items

Iterator of all items

keys()

Iterator of all item identifiers

reload()

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.

unload()

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.

identifier

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

read()

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.

reader

Some Reader subclass or duck-typed class.

row_class

Class to instantiate for each row

fields

Sequence of pairs mapping field name to value factory

defaults

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

items

List of items read from the source

keys()

Iterator of all keys in this source

reload()

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