csvsmith 0.4.0__tar.gz → 0.6.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.
Files changed (28) hide show
  1. {csvsmith-0.4.0/src/csvsmith.egg-info → csvsmith-0.6.0}/PKG-INFO +45 -6
  2. {csvsmith-0.4.0 → csvsmith-0.6.0}/README.rst +44 -5
  3. {csvsmith-0.4.0 → csvsmith-0.6.0}/pyproject.toml +1 -1
  4. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith/__init__.py +4 -1
  5. csvsmith-0.6.0/src/csvsmith/clean_numeric.py +124 -0
  6. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith/cli.py +32 -0
  7. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith/string_distance.py +7 -1
  8. {csvsmith-0.4.0 → csvsmith-0.6.0/src/csvsmith.egg-info}/PKG-INFO +45 -6
  9. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith.egg-info/SOURCES.txt +2 -0
  10. csvsmith-0.6.0/tests/test_clean_numeric.py +36 -0
  11. {csvsmith-0.4.0 → csvsmith-0.6.0}/tests/test_cli.py +31 -0
  12. {csvsmith-0.4.0 → csvsmith-0.6.0}/LICENSE +0 -0
  13. {csvsmith-0.4.0 → csvsmith-0.6.0}/setup.cfg +0 -0
  14. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith/classify.py +0 -0
  15. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith/excel2csv.py +0 -0
  16. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith/filter_rows.py +0 -0
  17. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith/move_files.py +0 -0
  18. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith/row_dedup.py +0 -0
  19. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith.egg-info/dependency_links.txt +0 -0
  20. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith.egg-info/entry_points.txt +0 -0
  21. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith.egg-info/requires.txt +0 -0
  22. {csvsmith-0.4.0 → csvsmith-0.6.0}/src/csvsmith.egg-info/top_level.txt +0 -0
  23. {csvsmith-0.4.0 → csvsmith-0.6.0}/tests/test_classify.py +0 -0
  24. {csvsmith-0.4.0 → csvsmith-0.6.0}/tests/test_excel2csv.py +0 -0
  25. {csvsmith-0.4.0 → csvsmith-0.6.0}/tests/test_filter_rows.py +0 -0
  26. {csvsmith-0.4.0 → csvsmith-0.6.0}/tests/test_move_files.py +0 -0
  27. {csvsmith-0.4.0 → csvsmith-0.6.0}/tests/test_row_dedup.py +0 -0
  28. {csvsmith-0.4.0 → csvsmith-0.6.0}/tests/test_string_distance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csvsmith
3
- Version: 0.4.0
3
+ Version: 0.6.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
6
  License: MIT License
@@ -50,7 +50,7 @@ csvsmith
50
50
  :target: https://pypi.org/project/csvsmith/
51
51
 
52
52
  .. image:: https://img.shields.io/pypi/l/csvsmith.svg
53
- :target: https://pypi.org/project/ccsvsmith/
53
+ :target: https://pypi.org/project/csvsmith/
54
54
 
55
55
  Introduction
56
56
  ------------
@@ -77,6 +77,7 @@ Features
77
77
  - rollback support via manifest
78
78
  - row filtering by substring
79
79
  - Excel worksheet to CSV conversion
80
+ - numeric string cleaning and normalization
80
81
  - file moving by suffix
81
82
  - string distance and similarity analysis
82
83
  - a single command-line entrypoint with subcommands
@@ -153,7 +154,26 @@ Analyze string distance
153
154
  print(result.jaro_winkler_score)
154
155
  print(result.similarity_percentage)
155
156
 
156
- CLI Usage
157
+ Clean numeric values
158
+ ~~~~~~~~~~~~~~~~~~~~
159
+
160
+ .. code-block:: python
161
+
162
+ from csvsmith import clean_numeric
163
+
164
+ # Handles separators and parentheses for negative numbers
165
+ val1 = clean_numeric("1,234.56")
166
+ val2 = clean_numeric("(100.00)")
167
+ val3 = clean_numeric("1 234,56", sep=" ", decimal=",")
168
+
169
+ # Use relaxed mode to return original value on failure instead of raising ValueError
170
+ val4 = clean_numeric("not a number", relaxed=True)
171
+
172
+ print(val1) # 1234.56
173
+ print(val2) # -100.0
174
+ print(val3) # 1234.56
175
+ print(val4) # "not a number"
176
+
157
177
  Drop rows in a CSV by column name
158
178
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159
179
 
@@ -203,7 +223,7 @@ Move files by suffix
203
223
  print(f"Moved {moved_count} files.")
204
224
 
205
225
  CSV File Classification (Python)
206
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
226
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
207
227
 
208
228
  .. code-block:: python
209
229
 
@@ -315,6 +335,25 @@ Move files by suffix
315
335
  This moves files whose suffix matches one of the given values. The suffixes can
316
336
  be written with or without a leading dot, and matching is case-insensitive.
317
337
 
338
+ Clean numeric values
339
+ ~~~~~~~~~~~~~~~~~~~~
340
+
341
+ .. code-block:: bash
342
+
343
+ csvsmith clean-numeric "1,234.56"
344
+
345
+ Using custom separators:
346
+
347
+ .. code-block:: bash
348
+
349
+ csvsmith clean-numeric "1 234,56" --sep " " --decimal ","
350
+
351
+ Relaxed mode (returns original string on failure):
352
+
353
+ .. code-block:: bash
354
+
355
+ csvsmith clean-numeric "not a number" --relaxed
356
+
318
357
  Drop CSV rows
319
358
  ~~~~~~~~~~~~~
320
359
 
@@ -379,7 +418,7 @@ Example
379
418
 
380
419
  Suppose you have a CSV like this:
381
420
 
382
- .. code-block:: csv
421
+ .. code-block:: text
383
422
 
384
423
  id,name,notes
385
424
  1,Alice,ok
@@ -394,7 +433,7 @@ Running:
394
433
 
395
434
  produces a filtered file containing:
396
435
 
397
- .. code-block:: csv
436
+ .. code-block:: text
398
437
 
399
438
  id,name,notes
400
439
  1,Alice,ok
@@ -8,7 +8,7 @@ csvsmith
8
8
  :target: https://pypi.org/project/csvsmith/
9
9
 
10
10
  .. image:: https://img.shields.io/pypi/l/csvsmith.svg
11
- :target: https://pypi.org/project/ccsvsmith/
11
+ :target: https://pypi.org/project/csvsmith/
12
12
 
13
13
  Introduction
14
14
  ------------
@@ -35,6 +35,7 @@ Features
35
35
  - rollback support via manifest
36
36
  - row filtering by substring
37
37
  - Excel worksheet to CSV conversion
38
+ - numeric string cleaning and normalization
38
39
  - file moving by suffix
39
40
  - string distance and similarity analysis
40
41
  - a single command-line entrypoint with subcommands
@@ -111,7 +112,26 @@ Analyze string distance
111
112
  print(result.jaro_winkler_score)
112
113
  print(result.similarity_percentage)
113
114
 
114
- CLI Usage
115
+ Clean numeric values
116
+ ~~~~~~~~~~~~~~~~~~~~
117
+
118
+ .. code-block:: python
119
+
120
+ from csvsmith import clean_numeric
121
+
122
+ # Handles separators and parentheses for negative numbers
123
+ val1 = clean_numeric("1,234.56")
124
+ val2 = clean_numeric("(100.00)")
125
+ val3 = clean_numeric("1 234,56", sep=" ", decimal=",")
126
+
127
+ # Use relaxed mode to return original value on failure instead of raising ValueError
128
+ val4 = clean_numeric("not a number", relaxed=True)
129
+
130
+ print(val1) # 1234.56
131
+ print(val2) # -100.0
132
+ print(val3) # 1234.56
133
+ print(val4) # "not a number"
134
+
115
135
  Drop rows in a CSV by column name
116
136
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
117
137
 
@@ -161,7 +181,7 @@ Move files by suffix
161
181
  print(f"Moved {moved_count} files.")
162
182
 
163
183
  CSV File Classification (Python)
164
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
184
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165
185
 
166
186
  .. code-block:: python
167
187
 
@@ -273,6 +293,25 @@ Move files by suffix
273
293
  This moves files whose suffix matches one of the given values. The suffixes can
274
294
  be written with or without a leading dot, and matching is case-insensitive.
275
295
 
296
+ Clean numeric values
297
+ ~~~~~~~~~~~~~~~~~~~~
298
+
299
+ .. code-block:: bash
300
+
301
+ csvsmith clean-numeric "1,234.56"
302
+
303
+ Using custom separators:
304
+
305
+ .. code-block:: bash
306
+
307
+ csvsmith clean-numeric "1 234,56" --sep " " --decimal ","
308
+
309
+ Relaxed mode (returns original string on failure):
310
+
311
+ .. code-block:: bash
312
+
313
+ csvsmith clean-numeric "not a number" --relaxed
314
+
276
315
  Drop CSV rows
277
316
  ~~~~~~~~~~~~~
278
317
 
@@ -337,7 +376,7 @@ Example
337
376
 
338
377
  Suppose you have a CSV like this:
339
378
 
340
- .. code-block:: csv
379
+ .. code-block:: text
341
380
 
342
381
  id,name,notes
343
382
  1,Alice,ok
@@ -352,7 +391,7 @@ Running:
352
391
 
353
392
  produces a filtered file containing:
354
393
 
355
- .. code-block:: csv
394
+ .. code-block:: text
356
395
 
357
396
  id,name,notes
358
397
  1,Alice,ok
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "csvsmith"
7
- version = "0.4.0"
7
+ version = "0.6.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"
@@ -21,6 +21,7 @@ Compatibility aliases:
21
21
  - CSVCleaner
22
22
 
23
23
  Submodules:
24
+ - csvsmith.clean_numeric
24
25
  - csvsmith.string_distance
25
26
  - csvsmith.row_dedup
26
27
  - csvsmith.classify
@@ -30,8 +31,9 @@ Submodules:
30
31
  - csvsmith.cli (CLI entrypoint)
31
32
  """
32
33
 
33
- __version__ = "0.4.0"
34
+ __version__ = "0.6.0"
34
35
 
36
+ from .clean_numeric import clean_numeric
35
37
  from .row_dedup import (
36
38
  count_duplicates_sorted,
37
39
  add_row_digest,
@@ -61,4 +63,5 @@ __all__ = [
61
63
  "Relation",
62
64
  "Result",
63
65
  "analyze_pair",
66
+ "clean_numeric",
64
67
  ]
@@ -0,0 +1,124 @@
1
+ import re
2
+ from typing import Any
3
+
4
+ NON_BREAKING_SPACE = "\xa0"
5
+ SEPARATOR_PATTERN = re.compile(r"[ _\xa0]")
6
+ NUMBER_PATTERN = re.compile(r"^-?(?:\d+|\d*\.\d+)$")
7
+
8
+
9
+ def _normalize_numeric_text(value: Any, *, sep: str, decimal: str) -> str:
10
+ """
11
+ Normalize a numeric text string for consistent formatting.
12
+
13
+ Converts a given value to a string representation and ensures normalization of numeric formatting,
14
+ such as removing group separators, converting localized decimal separators, and handling negative
15
+ values enclosed in parentheses.
16
+
17
+ :param value: The value to be normalized, which may be of any type.
18
+ :type value: Any
19
+ :param sep: The character used as a group separator in the input value, which will be removed
20
+ during normalization.
21
+ :type sep: str
22
+ :param decimal: The character used as the decimal separator in the input value, which will
23
+ be replaced with a standard period ('.') during normalization.
24
+ :type decimal: str
25
+ :return: A normalized numeric string with consistent formatting.
26
+ :rtype: str
27
+ """
28
+ numeric_text = str(value).strip()
29
+
30
+ if numeric_text.startswith("(") and numeric_text.endswith(")"):
31
+ numeric_text = f"-{numeric_text[1:-1]}"
32
+
33
+ if sep:
34
+ numeric_text = numeric_text.replace(sep, "")
35
+
36
+ if decimal != ".":
37
+ numeric_text = numeric_text.replace(decimal, ".")
38
+
39
+ return numeric_text
40
+
41
+
42
+ def _has_valid_grouping(numeric_text: str, *, decimal: str) -> bool:
43
+ """
44
+ Checks whether a numeric text string has valid grouping based on a specified decimal character.
45
+
46
+ This function validates the structure of the given numeric text to determine if it adheres to allowed
47
+ grouping conventions. It ensures the string does not contain invalid or misplaced group separators,
48
+ decimal points, or spacing characters.
49
+
50
+ :param numeric_text: A string representing the numeric text to be validated.
51
+ :type numeric_text: str
52
+ :param decimal: A string representing the character used as the decimal point.
53
+ :type decimal: str
54
+ :return: True if the numeric text satisfies the grouping rules; otherwise, False.
55
+ :rtype: bool
56
+ """
57
+ if not numeric_text:
58
+ return False
59
+
60
+ unsigned_text = numeric_text[1:] if numeric_text.startswith("-") else numeric_text
61
+
62
+ if unsigned_text.count(decimal) > 1:
63
+ return False
64
+
65
+ integer_text, _, fraction_text = unsigned_text.partition(decimal)
66
+
67
+ if not integer_text and not fraction_text:
68
+ return False
69
+
70
+ for part in (integer_text, fraction_text):
71
+ if not part:
72
+ continue
73
+ if part.startswith("_") or part.endswith("_"):
74
+ return False
75
+ if part.startswith(" ") or part.endswith(" "):
76
+ return False
77
+ if part.startswith(NON_BREAKING_SPACE) or part.endswith(NON_BREAKING_SPACE):
78
+ return False
79
+ if "__" in part or " " in part or NON_BREAKING_SPACE * 2 in part:
80
+ return False
81
+
82
+ stripped_text = SEPARATOR_PATTERN.sub("", numeric_text)
83
+ return bool(NUMBER_PATTERN.fullmatch(stripped_text))
84
+
85
+
86
+ def clean_numeric(
87
+ value: Any, *, sep: str = ",", decimal: str = ".", relaxed: bool = False
88
+ ) -> float | Any:
89
+ """
90
+ Cleans and converts a given input to a float by normalizing its numeric representation.
91
+ Handles separators and decimal points based on the provided arguments. If the input
92
+ value is invalid or cannot be converted, a ValueError is raised unless relaxed mode
93
+ is enabled.
94
+
95
+ :param value: The input value to be cleaned and converted.
96
+ :type value: Any
97
+ :param sep: The character used as a thousands separator in the input value. Default is ",".
98
+ :type sep: str
99
+ :param decimal: The character used as a decimal point in the input value. Default is ".".
100
+ :type decimal: str
101
+ :param relaxed: If True, return the original input when it is not numeric.
102
+ :type relaxed: bool
103
+ :return: The cleaned and converted numeric value as a float, or the original value in relaxed mode.
104
+ :rtype: float | Any
105
+ :raises ValueError: If the input value cannot be converted to a valid number and relaxed is False.
106
+ """
107
+ if value is None:
108
+ return 0.0
109
+
110
+ normalized_number_text = _normalize_numeric_text(value, sep=sep, decimal=decimal)
111
+
112
+ if not _has_valid_grouping(normalized_number_text, decimal=decimal):
113
+ if relaxed:
114
+ return value
115
+ raise ValueError(f"Could not convert {value!r} to a valid number.")
116
+
117
+ numeric_text = SEPARATOR_PATTERN.sub("", normalized_number_text)
118
+
119
+ try:
120
+ return float(numeric_text)
121
+ except ValueError as exc:
122
+ if relaxed:
123
+ return value
124
+ raise ValueError(f"Could not convert {value!r} to a valid number.") from exc
@@ -9,6 +9,7 @@ from typing import Optional, Sequence
9
9
  from . import __version__
10
10
  from .classify import CSVClassifier
11
11
  from .excel2csv import excel_to_csv
12
+ from .clean_numeric import clean_numeric
12
13
  from .filter_rows import DropRowsBySubstring
13
14
  from .move_files import move_by_suffix
14
15
  from .row_dedup import (
@@ -148,6 +149,21 @@ def cmd_string_distance(args: argparse.Namespace) -> int:
148
149
  return 0
149
150
 
150
151
 
152
+ def cmd_clean_numeric(args: argparse.Namespace) -> int:
153
+ try:
154
+ cleaned = clean_numeric(
155
+ args.value,
156
+ sep=args.sep,
157
+ decimal=args.decimal,
158
+ relaxed=args.relaxed,
159
+ )
160
+ print(cleaned)
161
+ except ValueError as e:
162
+ print(f"Error: {e}", file=sys.stderr)
163
+ return 1
164
+ return 0
165
+
166
+
151
167
  def _add_row_duplicates_parser(subparsers) -> None:
152
168
  parser = subparsers.add_parser("row-duplicates", help="Find duplicate rows in a CSV.")
153
169
  parser.add_argument("input", help="Input CSV file.")
@@ -230,6 +246,21 @@ def _add_string_distance_parser(subparsers) -> None:
230
246
  parser.set_defaults(func=cmd_string_distance)
231
247
 
232
248
 
249
+ def _add_clean_numeric_parser(subparsers) -> None:
250
+ parser = subparsers.add_parser(
251
+ "clean-numeric", help="Clean and convert a numeric string to float."
252
+ )
253
+ parser.add_argument("value", help="Numeric value to clean.")
254
+ parser.add_argument("--sep", default=",", help="Group separator (default: ,).")
255
+ parser.add_argument("--decimal", default=".", help="Decimal separator (default: .).")
256
+ parser.add_argument(
257
+ "--relaxed",
258
+ action="store_true",
259
+ help="Return the original input when it is not numeric.",
260
+ )
261
+ parser.set_defaults(func=cmd_clean_numeric)
262
+
263
+
233
264
  def build_parser() -> argparse.ArgumentParser:
234
265
  parser = argparse.ArgumentParser(
235
266
  prog="csvsmith",
@@ -249,6 +280,7 @@ def build_parser() -> argparse.ArgumentParser:
249
280
  _add_excel_to_csv_parser(subparsers)
250
281
  _add_drop_rows_parser(subparsers)
251
282
  _add_string_distance_parser(subparsers)
283
+ _add_clean_numeric_parser(subparsers)
252
284
 
253
285
  return parser
254
286
 
@@ -75,7 +75,13 @@ class StringDistance:
75
75
 
76
76
  @staticmethod
77
77
  def strip_all(s: str) -> str:
78
- return "".join(ch for ch in s if not ch.isspace())
78
+ """
79
+ Remove all whitespace characters from a string.
80
+
81
+ Uses split/join logic so any whitespace character acts as a separator,
82
+ including spaces, tabs, and newlines.
83
+ """
84
+ return "".join(s.split())
79
85
 
80
86
  @staticmethod
81
87
  def equals_ignore_case(a: str, b: str) -> bool:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csvsmith
3
- Version: 0.4.0
3
+ Version: 0.6.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
6
  License: MIT License
@@ -50,7 +50,7 @@ csvsmith
50
50
  :target: https://pypi.org/project/csvsmith/
51
51
 
52
52
  .. image:: https://img.shields.io/pypi/l/csvsmith.svg
53
- :target: https://pypi.org/project/ccsvsmith/
53
+ :target: https://pypi.org/project/csvsmith/
54
54
 
55
55
  Introduction
56
56
  ------------
@@ -77,6 +77,7 @@ Features
77
77
  - rollback support via manifest
78
78
  - row filtering by substring
79
79
  - Excel worksheet to CSV conversion
80
+ - numeric string cleaning and normalization
80
81
  - file moving by suffix
81
82
  - string distance and similarity analysis
82
83
  - a single command-line entrypoint with subcommands
@@ -153,7 +154,26 @@ Analyze string distance
153
154
  print(result.jaro_winkler_score)
154
155
  print(result.similarity_percentage)
155
156
 
156
- CLI Usage
157
+ Clean numeric values
158
+ ~~~~~~~~~~~~~~~~~~~~
159
+
160
+ .. code-block:: python
161
+
162
+ from csvsmith import clean_numeric
163
+
164
+ # Handles separators and parentheses for negative numbers
165
+ val1 = clean_numeric("1,234.56")
166
+ val2 = clean_numeric("(100.00)")
167
+ val3 = clean_numeric("1 234,56", sep=" ", decimal=",")
168
+
169
+ # Use relaxed mode to return original value on failure instead of raising ValueError
170
+ val4 = clean_numeric("not a number", relaxed=True)
171
+
172
+ print(val1) # 1234.56
173
+ print(val2) # -100.0
174
+ print(val3) # 1234.56
175
+ print(val4) # "not a number"
176
+
157
177
  Drop rows in a CSV by column name
158
178
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159
179
 
@@ -203,7 +223,7 @@ Move files by suffix
203
223
  print(f"Moved {moved_count} files.")
204
224
 
205
225
  CSV File Classification (Python)
206
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
226
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
207
227
 
208
228
  .. code-block:: python
209
229
 
@@ -315,6 +335,25 @@ Move files by suffix
315
335
  This moves files whose suffix matches one of the given values. The suffixes can
316
336
  be written with or without a leading dot, and matching is case-insensitive.
317
337
 
338
+ Clean numeric values
339
+ ~~~~~~~~~~~~~~~~~~~~
340
+
341
+ .. code-block:: bash
342
+
343
+ csvsmith clean-numeric "1,234.56"
344
+
345
+ Using custom separators:
346
+
347
+ .. code-block:: bash
348
+
349
+ csvsmith clean-numeric "1 234,56" --sep " " --decimal ","
350
+
351
+ Relaxed mode (returns original string on failure):
352
+
353
+ .. code-block:: bash
354
+
355
+ csvsmith clean-numeric "not a number" --relaxed
356
+
318
357
  Drop CSV rows
319
358
  ~~~~~~~~~~~~~
320
359
 
@@ -379,7 +418,7 @@ Example
379
418
 
380
419
  Suppose you have a CSV like this:
381
420
 
382
- .. code-block:: csv
421
+ .. code-block:: text
383
422
 
384
423
  id,name,notes
385
424
  1,Alice,ok
@@ -394,7 +433,7 @@ Running:
394
433
 
395
434
  produces a filtered file containing:
396
435
 
397
- .. code-block:: csv
436
+ .. code-block:: text
398
437
 
399
438
  id,name,notes
400
439
  1,Alice,ok
@@ -3,6 +3,7 @@ README.rst
3
3
  pyproject.toml
4
4
  src/csvsmith/__init__.py
5
5
  src/csvsmith/classify.py
6
+ src/csvsmith/clean_numeric.py
6
7
  src/csvsmith/cli.py
7
8
  src/csvsmith/excel2csv.py
8
9
  src/csvsmith/filter_rows.py
@@ -16,6 +17,7 @@ src/csvsmith.egg-info/entry_points.txt
16
17
  src/csvsmith.egg-info/requires.txt
17
18
  src/csvsmith.egg-info/top_level.txt
18
19
  tests/test_classify.py
20
+ tests/test_clean_numeric.py
19
21
  tests/test_cli.py
20
22
  tests/test_excel2csv.py
21
23
  tests/test_filter_rows.py
@@ -0,0 +1,36 @@
1
+ import pytest
2
+ from src.csvsmith.clean_numeric import clean_numeric
3
+
4
+
5
+ def test_clean_numeric_with_valid_integer_string() -> None:
6
+ assert clean_numeric("1,234") == 1234.0
7
+
8
+
9
+ def test_clean_numeric_with_valid_float_string() -> None:
10
+ assert clean_numeric("1,234.56") == 1234.56
11
+
12
+
13
+ def test_clean_numeric_with_custom_separator() -> None:
14
+ assert clean_numeric("1.234,56", sep=".", decimal=",") == 1234.56
15
+
16
+
17
+ def test_clean_numeric_with_negative_value() -> None:
18
+ assert clean_numeric("-1,234.56") == -1234.56
19
+
20
+
21
+ def test_clean_numeric_with_parenthesis_for_negative() -> None:
22
+ assert clean_numeric("(1,234.56)") == -1234.56
23
+
24
+
25
+ def test_clean_numeric_with_invalid_value_raises_valueerror() -> None:
26
+ with pytest.raises(ValueError, match="Could not convert 'abc'.*"):
27
+ clean_numeric("abc")
28
+
29
+
30
+ def test_clean_numeric_with_multiple_decimal_points_raises_valueerror() -> None:
31
+ with pytest.raises(ValueError, match="Could not convert '1,234.5.6'.*"):
32
+ clean_numeric("1,234.5.6")
33
+
34
+
35
+ def test_clean_numeric_with_none_value() -> None:
36
+ assert clean_numeric(None) == 0.0
@@ -126,3 +126,34 @@ def test_cli_parses_move_files_command_with_suffixes_without_dots():
126
126
  assert args.source == "src_dir"
127
127
  assert args.dest == "dst_dir"
128
128
  assert args.suffixes == "csv,pdf"
129
+
130
+
131
+ def test_cli_parses_clean_numeric_command():
132
+ parser = build_parser()
133
+ args = parser.parse_args(["clean-numeric", "1,234.56", "--sep", ",", "--decimal", "."])
134
+
135
+ assert args.command == "clean-numeric"
136
+ assert args.value == "1,234.56"
137
+ assert args.sep == ","
138
+ assert args.decimal == "."
139
+
140
+
141
+ def test_cli_parses_clean_numeric_command_with_relaxed_mode():
142
+ parser = build_parser()
143
+ args = parser.parse_args(
144
+ ["clean-numeric", "not-a-number", "--relaxed"]
145
+ )
146
+
147
+ assert args.command == "clean-numeric"
148
+ assert args.value == "not-a-number"
149
+ assert args.relaxed is True
150
+
151
+
152
+ def test_cli_parses_string_distance_command():
153
+ parser = build_parser()
154
+ args = parser.parse_args(["string-distance", "a", "b", "--ignore-case"])
155
+
156
+ assert args.command == "string-distance"
157
+ assert args.string_a == "a"
158
+ assert args.string_b == "b"
159
+ assert args.ignore_case is True
File without changes
File without changes