Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/util/_validators.py: 23%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

127 statements  

1""" 

2Module that contains many useful utilities 

3for validating data or function arguments 

4""" 

5 

6from __future__ import annotations 

7 

8from collections.abc import ( 

9 Iterable, 

10 Sequence, 

11) 

12from typing import ( 

13 TypeVar, 

14 overload, 

15) 

16 

17import numpy as np 

18 

19from pandas._libs import lib 

20from pandas._libs.missing import NA 

21 

22from pandas.core.dtypes.common import ( 

23 is_bool, 

24 is_integer, 

25) 

26 

27BoolishT = TypeVar("BoolishT", bool, int) 

28BoolishNoneT = TypeVar("BoolishNoneT", bool, int, None) 

29 

30 

31def _check_arg_length(fname, args, max_fname_arg_count, compat_args) -> None: 

32 """ 

33 Checks whether 'args' has length of at most 'compat_args'. Raises 

34 a TypeError if that is not the case, similar to in Python when a 

35 function is called with too many arguments. 

36 """ 

37 if max_fname_arg_count < 0: 

38 raise ValueError("'max_fname_arg_count' must be non-negative") 

39 

40 if len(args) > len(compat_args): 

41 max_arg_count = len(compat_args) + max_fname_arg_count 

42 actual_arg_count = len(args) + max_fname_arg_count 

43 argument = "argument" if max_arg_count == 1 else "arguments" 

44 

45 raise TypeError( 

46 f"{fname}() takes at most {max_arg_count} {argument} " 

47 f"({actual_arg_count} given)" 

48 ) 

49 

50 

51def _check_for_default_values(fname, arg_val_dict, compat_args) -> None: 

52 """ 

53 Check that the keys in `arg_val_dict` are mapped to their 

54 default values as specified in `compat_args`. 

55 

56 Note that this function is to be called only when it has been 

57 checked that arg_val_dict.keys() is a subset of compat_args 

58 """ 

59 for key in arg_val_dict: 

60 # try checking equality directly with '=' operator, 

61 # as comparison may have been overridden for the left 

62 # hand object 

63 try: 

64 v1 = arg_val_dict[key] 

65 v2 = compat_args[key] 

66 

67 # check for None-ness otherwise we could end up 

68 # comparing a numpy array vs None 

69 if (v1 is not None and v2 is None) or (v1 is None and v2 is not None): 

70 match = False 

71 else: 

72 match = v1 == v2 

73 

74 if not is_bool(match): 

75 raise ValueError("'match' is not a boolean") 

76 

77 # could not compare them directly, so try comparison 

78 # using the 'is' operator 

79 except ValueError: 

80 match = arg_val_dict[key] is compat_args[key] 

81 

82 if not match: 

83 raise ValueError( 

84 f"the '{key}' parameter is not supported in " 

85 f"the pandas implementation of {fname}()" 

86 ) 

87 

88 

89def validate_args(fname, args, max_fname_arg_count, compat_args) -> None: 

90 """ 

91 Checks whether the length of the `*args` argument passed into a function 

92 has at most `len(compat_args)` arguments and whether or not all of these 

93 elements in `args` are set to their default values. 

94 

95 Parameters 

96 ---------- 

97 fname : str 

98 The name of the function being passed the `*args` parameter 

99 args : tuple 

100 The `*args` parameter passed into a function 

101 max_fname_arg_count : int 

102 The maximum number of arguments that the function `fname` 

103 can accept, excluding those in `args`. Used for displaying 

104 appropriate error messages. Must be non-negative. 

105 compat_args : dict 

106 A dictionary of keys and their associated default values. 

107 In order to accommodate buggy behaviour in some versions of `numpy`, 

108 where a signature displayed keyword arguments but then passed those 

109 arguments **positionally** internally when calling downstream 

110 implementations, a dict ensures that the original 

111 order of the keyword arguments is enforced. 

112 

113 Raises 

114 ------ 

115 TypeError 

116 If `args` contains more values than there are `compat_args` 

117 ValueError 

118 If `args` contains values that do not correspond to those 

119 of the default values specified in `compat_args` 

120 """ 

121 _check_arg_length(fname, args, max_fname_arg_count, compat_args) 

122 

123 # We do this so that we can provide a more informative 

124 # error message about the parameters that we are not 

125 # supporting in the pandas implementation of 'fname' 

126 kwargs = dict(zip(compat_args, args, strict=False)) 

127 _check_for_default_values(fname, kwargs, compat_args) 

128 

129 

130def _check_for_invalid_keys(fname, kwargs, compat_args) -> None: 

131 """ 

132 Checks whether 'kwargs' contains any keys that are not 

133 in 'compat_args' and raises a TypeError if there is one. 

134 """ 

135 # set(dict) --> set of the dictionary's keys 

136 diff = set(kwargs) - set(compat_args) 

137 

138 if diff: 

139 bad_arg = next(iter(diff)) 

140 raise TypeError(f"{fname}() got an unexpected keyword argument '{bad_arg}'") 

141 

142 

143def validate_kwargs(fname, kwargs, compat_args) -> None: 

144 """ 

145 Checks whether parameters passed to the **kwargs argument in a 

146 function `fname` are valid parameters as specified in `*compat_args` 

147 and whether or not they are set to their default values. 

148 

149 Parameters 

150 ---------- 

151 fname : str 

152 The name of the function being passed the `**kwargs` parameter 

153 kwargs : dict 

154 The `**kwargs` parameter passed into `fname` 

155 compat_args: dict 

156 A dictionary of keys that `kwargs` is allowed to have and their 

157 associated default values 

158 

159 Raises 

160 ------ 

161 TypeError if `kwargs` contains keys not in `compat_args` 

162 ValueError if `kwargs` contains keys in `compat_args` that do not 

163 map to the default values specified in `compat_args` 

164 """ 

165 kwds = kwargs.copy() 

166 _check_for_invalid_keys(fname, kwargs, compat_args) 

167 _check_for_default_values(fname, kwds, compat_args) 

168 

169 

170def validate_args_and_kwargs( 

171 fname, args, kwargs, max_fname_arg_count, compat_args 

172) -> None: 

173 """ 

174 Checks whether parameters passed to the *args and **kwargs argument in a 

175 function `fname` are valid parameters as specified in `*compat_args` 

176 and whether or not they are set to their default values. 

177 

178 Parameters 

179 ---------- 

180 fname: str 

181 The name of the function being passed the `**kwargs` parameter 

182 args: tuple 

183 The `*args` parameter passed into a function 

184 kwargs: dict 

185 The `**kwargs` parameter passed into `fname` 

186 max_fname_arg_count: int 

187 The minimum number of arguments that the function `fname` 

188 requires, excluding those in `args`. Used for displaying 

189 appropriate error messages. Must be non-negative. 

190 compat_args: dict 

191 A dictionary of keys that `kwargs` is allowed to 

192 have and their associated default values. 

193 

194 Raises 

195 ------ 

196 TypeError if `args` contains more values than there are 

197 `compat_args` OR `kwargs` contains keys not in `compat_args` 

198 ValueError if `args` contains values not at the default value (`None`) 

199 `kwargs` contains keys in `compat_args` that do not map to the default 

200 value as specified in `compat_args` 

201 

202 See Also 

203 -------- 

204 validate_args : Purely args validation. 

205 validate_kwargs : Purely kwargs validation. 

206 

207 """ 

208 # Check that the total number of arguments passed in (i.e. 

209 # args and kwargs) does not exceed the length of compat_args 

210 _check_arg_length( 

211 fname, args + tuple(kwargs.values()), max_fname_arg_count, compat_args 

212 ) 

213 

214 # Check there is no overlap with the positional and keyword 

215 # arguments, similar to what is done in actual Python functions 

216 args_dict = dict(zip(compat_args, args, strict=False)) 

217 

218 for key in args_dict: 

219 if key in kwargs: 

220 raise TypeError( 

221 f"{fname}() got multiple values for keyword argument '{key}'" 

222 ) 

223 

224 kwargs.update(args_dict) 

225 validate_kwargs(fname, kwargs, compat_args) 

226 

227 

228def validate_bool_kwarg( 

229 value: BoolishNoneT, 

230 arg_name: str, 

231 none_allowed: bool = True, 

232 int_allowed: bool = False, 

233) -> BoolishNoneT: 

234 """ 

235 Ensure that argument passed in arg_name can be interpreted as boolean. 

236 

237 Parameters 

238 ---------- 

239 value : bool 

240 Value to be validated. 

241 arg_name : str 

242 Name of the argument. To be reflected in the error message. 

243 none_allowed : bool, default True 

244 Whether to consider None to be a valid boolean. 

245 int_allowed : bool, default False 

246 Whether to consider integer value to be a valid boolean. 

247 

248 Returns 

249 ------- 

250 value 

251 The same value as input. 

252 

253 Raises 

254 ------ 

255 ValueError 

256 If the value is not a valid boolean. 

257 """ 

258 good_value = is_bool(value) 

259 if none_allowed: 

260 good_value = good_value or (value is None) 

261 

262 if int_allowed: 

263 good_value = good_value or isinstance(value, int) 

264 

265 if not good_value: 

266 raise ValueError( 

267 f'For argument "{arg_name}" expected type bool, received ' 

268 f"type {type(value).__name__}." 

269 ) 

270 return value 

271 

272 

273def validate_na_arg(value, name: str): 

274 """ 

275 Validate na arguments. 

276 

277 Parameters 

278 ---------- 

279 value : object 

280 Value to validate. 

281 name : str 

282 Name of the argument, used to raise an informative error message. 

283 

284 Raises 

285 ______ 

286 ValueError 

287 When ``value`` is determined to be invalid. 

288 """ 

289 if ( 

290 value is lib.no_default 

291 or isinstance(value, bool) 

292 or value is None 

293 or value is NA 

294 or (lib.is_float(value) and np.isnan(value)) 

295 ): 

296 return 

297 raise ValueError(f"{name} must be None, pd.NA, np.nan, True, or False; got {value}") 

298 

299 

300def validate_fillna_kwargs(value, method, validate_scalar_dict_value: bool = True): 

301 """ 

302 Validate the keyword arguments to 'fillna'. 

303 

304 This checks that exactly one of 'value' and 'method' is specified. 

305 If 'method' is specified, this validates that it's a valid method. 

306 

307 Parameters 

308 ---------- 

309 value, method : object 

310 The 'value' and 'method' keyword arguments for 'fillna'. 

311 validate_scalar_dict_value : bool, default True 

312 Whether to validate that 'value' is a scalar or dict. Specifically, 

313 validate that it is not a list or tuple. 

314 

315 Returns 

316 ------- 

317 value, method : object 

318 """ 

319 from pandas.core.missing import clean_fill_method 

320 

321 if value is None and method is None: 

322 raise ValueError("Must specify a fill 'value' or 'method'.") 

323 if value is None and method is not None: 

324 method = clean_fill_method(method) 

325 

326 elif value is not None and method is None: 

327 if validate_scalar_dict_value and isinstance(value, (list, tuple)): 

328 raise TypeError( 

329 '"value" parameter must be a scalar or dict, but ' 

330 f'you passed a "{type(value).__name__}"' 

331 ) 

332 

333 elif value is not None and method is not None: 

334 raise ValueError("Cannot specify both 'value' and 'method'.") 

335 

336 return value, method 

337 

338 

339def validate_percentile(q: float | Iterable[float]) -> np.ndarray: 

340 """ 

341 Validate percentiles (used by describe and quantile). 

342 

343 This function checks if the given float or iterable of floats is a valid percentile 

344 otherwise raises a ValueError. 

345 

346 Parameters 

347 ---------- 

348 q: float or iterable of floats 

349 A single percentile or an iterable of percentiles. 

350 

351 Returns 

352 ------- 

353 ndarray 

354 An ndarray of the percentiles if valid. 

355 

356 Raises 

357 ------ 

358 ValueError if percentiles are not in given interval([0, 1]). 

359 """ 

360 q_arr = np.asarray(q) 

361 # Don't change this to an f-string. The string formatting 

362 # is too expensive for cases where we don't need it. 

363 msg = "percentiles should all be in the interval [0, 1]" 

364 if q_arr.ndim == 0: 

365 if not 0 <= q_arr <= 1: 

366 raise ValueError(msg) 

367 elif not all(0 <= qs <= 1 for qs in q_arr): 

368 raise ValueError(msg) 

369 return q_arr 

370 

371 

372@overload 

373def validate_ascending(ascending: BoolishT) -> BoolishT: ... 

374 

375 

376@overload 

377def validate_ascending(ascending: Sequence[BoolishT]) -> list[BoolishT]: ... 

378 

379 

380def validate_ascending( 

381 ascending: bool | int | Sequence[BoolishT], 

382) -> bool | int | list[BoolishT]: 

383 """Validate ``ascending`` kwargs for ``sort_index`` method.""" 

384 kwargs = {"none_allowed": False, "int_allowed": True} 

385 if not isinstance(ascending, Sequence): 

386 return validate_bool_kwarg(ascending, "ascending", **kwargs) 

387 

388 return [validate_bool_kwarg(item, "ascending", **kwargs) for item in ascending] 

389 

390 

391def validate_endpoints(closed: str | None) -> tuple[bool, bool]: 

392 """ 

393 Check that the `closed` argument is among [None, "left", "right"] 

394 

395 Parameters 

396 ---------- 

397 closed : {None, "left", "right"} 

398 

399 Returns 

400 ------- 

401 left_closed : bool 

402 right_closed : bool 

403 

404 Raises 

405 ------ 

406 ValueError : if argument is not among valid values 

407 """ 

408 left_closed = False 

409 right_closed = False 

410 

411 if closed is None: 

412 left_closed = True 

413 right_closed = True 

414 elif closed == "left": 

415 left_closed = True 

416 elif closed == "right": 

417 right_closed = True 

418 else: 

419 raise ValueError("Closed has to be either 'left', 'right' or None") 

420 

421 return left_closed, right_closed 

422 

423 

424def validate_inclusive(inclusive: str | None) -> tuple[bool, bool]: 

425 """ 

426 Check that the `inclusive` argument is among {"both", "neither", "left", "right"}. 

427 

428 Parameters 

429 ---------- 

430 inclusive : {"both", "neither", "left", "right"} 

431 

432 Returns 

433 ------- 

434 left_right_inclusive : tuple[bool, bool] 

435 

436 Raises 

437 ------ 

438 ValueError : if argument is not among valid values 

439 """ 

440 left_right_inclusive: tuple[bool, bool] | None = None 

441 

442 if isinstance(inclusive, str): 

443 left_right_inclusive = { 

444 "both": (True, True), 

445 "left": (True, False), 

446 "right": (False, True), 

447 "neither": (False, False), 

448 }.get(inclusive) 

449 

450 if left_right_inclusive is None: 

451 raise ValueError( 

452 "Inclusive has to be either 'both', 'neither', 'left' or 'right'" 

453 ) 

454 

455 return left_right_inclusive 

456 

457 

458def validate_insert_loc(loc: int, length: int) -> int: 

459 """ 

460 Check that we have an integer between -length and length, inclusive. 

461 

462 Standardize negative loc to within [0, length]. 

463 

464 The exceptions we raise on failure match np.insert. 

465 """ 

466 if not is_integer(loc): 

467 raise TypeError(f"loc must be an integer between -{length} and {length}") 

468 

469 if loc < 0: 

470 loc += length 

471 if not 0 <= loc <= length: 

472 raise IndexError(f"loc must be an integer between -{length} and {length}") 

473 return loc # pyright: ignore[reportReturnType] 

474 

475 

476def check_dtype_backend(dtype_backend) -> None: 

477 if dtype_backend is not lib.no_default: 

478 if dtype_backend not in ["numpy_nullable", "pyarrow"]: 

479 raise ValueError( 

480 f"dtype_backend {dtype_backend} is invalid, only 'numpy_nullable' and " 

481 f"'pyarrow' are allowed.", 

482 )