1from __future__ import annotations
2
3from collections import defaultdict
4from copy import copy
5import csv
6from enum import Enum
7import itertools
8from typing import (
9 TYPE_CHECKING,
10 Any,
11 cast,
12 final,
13 overload,
14)
15import warnings
16
17import numpy as np
18
19from pandas._libs import (
20 lib,
21 parsers,
22)
23import pandas._libs.ops as libops
24from pandas._libs.parsers import STR_NA_VALUES
25from pandas.compat._optional import import_optional_dependency
26from pandas.errors import (
27 ParserError,
28 ParserWarning,
29)
30from pandas.util._exceptions import find_stack_level
31
32from pandas.core.dtypes.common import (
33 is_bool_dtype,
34 is_dict_like,
35 is_float_dtype,
36 is_integer,
37 is_integer_dtype,
38 is_list_like,
39 is_object_dtype,
40 is_string_dtype,
41)
42from pandas.core.dtypes.missing import isna
43
44from pandas import (
45 DataFrame,
46 DatetimeIndex,
47 StringDtype,
48)
49from pandas.core import algorithms
50from pandas.core.arrays import (
51 ArrowExtensionArray,
52 BaseMaskedArray,
53 BooleanArray,
54 FloatingArray,
55 IntegerArray,
56)
57from pandas.core.indexes.api import (
58 Index,
59 MultiIndex,
60 default_index,
61 ensure_index_from_sequences,
62)
63from pandas.core.series import Series
64from pandas.core.tools import datetimes as tools
65
66from pandas.io.common import is_potential_multi_index
67
68if TYPE_CHECKING:
69 from collections.abc import (
70 Callable,
71 Iterable,
72 Mapping,
73 Sequence,
74 )
75
76 from pandas._typing import (
77 ArrayLike,
78 DtypeArg,
79 Hashable,
80 HashableT,
81 Scalar,
82 SequenceT,
83 )
84
85
86class ParserBase:
87 class BadLineHandleMethod(Enum):
88 BLHM_ERROR = 0
89 BLHM_WARN = 1
90 BLHM_SKIP = 2
91
92 _implicit_index: bool
93 _first_chunk: bool
94 keep_default_na: bool
95 dayfirst: bool
96 cache_dates: bool
97 usecols_dtype: str | None
98
99 def __init__(self, kwds) -> None:
100 self._implicit_index = False
101
102 self.names = kwds.get("names")
103 self.orig_names: Sequence[Hashable] | None = None
104
105 self.index_col = kwds.get("index_col", None)
106 self.unnamed_cols: set = set()
107 self.index_names: Sequence[Hashable] | None = None
108 self.col_names: Sequence[Hashable] | None = None
109
110 parse_dates = kwds.pop("parse_dates", False)
111 if parse_dates is None or lib.is_bool(parse_dates):
112 parse_dates = bool(parse_dates)
113 elif not isinstance(parse_dates, list):
114 raise TypeError(
115 "Only booleans and lists are accepted for the 'parse_dates' parameter"
116 )
117 self.parse_dates: bool | list = parse_dates
118 self.date_parser = kwds.pop("date_parser", lib.no_default)
119 self.date_format = kwds.pop("date_format", None)
120 self.dayfirst = kwds.pop("dayfirst", False)
121
122 self.na_values = kwds.get("na_values")
123 self.na_fvalues = kwds.get("na_fvalues")
124 self.na_filter = kwds.get("na_filter", False)
125 self.keep_default_na = kwds.get("keep_default_na", True)
126
127 self.dtype = copy(kwds.get("dtype", None))
128 self.converters = kwds.get("converters")
129 self.dtype_backend = kwds.get("dtype_backend")
130
131 self.true_values = kwds.get("true_values")
132 self.false_values = kwds.get("false_values")
133 self.cache_dates = kwds.pop("cache_dates", True)
134
135 # validate header options for mi
136 self.header = kwds.get("header")
137 if is_list_like(self.header, allow_sets=False):
138 if kwds.get("usecols"):
139 raise ValueError(
140 "cannot specify usecols when specifying a multi-index header"
141 )
142 if kwds.get("names"):
143 raise ValueError(
144 "cannot specify names when specifying a multi-index header"
145 )
146
147 # validate index_col that only contains integers
148 if self.index_col is not None:
149 # In this case we can pin down index_col as list[int]
150 if is_integer(self.index_col):
151 self.index_col = [self.index_col]
152 elif not (
153 is_list_like(self.index_col, allow_sets=False)
154 and all(map(is_integer, self.index_col))
155 ):
156 raise ValueError(
157 "index_col must only contain integers of column positions "
158 "when specifying a multi-index header"
159 )
160 else:
161 self.index_col = list(self.index_col)
162
163 self._first_chunk = True
164
165 self.usecols, self.usecols_dtype = _validate_usecols_arg(kwds["usecols"])
166
167 # Fallback to error to pass a sketchy test(test_override_set_noconvert_columns)
168 # Normally, this arg would get pre-processed earlier on
169 self.on_bad_lines = kwds.get(
170 "on_bad_lines", self.BadLineHandleMethod.BLHM_ERROR
171 )
172
173 # See TextReader.warning_sink: when not None, ParserWarnings are
174 # collected here instead of being raised, so the parallel reader can
175 # raise one copy of a warning its several reads each hit (GH#66259).
176 self._warning_sink: list[tuple[str, type[Warning]]] | None = None
177
178 def close(self) -> None:
179 pass
180
181 @final
182 def _warn_parser(self, msg: str) -> None:
183 if self._warning_sink is not None:
184 self._warning_sink.append((msg, ParserWarning))
185 else:
186 warnings.warn(msg, ParserWarning, stacklevel=find_stack_level())
187
188 @final
189 def _should_parse_dates(self, i: int) -> bool:
190 if isinstance(self.parse_dates, bool):
191 return self.parse_dates
192 else:
193 if self.index_names is not None:
194 name = self.index_names[i]
195 else:
196 name = None
197 j = i if self.index_col is None else self.index_col[i]
198
199 return (j in self.parse_dates) or (
200 name is not None and name in self.parse_dates
201 )
202
203 @final
204 def _extract_multi_indexer_columns(
205 self,
206 header,
207 index_names: Sequence[Hashable] | None,
208 passed_names: bool = False,
209 ) -> tuple[
210 Sequence[Hashable], Sequence[Hashable] | None, Sequence[Hashable] | None, bool
211 ]:
212 """
213 Extract and return the names, index_names, col_names if the column
214 names are a MultiIndex.
215
216 Parameters
217 ----------
218 header: list of lists
219 The header rows
220 index_names: list, optional
221 The names of the future index
222 passed_names: bool, default False
223 A flag specifying if names where passed
224
225 """
226 if len(header) < 2:
227 return header[0], index_names, None, passed_names
228
229 # the names are the tuples of the header that are not the index cols
230 # 0 is the name of the index, assuming index_col is a list of column
231 # numbers
232 ic = self.index_col
233 if ic is None:
234 ic = []
235
236 if not isinstance(ic, (list, tuple, np.ndarray)):
237 ic = [ic]
238 sic = set(ic)
239
240 # clean the index_names
241 index_names = header.pop(-1)
242 index_names, _, _ = self._clean_index_names(index_names, self.index_col)
243
244 # extract the columns
245 field_count = len(header[0])
246
247 # check if header lengths are equal
248 if not all(len(header_iter) == field_count for header_iter in header[1:]):
249 raise ParserError("Header rows must have an equal number of columns.")
250
251 def extract(r):
252 return tuple(r[i] for i in range(field_count) if i not in sic)
253
254 columns = list(zip(*(extract(r) for r in header), strict=True))
255 names = columns.copy()
256 for single_ic in sorted(ic):
257 names.insert(single_ic, single_ic)
258
259 # Clean the column names (if we have an index_col).
260 if ic:
261 col_names = [
262 r[ic[0]]
263 if ((r[ic[0]] is not None) and r[ic[0]] not in self.unnamed_cols)
264 else None
265 for r in header
266 ]
267 else:
268 col_names = [None] * len(header)
269
270 passed_names = True
271
272 return names, index_names, col_names, passed_names
273
274 @final
275 def _maybe_make_multi_index_columns(
276 self,
277 columns: SequenceT,
278 col_names: Sequence[Hashable] | None = None,
279 ) -> SequenceT | MultiIndex:
280 # possibly create a column mi here
281 if is_potential_multi_index(columns):
282 columns_mi = cast("Sequence[tuple[Hashable, ...]]", columns)
283 return MultiIndex.from_tuples(columns_mi, names=col_names)
284 return columns
285
286 @final
287 def _make_index(
288 self, alldata, columns, indexnamerow: list[Scalar] | None = None
289 ) -> tuple[Index | None, Sequence[Hashable] | MultiIndex]:
290 index: Index | None
291 if isinstance(self.index_col, list) and len(self.index_col):
292 to_remove = []
293 indexes = []
294 for idx in self.index_col:
295 if isinstance(idx, str):
296 raise ValueError(f"Index {idx} invalid")
297 to_remove.append(idx)
298 indexes.append(alldata[idx])
299 # remove index items from content and columns, don't pop in
300 # loop
301 for i in sorted(to_remove, reverse=True):
302 alldata.pop(i)
303 if not self._implicit_index:
304 columns.pop(i)
305 index = self._agg_index(indexes)
306
307 # add names for the index
308 if indexnamerow:
309 coffset = len(indexnamerow) - len(columns)
310 index = index.set_names(indexnamerow[:coffset])
311 else:
312 index = None
313
314 # maybe create a mi on the columns
315 columns = self._maybe_make_multi_index_columns(columns, self.col_names)
316
317 return index, columns
318
319 @final
320 def _clean_mapping(self, mapping):
321 """converts col numbers to names"""
322 if not isinstance(mapping, dict):
323 return mapping
324 clean = {}
325 # for mypy
326 assert self.orig_names is not None
327
328 for col, v in mapping.items():
329 if isinstance(col, int) and col not in self.orig_names:
330 col = self.orig_names[col]
331 clean[col] = v
332 if isinstance(mapping, defaultdict):
333 remaining_cols = set(self.orig_names) - set(clean.keys())
334 clean.update({col: mapping[col] for col in remaining_cols})
335 return clean
336
337 @final
338 def _agg_index(self, index) -> Index:
339 arrays = []
340 converters = self._clean_mapping(self.converters)
341 clean_dtypes = self._clean_mapping(self.dtype)
342
343 if self.index_names is not None:
344 names: Iterable = self.index_names
345 zip_strict = True
346 else:
347 names = itertools.cycle([None])
348 zip_strict = False
349 for i, (arr, name) in enumerate(zip(index, names, strict=zip_strict)):
350 if self._should_parse_dates(i):
351 arr = date_converter(
352 arr,
353 col=self.index_names[i] if self.index_names is not None else None,
354 dayfirst=self.dayfirst,
355 cache_dates=self.cache_dates,
356 date_format=self.date_format,
357 )
358
359 if self.na_filter:
360 col_na_values = self.na_values
361 col_na_fvalues = self.na_fvalues
362 else:
363 col_na_values = set()
364 col_na_fvalues = set()
365
366 if isinstance(self.na_values, dict):
367 assert self.index_names is not None
368 col_name = self.index_names[i]
369 if col_name is not None:
370 col_na_values, col_na_fvalues = get_na_values(
371 col_name, self.na_values, self.na_fvalues, self.keep_default_na
372 )
373 else:
374 col_na_values, col_na_fvalues = set(), set()
375
376 cast_type = None
377 index_converter = False
378 if self.index_names is not None:
379 if isinstance(clean_dtypes, dict):
380 cast_type = clean_dtypes.get(self.index_names[i], None)
381
382 if isinstance(converters, dict):
383 index_converter = converters.get(self.index_names[i]) is not None
384
385 try_num_bool = not (
386 (cast_type and is_string_dtype(cast_type)) or index_converter
387 )
388
389 arr, _ = self._infer_types(
390 arr, col_na_values | col_na_fvalues, cast_type is None, try_num_bool
391 )
392 if cast_type is not None:
393 # Don't perform RangeIndex inference
394 idx = Index(arr, name=name, dtype=cast_type, copy=False)
395 else:
396 idx = ensure_index_from_sequences([arr], [name])
397 arrays.append(idx)
398
399 if len(arrays) == 1:
400 return arrays[0]
401 else:
402 return MultiIndex.from_arrays(arrays)
403
404 @final
405 def _set_noconvert_dtype_columns(
406 self, col_indices: list[int], names: Sequence[Hashable]
407 ) -> set[int]:
408 """
409 Set the columns that should not undergo dtype conversions.
410
411 Currently, any column that is involved with date parsing will not
412 undergo such conversions. If usecols is specified, the positions of the columns
413 not to cast is relative to the usecols not to all columns.
414
415 Parameters
416 ----------
417 col_indices: The indices specifying order and positions of the columns
418 names: The column names which order is corresponding with the order
419 of col_indices
420
421 Returns
422 -------
423 A set of integers containing the positions of the columns not to convert.
424 """
425 usecols: list[int] | list[str] | None
426 noconvert_columns = set()
427 if self.usecols_dtype == "integer":
428 # A set of integers will be converted to a list in
429 # the correct order every single time.
430 usecols = sorted(self.usecols)
431 elif callable(self.usecols) or self.usecols_dtype not in ("empty", None):
432 # The names attribute should have the correct columns
433 # in the proper order for indexing with parse_dates.
434 usecols = col_indices
435 else:
436 # Usecols is empty.
437 usecols = None
438
439 def _set(x) -> int:
440 if usecols is not None and is_integer(x):
441 x = usecols[x]
442
443 if not is_integer(x):
444 x = col_indices[names.index(x)]
445
446 return x
447
448 if isinstance(self.parse_dates, list):
449 validate_parse_dates_presence(self.parse_dates, names)
450 for val in self.parse_dates:
451 noconvert_columns.add(_set(val))
452
453 elif self.parse_dates:
454 if isinstance(self.index_col, list):
455 for k in self.index_col:
456 noconvert_columns.add(_set(k))
457 elif self.index_col is not None:
458 noconvert_columns.add(_set(self.index_col))
459
460 return noconvert_columns
461
462 @final
463 def _infer_types(
464 self, values, na_values, no_dtype_specified, try_num_bool: bool = True
465 ) -> tuple[ArrayLike, int]:
466 """
467 Infer types of values, possibly casting
468
469 Parameters
470 ----------
471 values : ndarray
472 na_values : set
473 no_dtype_specified: Specifies if we want to cast explicitly
474 try_num_bool : bool, default try
475 try to cast values to numeric (first preference) or boolean
476
477 Returns
478 -------
479 converted : ndarray or ExtensionArray
480 na_count : int
481 """
482 na_count = 0
483 if issubclass(values.dtype.type, (np.number, np.bool_)):
484 # If our array has numeric dtype, we don't have to check for strings in isin
485 na_values = np.array([val for val in na_values if not isinstance(val, str)])
486 mask = algorithms.isin(values, na_values)
487 na_count = mask.astype("uint8", copy=False).sum()
488 if na_count > 0:
489 if is_integer_dtype(values):
490 values = values.astype(np.float64)
491 np.putmask(values, mask, np.nan)
492 return values, na_count
493
494 dtype_backend = self.dtype_backend
495 non_default_dtype_backend = (
496 no_dtype_specified and dtype_backend is not lib.no_default
497 )
498 result: ArrayLike
499
500 if try_num_bool and is_object_dtype(values.dtype):
501 # exclude e.g DatetimeIndex here
502 try:
503 result, result_mask = lib.maybe_convert_numeric(
504 values,
505 na_values,
506 False,
507 convert_to_masked_nullable=non_default_dtype_backend, # type: ignore[arg-type]
508 )
509 except (ValueError, TypeError):
510 # e.g. encountering datetime string gets ValueError
511 # TypeError can be raised in floatify
512 na_count = parsers.sanitize_objects(values, na_values)
513 result = values
514 else:
515 if non_default_dtype_backend:
516 if result_mask is None:
517 result_mask = np.zeros(result.shape, dtype=np.bool_)
518
519 if result_mask.all():
520 result = IntegerArray(
521 np.ones(result_mask.shape, dtype=np.int64), result_mask
522 )
523 elif is_integer_dtype(result):
524 result = IntegerArray(result, result_mask)
525 elif is_bool_dtype(result):
526 result = BooleanArray(result, result_mask)
527 elif is_float_dtype(result):
528 result = FloatingArray(result, result_mask)
529
530 na_count = result_mask.sum() # type: ignore[assignment]
531 else:
532 na_count = isna(result).sum() # type: ignore[assignment]
533 else:
534 result = values
535 if values.dtype == np.object_:
536 na_count = parsers.sanitize_objects(values, na_values)
537
538 if (
539 result.dtype == np.object_
540 and try_num_bool
541 and (len(result) == 0 or not isinstance(result[0], int))
542 ):
543 result, bool_mask = libops.maybe_convert_bool(
544 np.asarray(values),
545 true_values=self.true_values,
546 false_values=self.false_values,
547 convert_to_masked_nullable=non_default_dtype_backend, # type: ignore[arg-type]
548 )
549 if result.dtype == np.bool_ and non_default_dtype_backend:
550 if bool_mask is None:
551 bool_mask = np.zeros(result.shape, dtype=np.bool_)
552 result = BooleanArray(result, bool_mask)
553 elif result.dtype == np.object_ and non_default_dtype_backend:
554 # read_excel sends array of datetime objects
555 if not lib.is_datetime_array(result, skipna=True):
556 dtype = StringDtype()
557 cls = dtype.construct_array_type()
558 result = cls._from_sequence(values, dtype=dtype)
559
560 if dtype_backend == "pyarrow":
561 pa = import_optional_dependency("pyarrow")
562 if isinstance(result, np.ndarray):
563 result = ArrowExtensionArray(pa.array(result, from_pandas=True))
564 elif isinstance(result, BaseMaskedArray):
565 if result._mask.all():
566 # We want an arrow null array here
567 result = ArrowExtensionArray(pa.array([None] * len(result)))
568 else:
569 result = ArrowExtensionArray(
570 pa.array(result._data, mask=result._mask)
571 )
572 else:
573 result = ArrowExtensionArray(
574 pa.array(result.to_numpy(), from_pandas=True)
575 )
576
577 return result, na_count
578
579 @overload
580 def _do_date_conversions(
581 self,
582 names: Index,
583 data: DataFrame,
584 ) -> DataFrame: ...
585
586 @overload
587 def _do_date_conversions(
588 self,
589 names: Sequence[Hashable],
590 data: Mapping[Hashable, ArrayLike],
591 ) -> Mapping[Hashable, ArrayLike]: ...
592
593 @final
594 def _do_date_conversions(
595 self,
596 names: Sequence[Hashable] | Index,
597 data: Mapping[Hashable, ArrayLike] | DataFrame,
598 ) -> Mapping[Hashable, ArrayLike] | DataFrame:
599 if not isinstance(self.parse_dates, list):
600 return data
601 for colspec in self.parse_dates:
602 if isinstance(colspec, int) and colspec not in data:
603 colspec = names[colspec]
604 if (isinstance(self.index_col, list) and colspec in self.index_col) or (
605 isinstance(self.index_names, list) and colspec in self.index_names
606 ):
607 continue
608 result = date_converter(
609 data[colspec],
610 col=colspec,
611 dayfirst=self.dayfirst,
612 cache_dates=self.cache_dates,
613 date_format=self.date_format,
614 )
615 # error: Unsupported target for indexed assignment
616 # ("Mapping[Hashable, ExtensionArray | ndarray[Any, Any]] | DataFrame")
617 data[colspec] = result # type: ignore[index]
618
619 return data
620
621 @final
622 def _check_data_length(
623 self,
624 columns: Sequence[Hashable],
625 data: Sequence[ArrayLike],
626 ) -> None:
627 """Checks if length of data is equal to length of column names.
628
629 One set of trailing commas is allowed. self.index_col not False
630 results in a ParserError previously when lengths do not match.
631
632 Parameters
633 ----------
634 columns: list of column names
635 data: list of array-likes containing the data column-wise.
636 """
637 if not self.index_col and len(columns) != len(data) and columns:
638 empty_str = is_object_dtype(data[-1]) and data[-1] == ""
639 # error: No overload variant of "__ror__" of "ndarray" matches
640 # argument type "ExtensionArray"
641 empty_str_or_na = empty_str | isna(data[-1]) # type: ignore[operator]
642 if len(columns) == len(data) - 1 and np.all(empty_str_or_na):
643 return
644 self._warn_parser(
645 "Length of header or names does not match length of data. This leads "
646 "to a loss of data with index_col=False."
647 )
648
649 @final
650 def _validate_usecols_names(self, usecols: SequenceT, names: Sequence) -> SequenceT:
651 """
652 Validates that all usecols are present in a given
653 list of names. If not, raise a ValueError that
654 shows what usecols are missing.
655
656 Parameters
657 ----------
658 usecols : iterable of usecols
659 The columns to validate are present in names.
660 names : iterable of names
661 The column names to check against.
662
663 Returns
664 -------
665 usecols : iterable of usecols
666 The `usecols` parameter if the validation succeeds.
667
668 Raises
669 ------
670 ValueError : Columns were missing. Error message will list them.
671 """
672 missing = [c for c in usecols if c not in names]
673 if len(missing) > 0:
674 raise ValueError(
675 f"Usecols do not match columns, columns expected but not found: "
676 f"{missing}"
677 )
678
679 return usecols
680
681 @final
682 def _clean_index_names(self, columns, index_col) -> tuple[list | None, list, list]:
683 if not is_index_col(index_col):
684 return None, columns, index_col
685
686 columns = list(columns)
687
688 # In case of no rows and multiindex columns we have to set index_names to
689 # list of Nones GH#38292
690 if not columns:
691 return [None] * len(index_col), columns, index_col
692
693 cp_cols = list(columns)
694 index_names: list[str | int | None] = []
695
696 # don't mutate
697 index_col = list(index_col)
698
699 for i, c in enumerate(index_col):
700 if isinstance(c, str):
701 index_names.append(c)
702 for j, name in enumerate(cp_cols):
703 if name == c:
704 index_col[i] = j
705 columns.remove(name)
706 break
707 else:
708 name = cp_cols[c]
709 columns.remove(name)
710 index_names.append(name)
711
712 # Only clean index names that were placeholders.
713 for i, name in enumerate(index_names):
714 if isinstance(name, str) and name in self.unnamed_cols:
715 index_names[i] = None
716
717 return index_names, columns, index_col
718
719 @final
720 def _get_empty_meta(
721 self, columns: Sequence[HashableT], dtype: DtypeArg | None = None
722 ) -> tuple[Index, list[HashableT], dict[HashableT, Series]]:
723 columns = list(columns)
724
725 index_col = self.index_col
726 index_names = self.index_names
727
728 # Convert `dtype` to a defaultdict of some kind.
729 # This will enable us to write `dtype[col_name]`
730 # without worrying about KeyError issues later on.
731 dtype_dict: defaultdict[Hashable, Any]
732 if not is_dict_like(dtype):
733 # if dtype == None, default will be object.
734 dtype_dict = defaultdict(lambda: dtype)
735 else:
736 dtype = cast("dict", dtype)
737 dtype_dict = defaultdict(
738 lambda: None,
739 {columns[k] if is_integer(k) else k: v for k, v in dtype.items()},
740 )
741
742 # Even though we have no data, the "index" of the empty DataFrame
743 # could for example still be an empty MultiIndex. Thus, we need to
744 # check whether we have any index columns specified, via either:
745 #
746 # 1) index_col (column indices)
747 # 2) index_names (column names)
748 #
749 # Both must be non-null to ensure a successful construction. Otherwise,
750 # we have to create a generic empty Index.
751 index: Index
752 if (index_col is None or index_col is False) or index_names is None:
753 index = default_index(0)
754 else:
755 # TODO: We could return default_index(0) if dtype_dict[name] is None
756 data = [
757 Index([], name=name, dtype=dtype_dict[name]) for name in index_names
758 ]
759 if len(data) == 1:
760 index = data[0]
761 else:
762 index = MultiIndex.from_arrays(data)
763 index_col.sort()
764
765 for i, n in enumerate(index_col):
766 columns.pop(n - i)
767
768 col_dict = {
769 col_name: Series([], dtype=dtype_dict[col_name]) for col_name in columns
770 }
771
772 return index, columns, col_dict
773
774
775def date_converter(
776 date_col,
777 col: Hashable,
778 dayfirst: bool = False,
779 cache_dates: bool = True,
780 date_format: dict[Hashable, str] | str | None = None,
781):
782 if date_col.dtype.kind in "Mm":
783 return date_col
784
785 date_fmt = date_format.get(col) if isinstance(date_format, dict) else date_format
786
787 str_objs = lib.ensure_string_array(np.asarray(date_col))
788 try:
789 result = tools.to_datetime(
790 str_objs,
791 format=date_fmt,
792 utc=False,
793 dayfirst=dayfirst,
794 cache=cache_dates,
795 )
796 except (ValueError, TypeError):
797 # test_usecols_with_parse_dates4
798 # test_multi_index_parse_dates
799 return str_objs
800
801 if isinstance(result, DatetimeIndex):
802 arr = result.to_numpy()
803 arr.flags.writeable = True
804 return arr
805 return result._values
806
807
808parser_defaults = {
809 "delimiter": None,
810 "escapechar": None,
811 "quotechar": '"',
812 "quoting": csv.QUOTE_MINIMAL,
813 "doublequote": True,
814 "skipinitialspace": False,
815 "lineterminator": None,
816 "header": "infer",
817 "index_col": None,
818 "names": None,
819 "skiprows": None,
820 "skipfooter": 0,
821 "nrows": None,
822 "na_values": None,
823 "keep_default_na": True,
824 "true_values": None,
825 "false_values": None,
826 "converters": None,
827 "dtype": None,
828 "cache_dates": True,
829 "thousands": None,
830 "comment": None,
831 "decimal": ".",
832 # 'engine': 'c',
833 "parse_dates": False,
834 "dayfirst": False,
835 "date_format": None,
836 "usecols": None,
837 # 'iterator': False,
838 "chunksize": None,
839 "encoding": None,
840 "compression": None,
841 "skip_blank_lines": True,
842 "encoding_errors": "strict",
843 "on_bad_lines": ParserBase.BadLineHandleMethod.BLHM_ERROR,
844 "dtype_backend": lib.no_default,
845}
846
847
848def get_na_values(col, na_values, na_fvalues, keep_default_na: bool):
849 """
850 Get the NaN values for a given column.
851
852 Parameters
853 ----------
854 col : str
855 The name of the column.
856 na_values : array-like, dict
857 The object listing the NaN values as strings.
858 na_fvalues : array-like, dict
859 The object listing the NaN values as floats.
860 keep_default_na : bool
861 If `na_values` is a dict, and the column is not mapped in the
862 dictionary, whether to return the default NaN values or the empty set.
863
864 Returns
865 -------
866 nan_tuple : A length-two tuple composed of
867
868 1) na_values : the string NaN values for that column.
869 2) na_fvalues : the float NaN values for that column.
870 """
871 if isinstance(na_values, dict):
872 if col in na_values:
873 return na_values[col], na_fvalues[col]
874 else:
875 if keep_default_na:
876 return STR_NA_VALUES, set()
877
878 return set(), set()
879 else:
880 return na_values, na_fvalues
881
882
883def is_index_col(col) -> bool:
884 return col is not None and col is not False
885
886
887def validate_parse_dates_presence(
888 parse_dates: bool | list, columns: Sequence[Hashable]
889) -> set:
890 """
891 Check if parse_dates are in columns.
892
893 If user has provided names for parse_dates, check if those columns
894 are available.
895
896 Parameters
897 ----------
898 columns : list
899 List of names of the dataframe.
900
901 Returns
902 -------
903 The names of the columns which will get parsed later if a list
904 is given as specification.
905
906 Raises
907 ------
908 ValueError
909 If column to parse_date is not in dataframe.
910
911 """
912 if not isinstance(parse_dates, list):
913 return set()
914
915 missing = set()
916 unique_cols = set()
917 for col in parse_dates:
918 if isinstance(col, str):
919 if col not in columns:
920 missing.add(col)
921 else:
922 unique_cols.add(col)
923 elif col in columns:
924 unique_cols.add(col)
925 else:
926 unique_cols.add(columns[col])
927 if missing:
928 missing_cols = ", ".join(sorted(missing))
929 raise ValueError(f"Missing column provided to 'parse_dates': '{missing_cols}'")
930 return unique_cols
931
932
933def _validate_usecols_arg(usecols):
934 """
935 Validate the 'usecols' parameter.
936
937 Checks whether or not the 'usecols' parameter contains all integers
938 (column selection by index), strings (column by name) or is a callable.
939 Raises a ValueError if that is not the case.
940
941 Parameters
942 ----------
943 usecols : list-like, callable, or None
944 List of columns to use when parsing or a callable that can be used
945 to filter a list of table columns.
946
947 Returns
948 -------
949 usecols_tuple : tuple
950 A tuple of (verified_usecols, usecols_dtype).
951
952 'verified_usecols' is either a set if an array-like is passed in or
953 'usecols' if a callable or None is passed in.
954
955 'usecols_dtype` is the inferred dtype of 'usecols' if an array-like
956 is passed in or None if a callable or None is passed in.
957 """
958 msg = (
959 "'usecols' must either be list-like of all strings, all unicode, "
960 "all integers or a callable."
961 )
962 if usecols is not None:
963 if callable(usecols):
964 return usecols, None
965
966 if not is_list_like(usecols):
967 # see gh-20529
968 #
969 # Ensure it is iterable container but not string.
970 raise ValueError(msg)
971
972 usecols_dtype = lib.infer_dtype(usecols, skipna=False)
973
974 if usecols_dtype not in ("empty", "integer", "string"):
975 raise ValueError(msg)
976
977 usecols = set(usecols)
978
979 return usecols, usecols_dtype
980 return usecols, None
981
982
983@overload
984def evaluate_callable_usecols(
985 usecols: Callable[[Hashable], object],
986 names: Iterable[Hashable],
987) -> set[int]: ...
988
989
990@overload
991def evaluate_callable_usecols(
992 usecols: SequenceT, names: Iterable[Hashable]
993) -> SequenceT: ...
994
995
996def evaluate_callable_usecols(
997 usecols: Callable[[Hashable], object] | SequenceT,
998 names: Iterable[Hashable],
999) -> SequenceT | set[int]:
1000 """
1001 Check whether or not the 'usecols' parameter
1002 is a callable. If so, enumerates the 'names'
1003 parameter and returns a set of indices for
1004 each entry in 'names' that evaluates to True.
1005 If not a callable, returns 'usecols'.
1006 """
1007 if callable(usecols):
1008 return {i for i, name in enumerate(names) if usecols(name)}
1009 return usecols