csvdoctor 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 csvdoctor contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: csvdoctor
3
+ Version: 0.1.0
4
+ Summary: Diagnose and repair messy CSV files: encodings, delimiters, ragged rows, Excel damage and more. Zero dependencies.
5
+ Author: csvdoctor contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/jasonkeanuchristy1-cyber/csvdoctor
8
+ Project-URL: Issues, https://github.com/jasonkeanuchristy1-cyber/csvdoctor/issues
9
+ Project-URL: Changelog, https://github.com/jasonkeanuchristy1-cyber/csvdoctor/blob/main/CHANGELOG.md
10
+ Keywords: csv,data-cleaning,data-quality,encoding,excel,etl,cli,linter
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Topic :: Utilities
21
+ Classifier: Topic :: Text Processing :: General
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+ Requires-Dist: ruff>=0.5; extra == "dev"
29
+ Requires-Dist: build; extra == "dev"
30
+ Requires-Dist: twine; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # csvdoctor
34
+
35
+ [![CI](https://github.com/jasonkeanuchristy1-cyber/csvdoctor/actions/workflows/ci.yml/badge.svg)](https://github.com/jasonkeanuchristy1-cyber/csvdoctor/actions/workflows/ci.yml)
36
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
37
+ ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
38
+ ![Dependencies: none](https://img.shields.io/badge/dependencies-none-brightgreen.svg)
39
+
40
+ **Find out what is wrong with a CSV file, and fix it, in one command.**
41
+
42
+ CSV has no standard. Every export is a small gamble: Excel writes `;`-separated
43
+ Windows-1252 in many countries, databases dump UTF-16, web tools add invisible
44
+ byte-order marks, and somebody always sorts the spreadsheet and leaves an empty
45
+ column behind. `csvdoctor` tells you *exactly* what is wrong and writes a clean,
46
+ UTF-8, comma-separated copy, without touching your original.
47
+
48
+ - **Zero dependencies.** Pure Python standard library. Works anywhere Python 3.9+ does.
49
+ - **Honest.** It re-checks its own output and tells you what it could *not* fix.
50
+ - **Safe by default.** It never silently deletes rows or blanks values. Anything
51
+ destructive is opt-in.
52
+ - **Usable everywhere.** CLI, Python library, JSON output for scripts, and a
53
+ [pre-commit](https://pre-commit.com) hook for CI.
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install git+https://github.com/jasonkeanuchristy1-cyber/csvdoctor.git
59
+ # or, to keep it out of your project environments:
60
+ pipx install git+https://github.com/jasonkeanuchristy1-cyber/csvdoctor.git
61
+ ```
62
+
63
+ ## Quick start
64
+
65
+ ```console
66
+ $ csvdoctor check examples/messy.csv
67
+ examples/messy.csv
68
+ Encoding ........ Windows-1252
69
+ Delimiter ....... semicolon
70
+ Line endings .... CRLF
71
+ Size ............ 9 data rows x 8 columns
72
+
73
+ Found 15 issues (3 errors, 10 warnings, 2 info)
74
+
75
+ ERROR RAGGED_ROWS Wrong number of fields in 1 row (the header has 8): 1 too short.
76
+ rows: 7
77
+ -> fixed by `csvdoctor fix`
78
+
79
+ ERROR EXTRA_DATA Real data beyond the last header column in 1 row. This usually means an
80
+ unquoted delimiter inside a value (e.g. a comma in 'Smith, John').
81
+ rows: 8
82
+ -> needs manual review
83
+ ...
84
+ ```
85
+
86
+ (The full report is abridged here; [`examples/messy.csv`](examples/messy.csv) is a
87
+ real, deliberately broken file you can try it on.)
88
+
89
+ Now fix it:
90
+
91
+ ```console
92
+ $ csvdoctor fix examples/messy.csv --dedupe --normalize-nulls --neutralize-formulas
93
+ Fixed examples/messy.csv -> examples/messy.fixed.csv
94
+ - Re-encoded from Windows-1252 to UTF-8
95
+ - Changed delimiter from semicolon to comma
96
+ - Trimmed whitespace in 2 cells
97
+ - Repaired garbled text in 1 cell
98
+ - Removed 1 empty row
99
+ - Padded 1 short row with empty fields
100
+ - Dropped 1 empty column: "Notes"
101
+ - Emptied 1 placeholder value (N/A, #REF!, ...)
102
+ - Prefixed 1 formula-like cell with an apostrophe
103
+ - Renamed duplicate columns: "Email" -> "Email_2"
104
+ - Removed 1 duplicate row
105
+
106
+ Still needs attention (4):
107
+ (row numbers below refer to the fixed file)
108
+ ERROR EXTRA_DATA ...
109
+ WARNING EXCEL_SCI_NOTATION ...
110
+ WARNING MIXED_TYPES ...
111
+ WARNING INCONSISTENT_DATES ...
112
+ ```
113
+
114
+ The four leftovers are things no tool can safely decide for you: a row with a stray
115
+ delimiter, IDs that Excel already rounded, a `12o` typed instead of `120`, and dates
116
+ that could be day-first *or* month-first. `csvdoctor` shows you where they are and
117
+ leaves the data alone.
118
+
119
+ ## What it checks
120
+
121
+ | Check | Severity | Resolved by |
122
+ | --- | --- | --- |
123
+ | `EMPTY_FILE` no data at all | error | - |
124
+ | `RAGGED_ROWS` rows with too few fields, or extra *empty* fields | error | `fix` |
125
+ | `EXTRA_DATA` real data past the last column (usually an unquoted delimiter) | error | manual |
126
+ | `DUPLICATE_HEADERS` same column name twice (most tools drop one silently) | error | `fix` renames `x_2` |
127
+ | `ENCODING` not UTF-8 (Windows-1252, Latin-1, UTF-16/32) | warning | `fix` |
128
+ | `LINE_ENDINGS` mixed CRLF / LF / CR (ignores newlines inside quoted cells) | warning | `fix` |
129
+ | `NUL_BYTES` NUL characters in the file | warning | `fix` |
130
+ | `EMPTY_ROWS` blank lines and rows of empty cells | warning | `fix` |
131
+ | `EMPTY_HEADERS` columns without a name | warning | `fix` names `column_N` |
132
+ | `EMPTY_COLUMNS` columns with no data in any row | warning | `fix` |
133
+ | `WHITESPACE` leading/trailing spaces, including non-breaking spaces | warning | `fix` |
134
+ | `INVISIBLE_CHARS` zero-width spaces, word joiners, stray BOMs | warning | `fix` |
135
+ | `MOJIBAKE` text like `Café` from a past UTF-8 / Windows-1252 mix-up | warning | `fix` |
136
+ | `DUPLICATE_ROWS` exact repeated rows | warning | `--dedupe` |
137
+ | `EXCEL_ERRORS` `#REF!`, `#DIV/0!`, `#N/A`... exported as data | warning | `--normalize-nulls` |
138
+ | `FORMULA_INJECTION` cells starting with `=`, `@`, `+cmd`... that spreadsheets may execute | warning | `--neutralize-formulas` |
139
+ | `EXCEL_SCI_NOTATION` IDs/phone numbers Excel rounded to `1.23457E+11` | warning | manual (the digits are gone) |
140
+ | `MIXED_TYPES` a mostly numeric/date column containing text like `12o` | warning | manual |
141
+ | `INCONSISTENT_DATES` several date formats in one column | warning | manual (day/month order is ambiguous) |
142
+ | `BOM` UTF-8 byte-order mark | info | `fix` (`--bom` to keep one for Excel) |
143
+ | `DELIMITER` not a comma | info | `fix` |
144
+ | `NULL_TOKENS` `N/A`, `null`, `none`, `-` standing in for "missing" | info | `--normalize-nulls` |
145
+
146
+ "`fix`" means the default `csvdoctor fix` handles it. `--flags` are opt-in because they
147
+ change or remove data: for example `NA` is a *real value* in some data sets (Namibia's
148
+ country code), so blanking placeholders is never done unless you ask.
149
+
150
+ ## Command reference
151
+
152
+ ```text
153
+ csvdoctor check FILE [FILE ...] report problems, change nothing
154
+ csvdoctor fix FILE write a cleaned copy
155
+ ```
156
+
157
+ Use `-` as the file name to read from standard input.
158
+
159
+ **`check`**
160
+
161
+ | Option | Meaning |
162
+ | --- | --- |
163
+ | `--format text\|json` | human report (default) or machine-readable JSON |
164
+ | `--fail-on error\|warning\|info\|never` | exit status 1 if an issue at least this severe exists (default `error`) |
165
+ | `--encoding NAME` | skip detection and read the file as this encoding |
166
+ | `--delimiter CHAR` | skip detection (`,` `;` `tab` `pipe` or any single character) |
167
+
168
+ **`fix`**
169
+
170
+ | Option | Meaning |
171
+ | --- | --- |
172
+ | `-o, --output PATH` | where to write (`-` for stdout). Default: `<name>.fixed.csv` next to the input |
173
+ | `--in-place` | overwrite the input; a `.bak` copy of the original is kept |
174
+ | `--dedupe` | remove exact duplicate rows |
175
+ | `--normalize-nulls` | turn `N/A`, `null`, `none`, `-` and spreadsheet errors into empty cells |
176
+ | `--neutralize-formulas` | prefix formula-looking cells with `'` to prevent CSV injection |
177
+ | `--out-delimiter CHAR` | delimiter to write (default `,`) |
178
+ | `--bom` | write a UTF-8 BOM so **Excel** reads accented characters correctly |
179
+ | `--crlf` | write Windows line endings |
180
+ | `--encoding`, `--delimiter` | as for `check` |
181
+
182
+ Progress messages go to **stderr**, so `csvdoctor fix data.csv -o - | other-tool` is safe.
183
+
184
+ ### Exit codes
185
+
186
+ | Code | Meaning |
187
+ | --- | --- |
188
+ | `0` | no issues at or above `--fail-on` (`check`), or fixed with no errors remaining (`fix`) |
189
+ | `1` | issues found (`check`), or errors remain after fixing (`fix`) |
190
+ | `2` | usage error or unreadable file |
191
+
192
+ ## Use it in CI and pre-commit
193
+
194
+ Fail a pull request when a committed CSV is broken:
195
+
196
+ ```yaml
197
+ # .github/workflows/csv.yml
198
+ - run: pip install git+https://github.com/jasonkeanuchristy1-cyber/csvdoctor.git
199
+ - run: csvdoctor check data/*.csv --fail-on error
200
+ ```
201
+
202
+ As a [pre-commit](https://pre-commit.com) hook:
203
+
204
+ ```yaml
205
+ repos:
206
+ - repo: https://github.com/jasonkeanuchristy1-cyber/csvdoctor
207
+ rev: v0.1.0
208
+ hooks:
209
+ - id: csvdoctor
210
+ ```
211
+
212
+ Machine-readable output for scripts and dashboards:
213
+
214
+ ```bash
215
+ csvdoctor check export.csv --format json | jq '.files[0].summary'
216
+ ```
217
+
218
+ ## Use it as a library
219
+
220
+ ```python
221
+ from csvdoctor import read_table, diagnose, repair
222
+
223
+ raw = open("export.csv", "rb").read()
224
+
225
+ report = diagnose(read_table(raw))
226
+ for issue in report.issues:
227
+ print(issue.severity, issue.code, issue.message)
228
+
229
+ fixed = repair(raw, dedupe=True) # -> Repaired(data=bytes, actions=[...], ...)
230
+ open("clean.csv", "wb").write(fixed.data)
231
+ print("\n".join(fixed.actions))
232
+ ```
233
+
234
+ | Function | Purpose |
235
+ | --- | --- |
236
+ | `read_table(raw, encoding=None, delimiter=None)` | detect encoding/dialect and parse bytes into a `Table` |
237
+ | `diagnose(table)` | return a `Report` with a list of `Issue` objects (`code`, `severity`, `message`, `rows`, `fix`) |
238
+ | `fix_table(table, ...)` | return cleaned rows plus a log of what changed |
239
+ | `render_csv(rows, delimiter=",", bom=False, crlf=False)` | serialise rows to UTF-8 CSV bytes |
240
+ | `repair(raw, ...)` | all of the above in one call |
241
+
242
+ ## Design principles
243
+
244
+ 1. **Never lose data silently.** Dropping rows and blanking values are opt-in. Rows
245
+ that cannot be repaired safely are left untouched and reported.
246
+ 2. **Don't guess when it matters.** `03/02/2024` could be 3 February or 2 March, so
247
+ csvdoctor reports mixed date formats instead of "fixing" them.
248
+ 3. **Verify, don't claim.** After `fix`, the result is re-parsed and re-checked, and
249
+ what remains is shown to you.
250
+ 4. **Idempotent.** Running `fix` on its own output changes nothing. This is covered by
251
+ randomised tests.
252
+
253
+ ## Limitations
254
+
255
+ - The whole file is loaded into memory. Files up to a few hundred MB are fine.
256
+ - Speed is roughly 50,000 rows per second for `check` on a typical 8-column file
257
+ (pure Python, no dependencies).
258
+ - Encoding and delimiter detection are heuristics. If it guesses wrong, pass
259
+ `--encoding` / `--delimiter`. Windows-1252 and Latin-1 cannot be told apart with
260
+ certainty, and csvdoctor assumes Windows-1252 whenever the bytes allow it.
261
+ - It does not validate data against a schema (for that, see
262
+ [Frictionless](https://frictionlessdata.io/)) and does not infer or convert types.
263
+
264
+ ## Contributing
265
+
266
+ Bug reports with a small sample file are the most valuable contribution: real-world
267
+ messy CSVs are exactly what makes this tool better. See [CONTRIBUTING.md](CONTRIBUTING.md)
268
+ for setup and how to add a new check.
269
+
270
+ ## License
271
+
272
+ [MIT](LICENSE)
@@ -0,0 +1,240 @@
1
+ # csvdoctor
2
+
3
+ [![CI](https://github.com/jasonkeanuchristy1-cyber/csvdoctor/actions/workflows/ci.yml/badge.svg)](https://github.com/jasonkeanuchristy1-cyber/csvdoctor/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)
6
+ ![Dependencies: none](https://img.shields.io/badge/dependencies-none-brightgreen.svg)
7
+
8
+ **Find out what is wrong with a CSV file, and fix it, in one command.**
9
+
10
+ CSV has no standard. Every export is a small gamble: Excel writes `;`-separated
11
+ Windows-1252 in many countries, databases dump UTF-16, web tools add invisible
12
+ byte-order marks, and somebody always sorts the spreadsheet and leaves an empty
13
+ column behind. `csvdoctor` tells you *exactly* what is wrong and writes a clean,
14
+ UTF-8, comma-separated copy, without touching your original.
15
+
16
+ - **Zero dependencies.** Pure Python standard library. Works anywhere Python 3.9+ does.
17
+ - **Honest.** It re-checks its own output and tells you what it could *not* fix.
18
+ - **Safe by default.** It never silently deletes rows or blanks values. Anything
19
+ destructive is opt-in.
20
+ - **Usable everywhere.** CLI, Python library, JSON output for scripts, and a
21
+ [pre-commit](https://pre-commit.com) hook for CI.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ pip install git+https://github.com/jasonkeanuchristy1-cyber/csvdoctor.git
27
+ # or, to keep it out of your project environments:
28
+ pipx install git+https://github.com/jasonkeanuchristy1-cyber/csvdoctor.git
29
+ ```
30
+
31
+ ## Quick start
32
+
33
+ ```console
34
+ $ csvdoctor check examples/messy.csv
35
+ examples/messy.csv
36
+ Encoding ........ Windows-1252
37
+ Delimiter ....... semicolon
38
+ Line endings .... CRLF
39
+ Size ............ 9 data rows x 8 columns
40
+
41
+ Found 15 issues (3 errors, 10 warnings, 2 info)
42
+
43
+ ERROR RAGGED_ROWS Wrong number of fields in 1 row (the header has 8): 1 too short.
44
+ rows: 7
45
+ -> fixed by `csvdoctor fix`
46
+
47
+ ERROR EXTRA_DATA Real data beyond the last header column in 1 row. This usually means an
48
+ unquoted delimiter inside a value (e.g. a comma in 'Smith, John').
49
+ rows: 8
50
+ -> needs manual review
51
+ ...
52
+ ```
53
+
54
+ (The full report is abridged here; [`examples/messy.csv`](examples/messy.csv) is a
55
+ real, deliberately broken file you can try it on.)
56
+
57
+ Now fix it:
58
+
59
+ ```console
60
+ $ csvdoctor fix examples/messy.csv --dedupe --normalize-nulls --neutralize-formulas
61
+ Fixed examples/messy.csv -> examples/messy.fixed.csv
62
+ - Re-encoded from Windows-1252 to UTF-8
63
+ - Changed delimiter from semicolon to comma
64
+ - Trimmed whitespace in 2 cells
65
+ - Repaired garbled text in 1 cell
66
+ - Removed 1 empty row
67
+ - Padded 1 short row with empty fields
68
+ - Dropped 1 empty column: "Notes"
69
+ - Emptied 1 placeholder value (N/A, #REF!, ...)
70
+ - Prefixed 1 formula-like cell with an apostrophe
71
+ - Renamed duplicate columns: "Email" -> "Email_2"
72
+ - Removed 1 duplicate row
73
+
74
+ Still needs attention (4):
75
+ (row numbers below refer to the fixed file)
76
+ ERROR EXTRA_DATA ...
77
+ WARNING EXCEL_SCI_NOTATION ...
78
+ WARNING MIXED_TYPES ...
79
+ WARNING INCONSISTENT_DATES ...
80
+ ```
81
+
82
+ The four leftovers are things no tool can safely decide for you: a row with a stray
83
+ delimiter, IDs that Excel already rounded, a `12o` typed instead of `120`, and dates
84
+ that could be day-first *or* month-first. `csvdoctor` shows you where they are and
85
+ leaves the data alone.
86
+
87
+ ## What it checks
88
+
89
+ | Check | Severity | Resolved by |
90
+ | --- | --- | --- |
91
+ | `EMPTY_FILE` no data at all | error | - |
92
+ | `RAGGED_ROWS` rows with too few fields, or extra *empty* fields | error | `fix` |
93
+ | `EXTRA_DATA` real data past the last column (usually an unquoted delimiter) | error | manual |
94
+ | `DUPLICATE_HEADERS` same column name twice (most tools drop one silently) | error | `fix` renames `x_2` |
95
+ | `ENCODING` not UTF-8 (Windows-1252, Latin-1, UTF-16/32) | warning | `fix` |
96
+ | `LINE_ENDINGS` mixed CRLF / LF / CR (ignores newlines inside quoted cells) | warning | `fix` |
97
+ | `NUL_BYTES` NUL characters in the file | warning | `fix` |
98
+ | `EMPTY_ROWS` blank lines and rows of empty cells | warning | `fix` |
99
+ | `EMPTY_HEADERS` columns without a name | warning | `fix` names `column_N` |
100
+ | `EMPTY_COLUMNS` columns with no data in any row | warning | `fix` |
101
+ | `WHITESPACE` leading/trailing spaces, including non-breaking spaces | warning | `fix` |
102
+ | `INVISIBLE_CHARS` zero-width spaces, word joiners, stray BOMs | warning | `fix` |
103
+ | `MOJIBAKE` text like `Café` from a past UTF-8 / Windows-1252 mix-up | warning | `fix` |
104
+ | `DUPLICATE_ROWS` exact repeated rows | warning | `--dedupe` |
105
+ | `EXCEL_ERRORS` `#REF!`, `#DIV/0!`, `#N/A`... exported as data | warning | `--normalize-nulls` |
106
+ | `FORMULA_INJECTION` cells starting with `=`, `@`, `+cmd`... that spreadsheets may execute | warning | `--neutralize-formulas` |
107
+ | `EXCEL_SCI_NOTATION` IDs/phone numbers Excel rounded to `1.23457E+11` | warning | manual (the digits are gone) |
108
+ | `MIXED_TYPES` a mostly numeric/date column containing text like `12o` | warning | manual |
109
+ | `INCONSISTENT_DATES` several date formats in one column | warning | manual (day/month order is ambiguous) |
110
+ | `BOM` UTF-8 byte-order mark | info | `fix` (`--bom` to keep one for Excel) |
111
+ | `DELIMITER` not a comma | info | `fix` |
112
+ | `NULL_TOKENS` `N/A`, `null`, `none`, `-` standing in for "missing" | info | `--normalize-nulls` |
113
+
114
+ "`fix`" means the default `csvdoctor fix` handles it. `--flags` are opt-in because they
115
+ change or remove data: for example `NA` is a *real value* in some data sets (Namibia's
116
+ country code), so blanking placeholders is never done unless you ask.
117
+
118
+ ## Command reference
119
+
120
+ ```text
121
+ csvdoctor check FILE [FILE ...] report problems, change nothing
122
+ csvdoctor fix FILE write a cleaned copy
123
+ ```
124
+
125
+ Use `-` as the file name to read from standard input.
126
+
127
+ **`check`**
128
+
129
+ | Option | Meaning |
130
+ | --- | --- |
131
+ | `--format text\|json` | human report (default) or machine-readable JSON |
132
+ | `--fail-on error\|warning\|info\|never` | exit status 1 if an issue at least this severe exists (default `error`) |
133
+ | `--encoding NAME` | skip detection and read the file as this encoding |
134
+ | `--delimiter CHAR` | skip detection (`,` `;` `tab` `pipe` or any single character) |
135
+
136
+ **`fix`**
137
+
138
+ | Option | Meaning |
139
+ | --- | --- |
140
+ | `-o, --output PATH` | where to write (`-` for stdout). Default: `<name>.fixed.csv` next to the input |
141
+ | `--in-place` | overwrite the input; a `.bak` copy of the original is kept |
142
+ | `--dedupe` | remove exact duplicate rows |
143
+ | `--normalize-nulls` | turn `N/A`, `null`, `none`, `-` and spreadsheet errors into empty cells |
144
+ | `--neutralize-formulas` | prefix formula-looking cells with `'` to prevent CSV injection |
145
+ | `--out-delimiter CHAR` | delimiter to write (default `,`) |
146
+ | `--bom` | write a UTF-8 BOM so **Excel** reads accented characters correctly |
147
+ | `--crlf` | write Windows line endings |
148
+ | `--encoding`, `--delimiter` | as for `check` |
149
+
150
+ Progress messages go to **stderr**, so `csvdoctor fix data.csv -o - | other-tool` is safe.
151
+
152
+ ### Exit codes
153
+
154
+ | Code | Meaning |
155
+ | --- | --- |
156
+ | `0` | no issues at or above `--fail-on` (`check`), or fixed with no errors remaining (`fix`) |
157
+ | `1` | issues found (`check`), or errors remain after fixing (`fix`) |
158
+ | `2` | usage error or unreadable file |
159
+
160
+ ## Use it in CI and pre-commit
161
+
162
+ Fail a pull request when a committed CSV is broken:
163
+
164
+ ```yaml
165
+ # .github/workflows/csv.yml
166
+ - run: pip install git+https://github.com/jasonkeanuchristy1-cyber/csvdoctor.git
167
+ - run: csvdoctor check data/*.csv --fail-on error
168
+ ```
169
+
170
+ As a [pre-commit](https://pre-commit.com) hook:
171
+
172
+ ```yaml
173
+ repos:
174
+ - repo: https://github.com/jasonkeanuchristy1-cyber/csvdoctor
175
+ rev: v0.1.0
176
+ hooks:
177
+ - id: csvdoctor
178
+ ```
179
+
180
+ Machine-readable output for scripts and dashboards:
181
+
182
+ ```bash
183
+ csvdoctor check export.csv --format json | jq '.files[0].summary'
184
+ ```
185
+
186
+ ## Use it as a library
187
+
188
+ ```python
189
+ from csvdoctor import read_table, diagnose, repair
190
+
191
+ raw = open("export.csv", "rb").read()
192
+
193
+ report = diagnose(read_table(raw))
194
+ for issue in report.issues:
195
+ print(issue.severity, issue.code, issue.message)
196
+
197
+ fixed = repair(raw, dedupe=True) # -> Repaired(data=bytes, actions=[...], ...)
198
+ open("clean.csv", "wb").write(fixed.data)
199
+ print("\n".join(fixed.actions))
200
+ ```
201
+
202
+ | Function | Purpose |
203
+ | --- | --- |
204
+ | `read_table(raw, encoding=None, delimiter=None)` | detect encoding/dialect and parse bytes into a `Table` |
205
+ | `diagnose(table)` | return a `Report` with a list of `Issue` objects (`code`, `severity`, `message`, `rows`, `fix`) |
206
+ | `fix_table(table, ...)` | return cleaned rows plus a log of what changed |
207
+ | `render_csv(rows, delimiter=",", bom=False, crlf=False)` | serialise rows to UTF-8 CSV bytes |
208
+ | `repair(raw, ...)` | all of the above in one call |
209
+
210
+ ## Design principles
211
+
212
+ 1. **Never lose data silently.** Dropping rows and blanking values are opt-in. Rows
213
+ that cannot be repaired safely are left untouched and reported.
214
+ 2. **Don't guess when it matters.** `03/02/2024` could be 3 February or 2 March, so
215
+ csvdoctor reports mixed date formats instead of "fixing" them.
216
+ 3. **Verify, don't claim.** After `fix`, the result is re-parsed and re-checked, and
217
+ what remains is shown to you.
218
+ 4. **Idempotent.** Running `fix` on its own output changes nothing. This is covered by
219
+ randomised tests.
220
+
221
+ ## Limitations
222
+
223
+ - The whole file is loaded into memory. Files up to a few hundred MB are fine.
224
+ - Speed is roughly 50,000 rows per second for `check` on a typical 8-column file
225
+ (pure Python, no dependencies).
226
+ - Encoding and delimiter detection are heuristics. If it guesses wrong, pass
227
+ `--encoding` / `--delimiter`. Windows-1252 and Latin-1 cannot be told apart with
228
+ certainty, and csvdoctor assumes Windows-1252 whenever the bytes allow it.
229
+ - It does not validate data against a schema (for that, see
230
+ [Frictionless](https://frictionlessdata.io/)) and does not infer or convert types.
231
+
232
+ ## Contributing
233
+
234
+ Bug reports with a small sample file are the most valuable contribution: real-world
235
+ messy CSVs are exactly what makes this tool better. See [CONTRIBUTING.md](CONTRIBUTING.md)
236
+ for setup and how to add a new check.
237
+
238
+ ## License
239
+
240
+ [MIT](LICENSE)
@@ -0,0 +1,59 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "csvdoctor"
7
+ dynamic = ["version"]
8
+ description = "Diagnose and repair messy CSV files: encodings, delimiters, ragged rows, Excel damage and more. Zero dependencies."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "csvdoctor contributors" }]
13
+ keywords = ["csv", "data-cleaning", "data-quality", "encoding", "excel", "etl", "cli", "linter"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: Science/Research",
19
+ "Intended Audience :: Information Technology",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Topic :: Utilities",
25
+ "Topic :: Text Processing :: General",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = []
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=7", "ruff>=0.5", "build", "twine"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/jasonkeanuchristy1-cyber/csvdoctor"
35
+ Issues = "https://github.com/jasonkeanuchristy1-cyber/csvdoctor/issues"
36
+ Changelog = "https://github.com/jasonkeanuchristy1-cyber/csvdoctor/blob/main/CHANGELOG.md"
37
+
38
+ [project.scripts]
39
+ csvdoctor = "csvdoctor.cli:main"
40
+
41
+ [tool.setuptools.dynamic]
42
+ version = { attr = "csvdoctor.__version__" }
43
+
44
+ [tool.setuptools.packages.find]
45
+ where = ["src"]
46
+
47
+ [tool.setuptools.package-data]
48
+ csvdoctor = ["py.typed"]
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
52
+
53
+ [tool.ruff]
54
+ line-length = 100
55
+ target-version = "py39"
56
+ src = ["src", "tests"]
57
+
58
+ [tool.ruff.lint]
59
+ select = ["E", "F", "W", "B"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,22 @@
1
+ """csvdoctor: diagnose and repair messy CSV files."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .diagnose import Issue, Report, diagnose # noqa: E402
6
+ from .fix import FixResult, Repaired, fix_table, render_csv, repair # noqa: E402
7
+ from .reader import CsvDoctorError, Table, read_table # noqa: E402
8
+
9
+ __all__ = [
10
+ "CsvDoctorError",
11
+ "FixResult",
12
+ "Issue",
13
+ "Repaired",
14
+ "Report",
15
+ "Table",
16
+ "__version__",
17
+ "diagnose",
18
+ "fix_table",
19
+ "read_table",
20
+ "render_csv",
21
+ "repair",
22
+ ]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())