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 id5could be accessed bydata_store.r_5. Or you could just use dict-style lookup, likedata_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 emitsdict``s), so you can just pass ``Noneif 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¶ 
- 
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_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.- 
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
Noneif 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
-