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
« 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.
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"""
29from __future__ import (
30 annotations,
31)
32import argparse
33import operator
34from typing import (
35 Any,
36 Final,
37)
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 }
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')
56 for name in self.ops:
57 if name in kwargs:
58 setattr(self, name, kwargs.pop(name))
60 super().__init__(*args, **kwargs)
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'
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)'
78 return f'valid range: {lo}, {up}'
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