Coverage for bzfs_main/check_range.py: 100%

32 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-07 04:44 +0000

1# class CheckRange is copied from https://gist.github.com/dmitriykovalev/2ab1aa33a8099ef2d514925d84aa89e7/30961300d3f8192f775709c06ff9a5b777475adf 

2# Written by Dmitriy Kovalev 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); 

5# you may not use this file except in compliance with the License. 

6# You may obtain a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, 

12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15# 

16"""Allows you to validate open, closed, and half-open intervals on int as well as float arguments. 

17 

18Each endpoint can be either a number or positive or negative infinity: 

19[a, b] --> min=a, max=b 

20[a, b) --> min=a, sup=b 

21(a, b] --> inf=a, max=b 

22(a, b) --> inf=a, sup=b 

23[a, +infinity) --> min=a 

24(a, +infinity) --> inf=a 

25(-infinity, b] --> max=b 

26(-infinity, b) --> sup=b 

27""" 

28 

29from __future__ import ( 

30 annotations, 

31) 

32import argparse 

33import operator 

34from typing import ( 

35 Any, 

36 Final, 

37) 

38 

39 

40# fmt: off 

41class CheckRange(argparse.Action): 

42 """Argparse action validating numeric ranges like ``(a,b]`` or ``[a,+∞)``.""" 

43 ops: Final = { 

44 'inf': operator.gt, 

45 'min': operator.ge, 

46 'sup': operator.lt, 

47 'max': operator.le, 

48 } 

49 

50 def __init__(self, *args: Any, **kwargs: Any) -> None: 

51 if 'min' in kwargs and 'inf' in kwargs: 

52 raise ValueError('either min or inf, but not both') 

53 if 'max' in kwargs and 'sup' in kwargs: 

54 raise ValueError('either max or sup, but not both') 

55 

56 for name in self.ops: 

57 if name in kwargs: 

58 setattr(self, name, kwargs.pop(name)) 

59 

60 super().__init__(*args, **kwargs) 

61 

62 def interval(self) -> str: 

63 """Returns a human-readable description of the allowed range.""" 

64 if hasattr(self, 'min'): 

65 lo = f'[{self.min}' 

66 elif hasattr(self, 'inf'): 

67 lo = f'({self.inf}' 

68 else: 

69 lo = '(-infinity' 

70 

71 if hasattr(self, 'max'): 

72 up = f'{self.max}]' 

73 elif hasattr(self, 'sup'): 

74 up = f'{self.sup})' 

75 else: 

76 up = '+infinity)' 

77 

78 return f'valid range: {lo}, {up}' 

79 

80 def __call__( 

81 self, 

82 parser: argparse.ArgumentParser, 

83 namespace: argparse.Namespace, 

84 values: Any, 

85 option_string: str | None = None, 

86 ) -> None: 

87 """Validate ``values`` against the configured interval and store result.""" 

88 for name, op in self.ops.items(): 

89 if hasattr(self, name) and not op(values, getattr(self, name)): 

90 raise argparse.ArgumentError(self, self.interval()) 

91 setattr(namespace, self.dest, values) 

92# fmt: on