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.
- csvdoctor-0.1.0/LICENSE +21 -0
- csvdoctor-0.1.0/PKG-INFO +272 -0
- csvdoctor-0.1.0/README.md +240 -0
- csvdoctor-0.1.0/pyproject.toml +59 -0
- csvdoctor-0.1.0/setup.cfg +4 -0
- csvdoctor-0.1.0/src/csvdoctor/__init__.py +22 -0
- csvdoctor-0.1.0/src/csvdoctor/__main__.py +4 -0
- csvdoctor-0.1.0/src/csvdoctor/cli.py +224 -0
- csvdoctor-0.1.0/src/csvdoctor/diagnose.py +592 -0
- csvdoctor-0.1.0/src/csvdoctor/fix.py +250 -0
- csvdoctor-0.1.0/src/csvdoctor/py.typed +0 -0
- csvdoctor-0.1.0/src/csvdoctor/reader.py +174 -0
- csvdoctor-0.1.0/src/csvdoctor/report.py +75 -0
- csvdoctor-0.1.0/src/csvdoctor/textutil.py +186 -0
- csvdoctor-0.1.0/src/csvdoctor.egg-info/PKG-INFO +272 -0
- csvdoctor-0.1.0/src/csvdoctor.egg-info/SOURCES.txt +24 -0
- csvdoctor-0.1.0/src/csvdoctor.egg-info/dependency_links.txt +1 -0
- csvdoctor-0.1.0/src/csvdoctor.egg-info/entry_points.txt +2 -0
- csvdoctor-0.1.0/src/csvdoctor.egg-info/requires.txt +6 -0
- csvdoctor-0.1.0/src/csvdoctor.egg-info/top_level.txt +1 -0
- csvdoctor-0.1.0/tests/test_cli.py +206 -0
- csvdoctor-0.1.0/tests/test_diagnose.py +183 -0
- csvdoctor-0.1.0/tests/test_docs.py +28 -0
- csvdoctor-0.1.0/tests/test_fix.py +161 -0
- csvdoctor-0.1.0/tests/test_properties.py +105 -0
- csvdoctor-0.1.0/tests/test_reader.py +118 -0
csvdoctor-0.1.0/LICENSE
ADDED
|
@@ -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.
|
csvdoctor-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/jasonkeanuchristy1-cyber/csvdoctor/actions/workflows/ci.yml)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+

|
|
38
|
+

|
|
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
|
+
[](https://github.com/jasonkeanuchristy1-cyber/csvdoctor/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+

|
|
6
|
+

|
|
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,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
|
+
]
|