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

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

116 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 TYPE_CHECKING, 

14 Any, 

15 TypeVar, 

16 overload, 

17) 

18 

19import numpy as np 

20 

21from pandas._libs import lib 

22from pandas._libs.missing import NA 

23 

24from pandas.core.dtypes.common import ( 

25 is_bool, 

26 is_integer, 

27) 

28 

29if TYPE_CHECKING: 

30 from pandas._typing import DtypeBackend 

31 

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

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

34 

35 

36def _check_arg_length( 

37 fname: str | None, 

38 args: tuple[Any, ...], 

39 max_fname_arg_count: int, 

40 compat_args: dict[str, Any], 

41) -> None: 

42 """ 

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

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

45 function is called with too many arguments. 

46 """ 

47 if max_fname_arg_count < 0: 

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

49 

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

51 max_arg_count = len(compat_args) + max_fname_arg_count 

52 actual_arg_count = len(args) + max_fname_arg_count 

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

54 

55 raise TypeError( 

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

57 f"({actual_arg_count} given)" 

58 ) 

59 

60 

61def _check_for_default_values( 

62 fname: str | None, 

63 arg_val_dict: dict[str, Any], 

64 compat_args: dict[str, Any], 

65) -> None: 

66 """ 

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

68 default values as specified in `compat_args`. 

69 

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

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

72 """ 

73 for key, v1 in arg_val_dict.items(): 

74 v2 = compat_args[key] 

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

76 # as comparison may have been overridden for the left 

77 # hand object 

78 try: 

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

80 # comparing a numpy array vs None 

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

82 match = False 

83 else: 

84 match = v1 == v2 

85 

86 if not is_bool(match): 

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

88 

89 # could not compare them directly, so try comparison 

90 # using the 'is' operator 

91 except ValueError: 

92 match = v1 is v2 

93 

94 if not match: 

95 raise ValueError( 

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

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

98 ) 

99 

100 

101def validate_args( 

102 fname: str | None, 

103 args: tuple[Any, ...], 

104 max_fname_arg_count: int, 

105 compat_args: dict[str, Any], 

106) -> None: 

107 """ 

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

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

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

111 

112 Parameters 

113 ---------- 

114 fname : str 

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

116 args : tuple 

117 The `*args` parameter passed into a function 

118 max_fname_arg_count : int 

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

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

121 appropriate error messages. Must be non-negative. 

122 compat_args : dict 

123 A dictionary of keys and their associated default values. 

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

125 where a signature displayed keyword arguments but then passed those 

126 arguments **positionally** internally when calling downstream 

127 implementations, a dict ensures that the original 

128 order of the keyword arguments is enforced. 

129 

130 Raises 

131 ------ 

132 TypeError 

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

134 ValueError 

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

136 of the default values specified in `compat_args` 

137 """ 

138 _check_arg_length(fname, args, max_fname_arg_count, compat_args) 

139 

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

141 # error message about the parameters that we are not 

142 # supporting in the pandas implementation of 'fname' 

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

144 _check_for_default_values(fname, kwargs, compat_args) 

145 

146 

147def _check_for_invalid_keys( 

148 fname: str | None, 

149 kwargs: dict[str, Any], 

150 compat_args: dict[str, Any], 

151) -> None: 

152 """ 

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

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

155 """ 

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

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

158 

159 if diff: 

160 bad_arg = next(iter(diff)) 

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

162 

163 

164def validate_kwargs( 

165 fname: str | None, 

166 kwargs: dict[str, Any], 

167 compat_args: dict[str, Any], 

168) -> None: 

169 """ 

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

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

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

173 

174 Parameters 

175 ---------- 

176 fname : str 

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

178 kwargs : dict 

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

180 compat_args: dict 

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

182 associated default values 

183 

184 Raises 

185 ------ 

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

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

188 map to the default values specified in `compat_args` 

189 """ 

190 kwds = kwargs.copy() 

191 _check_for_invalid_keys(fname, kwargs, compat_args) 

192 _check_for_default_values(fname, kwds, compat_args) 

193 

194 

195def validate_args_and_kwargs( 

196 fname: str | None, 

197 args: tuple[Any, ...], 

198 kwargs: dict[str, Any], 

199 max_fname_arg_count: int, 

200 compat_args: dict[str, Any], 

201) -> None: 

202 """ 

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

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

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

206 

207 Parameters 

208 ---------- 

209 fname: str 

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

211 args: tuple 

212 The `*args` parameter passed into a function 

213 kwargs: dict 

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

215 max_fname_arg_count: int 

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

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

218 appropriate error messages. Must be non-negative. 

219 compat_args: dict 

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

221 have and their associated default values. 

222 

223 Raises 

224 ------ 

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

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

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

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

229 value as specified in `compat_args` 

230 

231 See Also 

232 -------- 

233 validate_args : Purely args validation. 

234 validate_kwargs : Purely kwargs validation. 

235 

236 """ 

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

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

239 _check_arg_length( 

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

241 ) 

242 

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

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

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

246 

247 for key in args_dict: 

248 if key in kwargs: 

249 raise TypeError( 

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

251 ) 

252 

253 kwargs.update(args_dict) 

254 validate_kwargs(fname, kwargs, compat_args) 

255 

256 

257def validate_bool_kwarg( 

258 value: BoolishNoneT, 

259 arg_name: str, 

260 none_allowed: bool = True, 

261 int_allowed: bool = False, 

262) -> BoolishNoneT: 

263 """ 

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

265 

266 Parameters 

267 ---------- 

268 value : bool 

269 Value to be validated. 

270 arg_name : str 

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

272 none_allowed : bool, default True 

273 Whether to consider None to be a valid boolean. 

274 int_allowed : bool, default False 

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

276 

277 Returns 

278 ------- 

279 value 

280 The same value as input. 

281 

282 Raises 

283 ------ 

284 ValueError 

285 If the value is not a valid boolean. 

286 """ 

287 good_value = is_bool(value) 

288 if none_allowed: 

289 good_value = good_value or (value is None) 

290 

291 if int_allowed: 

292 good_value = good_value or isinstance(value, int) 

293 

294 if not good_value: 

295 raise ValueError( 

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

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

298 ) 

299 return value 

300 

301 

302def validate_na_arg(value: object, name: str) -> None: 

303 """ 

304 Validate na arguments. 

305 

306 Parameters 

307 ---------- 

308 value : object 

309 Value to validate. 

310 name : str 

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

312 

313 Raises 

314 ------ 

315 ValueError 

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

317 """ 

318 if ( 

319 value is lib.no_default 

320 or isinstance(value, bool) 

321 or value is None 

322 or value is NA 

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

324 ): 

325 return 

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

327 

328 

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

330 """ 

331 Validate percentiles (used by describe and quantile). 

332 

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

334 otherwise raises a ValueError. 

335 

336 Parameters 

337 ---------- 

338 q: float or iterable of floats 

339 A single percentile or an iterable of percentiles. 

340 

341 Returns 

342 ------- 

343 ndarray 

344 An ndarray of the percentiles if valid. 

345 

346 Raises 

347 ------ 

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

349 """ 

350 q_arr = np.asarray(q) 

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

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

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

354 if q_arr.ndim == 0: 

355 if not 0 <= q_arr <= 1: 

356 raise ValueError(msg) 

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

358 raise ValueError(msg) 

359 return q_arr 

360 

361 

362@overload 

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

364 

365 

366@overload 

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

368 

369 

370def validate_ascending( 

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

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

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

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

375 if not isinstance(ascending, Sequence): 

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

377 

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

379 

380 

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

382 """ 

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

384 

385 Parameters 

386 ---------- 

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

388 

389 Returns 

390 ------- 

391 left_closed : bool 

392 right_closed : bool 

393 

394 Raises 

395 ------ 

396 ValueError : if argument is not among valid values 

397 """ 

398 left_closed = False 

399 right_closed = False 

400 

401 if closed is None: 

402 left_closed = True 

403 right_closed = True 

404 elif closed == "left": 

405 left_closed = True 

406 elif closed == "right": 

407 right_closed = True 

408 else: 

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

410 

411 return left_closed, right_closed 

412 

413 

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

415 """ 

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

417 

418 Parameters 

419 ---------- 

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

421 

422 Returns 

423 ------- 

424 left_right_inclusive : tuple[bool, bool] 

425 

426 Raises 

427 ------ 

428 ValueError : if argument is not among valid values 

429 """ 

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

431 

432 if isinstance(inclusive, str): 

433 left_right_inclusive = { 

434 "both": (True, True), 

435 "left": (True, False), 

436 "right": (False, True), 

437 "neither": (False, False), 

438 }.get(inclusive) 

439 

440 if left_right_inclusive is None: 

441 raise ValueError( 

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

443 ) 

444 

445 return left_right_inclusive 

446 

447 

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

449 """ 

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

451 

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

453 

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

455 """ 

456 if not is_integer(loc): 

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

458 

459 if loc < 0: 

460 loc += length 

461 if not 0 <= loc <= length: 

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

463 return loc # pyright: ignore[reportReturnType] 

464 

465 

466def check_dtype_backend(dtype_backend: DtypeBackend | lib.NoDefault) -> None: 

467 if dtype_backend is not lib.no_default: 

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

469 raise ValueError( 

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

471 f"'pyarrow' are allowed.", 

472 )