authors: Ellery Galvin, Alexandra Provo
date updated: 2026-05-08
contact: ellery.galvin@colorado.edu, crdds@colorado.edu
This project checks spreadsheet files (.csv, .tsv, and .xlsx) for common data curation issues.
Each test reports:
- what failed,
- where it failed, and
- an example offending value.
pip install FAIRspreadsheetsimport fair
suite = fair.Test_Suite("your_data.csv")
suite.run()
suite.report()
suite.save(format = "csv")Test_Suite(wb_path, to_run=None, to_skip=None, config_path=None)
- wb_path: path to the workbook
Which tests to run? Defaults to all. Else, specify one of
- to_run: list of test names to run, lower case strings as below
- to_skip: list of test names to skip, lower case strings as below
Extra arguments to replace defaults for tests (see below):
- config_path: path to the config file, replaces defaults where specified
Test_Suite methods:
- run(): runs the tests
- report(): prints the results to the console
- save(format = "json"): saves the results to a JSON file
- save(format = "csv"): saves the results to a CSV file
file_name_length: filename length is within allowed boundsfile_name_whitespace: filename contains no whitespacefile_name_final: filename does not include the wordfinalfile_name_word_separation: filename does not mix camelCase, underscores, and dashesfile_name_special_characters: filename has no disallowed special charactersfile_encoding: text files decode cleanly in the configured encodingfile_delimiter: delimiter in .csv or .tsv file matches the extension
sheet_empty: worksheet is not emptysheet_name: sheet name contains no whitespace or disallowed special characterssheet_upper_left_corner: table begins at upper-left (no leading blank rows/columns)sheet_multi_table: sheet does not appear to contain multiple separate tables
header_duplicates: duplicate header names are flaggedheader_id: first header should not be exactlyIDheader_length: header length is within allowed boundsheader_first_char: headers should not start with digitsheader_space: headers should not contain spacesheader_untrimmed_white_space: leading/trailing whitespace in headers is flaggedheader_word_separation: headers should not mix camelCase, underscores, and dashesheader_special_characters: headers should not contain disallowed special charactersheader_date: date-like header names (for combined date fields) are flaggedheader_mixed_datatypes: columns with mixed numeric/text values are flagged
cell_aggregate_row: aggregate-like summary row at bottom (e.g., total/sum) is flaggedcell_special_characters: disallowed special characters in cells are flaggedcell_untrimmed_white_space: leading/trailing whitespace is flaggedcell_newlines_tabs: tabs/newlines/vertical tabs in cells are flaggedcell_missing_value_text: text placeholders for missing values (NA,null,-, etc.) are flaggedcell_question_mark_only: cells equal to?are flaggedcell_white_space_only: whitespace-only cells are flaggedcell_number_space: cells containing only digits+spaces are flaggedcell_dates: dates not matching required format are flaggedcell_scientific_notation: scientific notation in text cells is flaggedcell_units: values that combine number+unit in one cell (e.g.,10kg) are flagged
Options are set in a JSON config file (see config.example.json).
Important conventions:
- Top-level key = lower-case class name (for example
cell_dates) - Option names = keyword-only
__init__args for that test - Only the tests and options below are accepted
file_name_lengthmin_length(int, default5): minimum allowed filename lengthmax_length(int, default32): maximum allowed filename length
file_name_special_charactersspecial_char_pattern(regex string, default[!@#$%^&*()+=\[\]{};:'"|\\,<>\?/]): disallowed characters
file_encodingvalid_encoding(string, defaultutf-8): expected text encoding for CSV-like files
file_delimitervalid_delimiter_pairs(list of tuples, default[(",",".csv"),("\t",".tsv")]): expected pairings of delimiters and file extensions. Note: currently not possible to configure this in a JSON config file since JSON does not allow tuples
sheet_namespecial_char_pattern(regex string, default[!@#$%^&*()+=\[\]{};:'"|\\,<>\?/]): disallowed characters
header_lengthmin_length(int, default1): minimum header lengthmax_length(int, default24): maximum header length
header_special_charactersspecial_char_pattern(regex string, default[!@#$%^&*()+=\[\]{};:'"|\\,<>\?/]): disallowed characters
header_datedate_keywords(list of strings, default["date", "datetime", "timestamp"]): keywords used to detect date-like headers
cell_aggregate_rowaggregate_words(list of strings, default["total","sum","average","count","min","max"]): terms used to detect summary rows
cell_special_charactersdefault_pattern(regex string): disallowed chars for normal columnsurl_pattern(regex string, default^https?://): validation pattern for URL columnsfree_text_pattern(regex string): disallowed chars for free-text columnsurl_columns(list of strings, default[]): columns validated withurl_patternfree_text_columns(list of strings, default[]): columns validated withfree_text_patternskip_columns(list of strings, default[]): columns skipped by this test
cell_datesdate_columns(list of strings, default[]): explicit date columnsauto_detect_columns(bool, defaultfalse): infer likely date columns automaticallyformat_code(string, default%Y/%m/%d): requireddatetime.strptimedate formatdate_column_threshold(float0..1, default0.8): fraction of parseable values needed to auto-mark a column as date-like
cell_unitsunit_abbreviations(list of strings): unit suffixes used to detect values like12kg
Tests without options should be configured with an empty object or omitted.
Only failed (or not-completed) tests appear in saved output. Passing tests are omitted.
- Root object: keys are sheet names, plus
filefor file-level failures - Value per sheet: object keyed by test name
- Value per test:
message: failure/not-completed messageissues: object oflocation -> example
- For JSON compatibility, non-string issue keys (such as tuple coordinates) are stringified.
Columns are exactly:
path: input workbook pathsheet: sheet name (filefor file-level tests)test_name: lower-case test class namemessage: failure/not-completed messagelocation: issue key (string form)example: issue value
Each row corresponds to one issue entry from one failed test.
- .gitignore excludes the data directory from version control, and other files
- config.example.json is an example configuration for test options
- demo.xlsx is a file on which to test detectors.py
- detectors.py is the main module that implements a series of automated tests for data curation workflows
- pyproject.toml is the specification for the PyPI package
- LICENSE contains the licensing information for this software
Test_Suitediscovers concrete tests automatically from subclasses ofTest- Tests are grouped by level using class name prefixes:
file_,sheet_,header_,cell_ - Dependencies are declared via
Has_Dependency; the suite executes tests in dependency-safe order - Each test writes failures into
self.issues, then callsTest.validate(...)to set status/message trimmed_results()filters outputs to failed/not-completed tests only
- Choose the right base class:
- file checks: inherit
File - sheet checks: inherit
Sheet - header checks: inherit
Header - cell checks: inherit
Cell
- file checks: inherit
- Name the class with underscores (example
Cell_My_New_Check).
The runtime test key becomes lower-case class name (cell_my_new_check). - Put user-configurable settings in keyword-only args:
- use
def __init__(self, *, ...) - validate types/ranges in
__init__
- use
- Implement
validate(...)and populateself.issuesas:- key: location (header name, tuple coordinate,
filename, etc.) - value: offending example/value
- key: location (header name, tuple coordinate,
- End validation with
Test.validate(self, fail_message, pass_message)so status/message are consistent. - If your test depends on another test, add
Has_Dependency.__init__(self, DependencyClass)and use dependency outputs invalidate. - Add documentation:
- include the new test in the test list above
- add config options under the correct section (if any)
- update
config.example.jsonwith sensible defaults