csvsmith 0.11.1__tar.gz → 0.13.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.
- {csvsmith-0.11.1/src/csvsmith.egg-info → csvsmith-0.13.0}/PKG-INFO +2 -24
- {csvsmith-0.11.1 → csvsmith-0.13.0}/pyproject.toml +4 -4
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/__init__.py +10 -1
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/cli.py +33 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/csv_viewer.py +23 -0
- csvsmith-0.13.0/src/csvsmith/tools/strip_bom.py +52 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0/src/csvsmith.egg-info}/PKG-INFO +2 -24
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith.egg-info/SOURCES.txt +3 -1
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_cli.py +28 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_csv_viewer.py +52 -0
- csvsmith-0.13.0/tests/test_strip_bom.py +63 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/LICENSE +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/README.rst +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/setup.cfg +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/__init__.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/classify.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/dense_csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/excel2csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/filter_rows.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/find_matches_in_csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/knapsack_csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/move_files.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/row_dedup.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/sample_csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/tools/strict_concat.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/utils/__init__.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/utils/clean_numeric.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/utils/distance.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/utils/io.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith/utils/normalize.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith.egg-info/dependency_links.txt +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith.egg-info/entry_points.txt +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith.egg-info/requires.txt +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/src/csvsmith.egg-info/top_level.txt +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_classify.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_clean_numeric.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_dense_csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_excel2csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_filter_rows.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_find_matches_in_csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_knapsack_csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_move_files.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_normalize.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_row_dedup.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_sample_csv.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_strict_concat.py +0 -0
- {csvsmith-0.11.1 → csvsmith-0.13.0}/tests/test_string_distance.py +0 -0
|
@@ -1,37 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csvsmith
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Small CSV utilities: row deduplication, classification, row filtering, and CLI helpers.
|
|
5
5
|
Author-email: Eiichi YAMAMOTO <info@yeiichi.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2026 Eiichi YAMAMOTO
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in
|
|
18
|
-
all copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
25
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
26
|
-
IN THE SOFTWARE.
|
|
27
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Project-URL: Homepage, https://github.com/yeiichi/csvsmith
|
|
29
8
|
Project-URL: Repository, https://github.com/yeiichi/csvsmith
|
|
30
9
|
Project-URL: Documentation, https://csvsmith.readthedocs.io/en/latest/
|
|
31
10
|
Keywords: csv,deduplication,data-filtering,file-organization,filtering
|
|
32
11
|
Classifier: Programming Language :: Python :: 3
|
|
33
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
34
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
35
13
|
Classifier: Intended Audience :: Developers
|
|
36
14
|
Classifier: Topic :: Software Development :: Libraries
|
|
37
15
|
Classifier: Topic :: Utilities
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=77.0", "wheel"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "csvsmith"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.13.0"
|
|
8
8
|
description = "Small CSV utilities: row deduplication, classification, row filtering, and CLI helpers."
|
|
9
9
|
readme = "README.rst"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
|
-
license =
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
12
13
|
|
|
13
14
|
authors = [
|
|
14
15
|
{ name = "Eiichi YAMAMOTO", email = "info@yeiichi.com" }
|
|
@@ -25,7 +26,6 @@ keywords = [
|
|
|
25
26
|
classifiers = [
|
|
26
27
|
"Programming Language :: Python :: 3",
|
|
27
28
|
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
-
"License :: OSI Approved :: MIT License",
|
|
29
29
|
"Intended Audience :: Developers",
|
|
30
30
|
"Topic :: Software Development :: Libraries",
|
|
31
31
|
"Topic :: Utilities",
|
|
@@ -21,6 +21,7 @@ Public API:
|
|
|
21
21
|
- concentrate_csv
|
|
22
22
|
- rehydrate_csv
|
|
23
23
|
- create_sample_csv
|
|
24
|
+
- strip_utf8_bom
|
|
24
25
|
- DataFrame
|
|
25
26
|
- build_filter
|
|
26
27
|
- infer_type
|
|
@@ -42,7 +43,12 @@ Submodules:
|
|
|
42
43
|
- csvsmith.cli (CLI entrypoint)
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
__version__ = version("csvsmith")
|
|
50
|
+
except PackageNotFoundError:
|
|
51
|
+
__version__ = "0.0.0"
|
|
46
52
|
|
|
47
53
|
from .tools.classify import CSVClassifier
|
|
48
54
|
from .tools.csv_viewer import DataFrame, build_filter, infer_type
|
|
@@ -67,6 +73,7 @@ from .tools.row_dedup import (
|
|
|
67
73
|
dedupe_with_report,
|
|
68
74
|
)
|
|
69
75
|
from .tools.sample_csv import SampleCSVResult, create_sample_csv
|
|
76
|
+
from .tools.strip_bom import StripBomResult, strip_utf8_bom
|
|
70
77
|
from .tools.strict_concat import save_csv, strict_concat_rows
|
|
71
78
|
from .utils.clean_numeric import clean_numeric
|
|
72
79
|
from .utils.distance import StringDistance, Relation, Result, analyze_pair
|
|
@@ -101,6 +108,8 @@ __all__ = [
|
|
|
101
108
|
"rehydrate_csv",
|
|
102
109
|
"SampleCSVResult",
|
|
103
110
|
"create_sample_csv",
|
|
111
|
+
"StripBomResult",
|
|
112
|
+
"strip_utf8_bom",
|
|
104
113
|
"DataFrame",
|
|
105
114
|
"build_filter",
|
|
106
115
|
"infer_type",
|
|
@@ -24,6 +24,7 @@ from .tools.sample_csv import (
|
|
|
24
24
|
DEFAULT_ROW_COUNT,
|
|
25
25
|
create_sample_csv,
|
|
26
26
|
)
|
|
27
|
+
from .tools.strip_bom import strip_utf8_bom
|
|
27
28
|
from .tools.strict_concat import save_csv, strict_concat_rows
|
|
28
29
|
from .tools.find_matches_in_csv import find_matches_in_csv
|
|
29
30
|
from .tools.knapsack_csv import mark_knapsack_csv
|
|
@@ -204,6 +205,18 @@ def cmd_find_matches(args: argparse.Namespace) -> int:
|
|
|
204
205
|
return 0
|
|
205
206
|
|
|
206
207
|
|
|
208
|
+
def cmd_strip_bom(args: argparse.Namespace) -> int:
|
|
209
|
+
try:
|
|
210
|
+
result = strip_utf8_bom(args.input, args.output, in_place=args.in_place)
|
|
211
|
+
except (OSError, ValueError) as e:
|
|
212
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
213
|
+
return 1
|
|
214
|
+
|
|
215
|
+
status = "removed BOM" if result.removed else "no BOM found"
|
|
216
|
+
print(f"Wrote CSV to: {result.output_path} ({status})")
|
|
217
|
+
return 0
|
|
218
|
+
|
|
219
|
+
|
|
207
220
|
def cmd_strict_concat(args: argparse.Namespace) -> int:
|
|
208
221
|
input_dir = Path(args.input_dir)
|
|
209
222
|
if not input_dir.is_dir():
|
|
@@ -388,6 +401,25 @@ def _add_find_matches_parser(subparsers) -> None:
|
|
|
388
401
|
parser.set_defaults(func=cmd_find_matches)
|
|
389
402
|
|
|
390
403
|
|
|
404
|
+
def _add_strip_bom_parser(subparsers) -> None:
|
|
405
|
+
parser = subparsers.add_parser(
|
|
406
|
+
"strip-bom",
|
|
407
|
+
help="Remove a leading UTF-8 BOM from a CSV file.",
|
|
408
|
+
)
|
|
409
|
+
parser.add_argument("input", help="Input CSV file.")
|
|
410
|
+
parser.add_argument(
|
|
411
|
+
"-o",
|
|
412
|
+
"--output",
|
|
413
|
+
help="Output CSV file (default: <input-stem>.no-bom.csv).",
|
|
414
|
+
)
|
|
415
|
+
parser.add_argument(
|
|
416
|
+
"--in-place",
|
|
417
|
+
action="store_true",
|
|
418
|
+
help="Rewrite the input file instead of writing a separate output file.",
|
|
419
|
+
)
|
|
420
|
+
parser.set_defaults(func=cmd_strip_bom)
|
|
421
|
+
|
|
422
|
+
|
|
391
423
|
def _add_strict_concat_parser(subparsers) -> None:
|
|
392
424
|
parser = subparsers.add_parser(
|
|
393
425
|
"strict-concat",
|
|
@@ -664,6 +696,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
664
696
|
|
|
665
697
|
_add_row_duplicates_parser(subparsers)
|
|
666
698
|
_add_find_matches_parser(subparsers)
|
|
699
|
+
_add_strip_bom_parser(subparsers)
|
|
667
700
|
_add_strict_concat_parser(subparsers)
|
|
668
701
|
_add_concentrate_parser(subparsers)
|
|
669
702
|
_add_rehydrate_parser(subparsers)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import csv
|
|
4
|
+
import io
|
|
4
5
|
import operator
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any, Callable, TextIO
|
|
@@ -88,6 +89,28 @@ class DataFrame:
|
|
|
88
89
|
"""Return the first ``n`` rows formatted as a text table."""
|
|
89
90
|
return self.render(end=n)
|
|
90
91
|
|
|
92
|
+
def to_csv(
|
|
93
|
+
self,
|
|
94
|
+
filepath: str | Path | None = None,
|
|
95
|
+
*,
|
|
96
|
+
encoding: str = "utf-8",
|
|
97
|
+
) -> str | Path:
|
|
98
|
+
"""Return the ``DataFrame`` as CSV, or write it to ``filepath``."""
|
|
99
|
+
output = io.StringIO()
|
|
100
|
+
writer = csv.writer(output)
|
|
101
|
+
writer.writerow(self.columns)
|
|
102
|
+
for index in range(len(self)):
|
|
103
|
+
writer.writerow([self._data[column][index] for column in self.columns])
|
|
104
|
+
csv_text = output.getvalue()
|
|
105
|
+
|
|
106
|
+
if filepath is None:
|
|
107
|
+
return csv_text
|
|
108
|
+
|
|
109
|
+
path = Path(filepath)
|
|
110
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
path.write_text(csv_text, encoding=encoding, newline="")
|
|
112
|
+
return path
|
|
113
|
+
|
|
91
114
|
def select(self, columns: list[str]) -> DataFrame:
|
|
92
115
|
"""Return a new ``DataFrame`` with the selected columns."""
|
|
93
116
|
missing = [column for column in columns if column not in self._data]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
UTF8_BOM = b"\xef\xbb\xbf"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class StripBomResult:
|
|
12
|
+
input_path: Path
|
|
13
|
+
output_path: Path
|
|
14
|
+
removed: bool
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def default_output_path(csv_path: str | Path) -> Path:
|
|
18
|
+
"""Return the default output path for a BOM-stripped CSV."""
|
|
19
|
+
path = Path(csv_path)
|
|
20
|
+
return path.with_name(f"{path.stem}.no-bom{path.suffix}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def strip_utf8_bom(
|
|
24
|
+
csv_path: str | Path,
|
|
25
|
+
output_path: str | Path | None = None,
|
|
26
|
+
*,
|
|
27
|
+
in_place: bool = False,
|
|
28
|
+
) -> StripBomResult:
|
|
29
|
+
"""Remove a leading UTF-8 BOM from a CSV file while preserving all other bytes."""
|
|
30
|
+
input_path = Path(csv_path)
|
|
31
|
+
if in_place and output_path is not None:
|
|
32
|
+
raise ValueError("--in-place cannot be used with --output")
|
|
33
|
+
|
|
34
|
+
if in_place:
|
|
35
|
+
resolved_output_path = input_path
|
|
36
|
+
elif output_path:
|
|
37
|
+
resolved_output_path = Path(output_path)
|
|
38
|
+
else:
|
|
39
|
+
resolved_output_path = default_output_path(input_path)
|
|
40
|
+
|
|
41
|
+
data = input_path.read_bytes()
|
|
42
|
+
removed = data.startswith(UTF8_BOM)
|
|
43
|
+
output_data = data[len(UTF8_BOM) :] if removed else data
|
|
44
|
+
|
|
45
|
+
resolved_output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
resolved_output_path.write_bytes(output_data)
|
|
47
|
+
|
|
48
|
+
return StripBomResult(
|
|
49
|
+
input_path=input_path,
|
|
50
|
+
output_path=resolved_output_path,
|
|
51
|
+
removed=removed,
|
|
52
|
+
)
|
|
@@ -1,37 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csvsmith
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Small CSV utilities: row deduplication, classification, row filtering, and CLI helpers.
|
|
5
5
|
Author-email: Eiichi YAMAMOTO <info@yeiichi.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2026 Eiichi YAMAMOTO
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in
|
|
18
|
-
all copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
25
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
26
|
-
IN THE SOFTWARE.
|
|
27
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Project-URL: Homepage, https://github.com/yeiichi/csvsmith
|
|
29
8
|
Project-URL: Repository, https://github.com/yeiichi/csvsmith
|
|
30
9
|
Project-URL: Documentation, https://csvsmith.readthedocs.io/en/latest/
|
|
31
10
|
Keywords: csv,deduplication,data-filtering,file-organization,filtering
|
|
32
11
|
Classifier: Programming Language :: Python :: 3
|
|
33
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
34
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
35
13
|
Classifier: Intended Audience :: Developers
|
|
36
14
|
Classifier: Topic :: Software Development :: Libraries
|
|
37
15
|
Classifier: Topic :: Utilities
|
|
@@ -21,6 +21,7 @@ src/csvsmith/tools/move_files.py
|
|
|
21
21
|
src/csvsmith/tools/row_dedup.py
|
|
22
22
|
src/csvsmith/tools/sample_csv.py
|
|
23
23
|
src/csvsmith/tools/strict_concat.py
|
|
24
|
+
src/csvsmith/tools/strip_bom.py
|
|
24
25
|
src/csvsmith/utils/__init__.py
|
|
25
26
|
src/csvsmith/utils/clean_numeric.py
|
|
26
27
|
src/csvsmith/utils/distance.py
|
|
@@ -40,4 +41,5 @@ tests/test_normalize.py
|
|
|
40
41
|
tests/test_row_dedup.py
|
|
41
42
|
tests/test_sample_csv.py
|
|
42
43
|
tests/test_strict_concat.py
|
|
43
|
-
tests/test_string_distance.py
|
|
44
|
+
tests/test_string_distance.py
|
|
45
|
+
tests/test_strip_bom.py
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
4
|
+
|
|
5
|
+
import csvsmith
|
|
2
6
|
from csvsmith.cli import build_parser, main
|
|
3
7
|
|
|
4
8
|
|
|
9
|
+
def test_package_version_matches_metadata():
|
|
10
|
+
assert csvsmith.__version__ == version("csvsmith")
|
|
11
|
+
|
|
12
|
+
|
|
5
13
|
def test_main_help():
|
|
6
14
|
with pytest.raises(SystemExit) as excinfo:
|
|
7
15
|
main(["--help"])
|
|
@@ -190,6 +198,26 @@ def test_cli_parses_find_matches_command():
|
|
|
190
198
|
assert args.no_nfkc is True
|
|
191
199
|
|
|
192
200
|
|
|
201
|
+
def test_cli_parses_strip_bom_command():
|
|
202
|
+
parser = build_parser()
|
|
203
|
+
args = parser.parse_args(["strip-bom", "input.csv", "-o", "output.csv"])
|
|
204
|
+
|
|
205
|
+
assert args.command == "strip-bom"
|
|
206
|
+
assert args.input == "input.csv"
|
|
207
|
+
assert args.output == "output.csv"
|
|
208
|
+
assert args.in_place is False
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_cli_parses_strip_bom_command_in_place():
|
|
212
|
+
parser = build_parser()
|
|
213
|
+
args = parser.parse_args(["strip-bom", "input.csv", "--in-place"])
|
|
214
|
+
|
|
215
|
+
assert args.command == "strip-bom"
|
|
216
|
+
assert args.input == "input.csv"
|
|
217
|
+
assert args.output is None
|
|
218
|
+
assert args.in_place is True
|
|
219
|
+
|
|
220
|
+
|
|
193
221
|
def test_cli_parses_strict_concat_command():
|
|
194
222
|
parser = build_parser()
|
|
195
223
|
args = parser.parse_args(["strict-concat", "input_dir", "-o", "output.csv"])
|
|
@@ -70,6 +70,58 @@ def test_dataframe_filter_supports_numeric_comparisons(tmp_path):
|
|
|
70
70
|
assert df["name"] == ["Alice"]
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
def test_dataframe_to_csv_returns_csv_string(tmp_path):
|
|
74
|
+
source = tmp_path / "sample.csv"
|
|
75
|
+
write_csv(
|
|
76
|
+
source,
|
|
77
|
+
[
|
|
78
|
+
["name", "score", "note"],
|
|
79
|
+
["Alice", "10", "Hello, Tokyo"],
|
|
80
|
+
["Bob", "8", "Line\nbreak"],
|
|
81
|
+
],
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
df = DataFrame.from_csv(source).filter(build_filter("score", ">", "7"))
|
|
85
|
+
|
|
86
|
+
assert (
|
|
87
|
+
df.to_csv()
|
|
88
|
+
== 'name,score,note\r\nAlice,10,"Hello, Tokyo"\r\nBob,8,"Line\nbreak"\r\n'
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_dataframe_to_csv_writes_csv_file(tmp_path):
|
|
93
|
+
source = tmp_path / "sample.csv"
|
|
94
|
+
output = tmp_path / "exports" / "filtered.csv"
|
|
95
|
+
write_csv(
|
|
96
|
+
source,
|
|
97
|
+
[
|
|
98
|
+
["name", "score", "note"],
|
|
99
|
+
["Alice", "10", "Hello, Tokyo"],
|
|
100
|
+
["Bob", "8", "Line\nbreak"],
|
|
101
|
+
],
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
df = DataFrame.from_csv(source).filter(build_filter("score", ">", "8"))
|
|
105
|
+
|
|
106
|
+
result = df.to_csv(output)
|
|
107
|
+
|
|
108
|
+
assert result == output
|
|
109
|
+
assert output.read_text(encoding="utf-8") == "name,score,note\nAlice,10,\"Hello, Tokyo\"\n"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_dataframe_to_csv_round_trips_with_from_csv(tmp_path):
|
|
113
|
+
source = tmp_path / "sample.csv"
|
|
114
|
+
output = tmp_path / "output.csv"
|
|
115
|
+
write_csv(source, [["id", "name"], ["001", "Alice"], ["002", "Bob"]])
|
|
116
|
+
|
|
117
|
+
DataFrame.from_csv(source, convert_types=False).to_csv(output)
|
|
118
|
+
|
|
119
|
+
round_tripped = DataFrame.from_csv(output, convert_types=False)
|
|
120
|
+
assert round_tripped.columns == ["id", "name"]
|
|
121
|
+
assert round_tripped["id"] == ["001", "002"]
|
|
122
|
+
assert round_tripped["name"] == ["Alice", "Bob"]
|
|
123
|
+
|
|
124
|
+
|
|
73
125
|
def test_view_command_filters_and_selects_columns(tmp_path, capsys):
|
|
74
126
|
source = tmp_path / "sample.csv"
|
|
75
127
|
write_csv(
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from csvsmith.cli import main
|
|
4
|
+
from csvsmith.tools.strip_bom import UTF8_BOM, default_output_path, strip_utf8_bom
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_strip_utf8_bom_writes_default_output_without_bom(tmp_path):
|
|
8
|
+
source = tmp_path / "input.csv"
|
|
9
|
+
source.write_bytes(UTF8_BOM + b"id,name\r\n1,Alice\r\n")
|
|
10
|
+
|
|
11
|
+
result = strip_utf8_bom(source)
|
|
12
|
+
|
|
13
|
+
assert result.output_path == tmp_path / "input.no-bom.csv"
|
|
14
|
+
assert result.removed is True
|
|
15
|
+
assert result.output_path.read_bytes() == b"id,name\r\n1,Alice\r\n"
|
|
16
|
+
assert source.read_bytes().startswith(UTF8_BOM)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_strip_utf8_bom_preserves_file_without_bom(tmp_path):
|
|
20
|
+
source = tmp_path / "input.csv"
|
|
21
|
+
output = tmp_path / "output.csv"
|
|
22
|
+
source.write_bytes(b"id,name\n1,Alice\n")
|
|
23
|
+
|
|
24
|
+
result = strip_utf8_bom(source, output)
|
|
25
|
+
|
|
26
|
+
assert result.output_path == output
|
|
27
|
+
assert result.removed is False
|
|
28
|
+
assert output.read_bytes() == b"id,name\n1,Alice\n"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_strip_utf8_bom_can_rewrite_in_place(tmp_path):
|
|
32
|
+
source = tmp_path / "input.csv"
|
|
33
|
+
source.write_bytes(UTF8_BOM + b"id,name\n1,Alice\n")
|
|
34
|
+
|
|
35
|
+
result = strip_utf8_bom(source, in_place=True)
|
|
36
|
+
|
|
37
|
+
assert result.output_path == source
|
|
38
|
+
assert result.removed is True
|
|
39
|
+
assert source.read_bytes() == b"id,name\n1,Alice\n"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_strip_utf8_bom_rejects_in_place_with_output(tmp_path):
|
|
43
|
+
source = tmp_path / "input.csv"
|
|
44
|
+
source.write_bytes(UTF8_BOM + b"id,name\n")
|
|
45
|
+
|
|
46
|
+
with pytest.raises(ValueError, match="--in-place"):
|
|
47
|
+
strip_utf8_bom(source, tmp_path / "output.csv", in_place=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_default_output_path_handles_multi_dot_names():
|
|
51
|
+
assert default_output_path("report.final.csv").name == "report.final.no-bom.csv"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_strip_bom_cli_writes_requested_output(tmp_path, capsys):
|
|
55
|
+
source = tmp_path / "input.csv"
|
|
56
|
+
output = tmp_path / "clean.csv"
|
|
57
|
+
source.write_bytes(UTF8_BOM + b"id,name\n1,Alice\n")
|
|
58
|
+
|
|
59
|
+
exit_code = main(["strip-bom", str(source), "-o", str(output)])
|
|
60
|
+
|
|
61
|
+
assert exit_code == 0
|
|
62
|
+
assert output.read_bytes() == b"id,name\n1,Alice\n"
|
|
63
|
+
assert "removed BOM" in capsys.readouterr().out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|