From 2be592314dbdea448ca5f74a076ae44fc26cb7a0 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Wed, 16 Mar 2016 21:38:30 -0400 Subject: [PATCH 1/3] Add Float Range in Types --- click/__init__.py | 4 +-- click/types.py | 67 +++++++++++++++++++++++++++++++++++---------- docs/parameters.rst | 3 ++ tests/test_basic.py | 33 ++++++++++++++++++++++ 4 files changed, 91 insertions(+), 16 deletions(-) diff --git a/click/__init__.py b/click/__init__.py index 774d98822..58df50303 100644 --- a/click/__init__.py +++ b/click/__init__.py @@ -28,7 +28,7 @@ # Types from .types import ParamType, File, Path, Choice, IntRange, Tuple, \ - STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED + STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange # Utilities from .utils import echo, get_binary_stream, get_text_stream, open_file, \ @@ -66,7 +66,7 @@ # Types 'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING', - 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', + 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', 'FloatRange' # Utilities 'echo', 'get_binary_stream', 'get_text_stream', 'open_file', diff --git a/click/types.py b/click/types.py index 7a247471d..418b45a38 100644 --- a/click/types.py +++ b/click/types.py @@ -214,6 +214,59 @@ def __repr__(self): return 'IntRange(%r, %r)' % (self.min, self.max) +class FloatParamType(ParamType): + name = 'float' + + def convert(self, value, param, ctx): + try: + return float(value) + except (UnicodeError, ValueError): + self.fail('%s is not a valid floating point value' % + value, param, ctx) + + def __repr__(self): + return 'FLOAT' + + +class FloatRange(FloatParamType): + """A parameter that works similar to :data:`click.FLOAT` but restricts + the value to fit into a range. The default behavior is to fail if the + value falls outside the range, but it can also be silently clamped + between the two edges. + + See :ref:`ranges` for an example. + """ + name = 'float range' + + def __init__(self, min=None, max=None, clamp=False): + self.min = min + self.max = max + self.clamp = clamp + + def convert(self, value, param, ctx): + rv = FloatParamType.convert(self, value, param, ctx) + if self.clamp: + if self.min is not None and rv < self.min: + return self.min + if self.max is not None and rv > self.max: + return self.max + if self.min is not None and rv < self.min or \ + self.max is not None and rv > self.max: + if self.min is None: + self.fail('%s is bigger than the maximum valid value ' + '%s.' % (rv, self.max), param, ctx) + elif self.max is None: + self.fail('%s is smaller than the minimum valid value ' + '%s.' % (rv, self.min), param, ctx) + else: + self.fail('%s is not in the valid range of %s to %s.' + % (rv, self.min, self.max), param, ctx) + return rv + + def __repr__(self): + return 'FloatRange(%r, %r)' % (self.min, self.max) + + class BoolParamType(ParamType): name = 'boolean' @@ -231,20 +284,6 @@ def __repr__(self): return 'BOOL' -class FloatParamType(ParamType): - name = 'float' - - def convert(self, value, param, ctx): - try: - return float(value) - except (UnicodeError, ValueError): - self.fail('%s is not a valid floating point value' % - value, param, ctx) - - def __repr__(self): - return 'FLOAT' - - class UUIDParameterType(ParamType): name = 'uuid' diff --git a/docs/parameters.rst b/docs/parameters.rst index 920b9beb6..e545343d4 100644 --- a/docs/parameters.rst +++ b/docs/parameters.rst @@ -67,6 +67,9 @@ different behavior and some are supported out of the box: .. autoclass:: IntRange :noindex: +.. autoclass:: FloatRange + :noindex: + Custom parameter types can be implemented by subclassing :class:`click.ParamType`. For simple cases, passing a Python function that fails with a `ValueError` is also supported, though discouraged. diff --git a/tests/test_basic.py b/tests/test_basic.py index 421ba4108..ec4df7d21 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -343,6 +343,39 @@ def clamp(x): assert result.output == '0\n' +def test_float_range_option(runner): + @click.command() + @click.option('--x', type=click.FloatRange(0, 5)) + def cli(x): + click.echo(x) + + result = runner.invoke(cli, ['--x=5.0']) + assert not result.exception + assert result.output == '5.0\n' + + result = runner.invoke(cli, ['--x=6.0']) + assert result.exit_code == 2 + assert 'Invalid value for "--x": 6.0 is not in the valid range of 0 to 5.\n' \ + in result.output + + @click.command() + @click.option('--x', type=click.FloatRange(0, 5, clamp=True)) + def clamp(x): + click.echo(x) + + result = runner.invoke(clamp, ['--x=5.0']) + assert not result.exception + assert result.output == '5.0\n' + + result = runner.invoke(clamp, ['--x=6.0']) + assert not result.exception + assert result.output == '5\n' + + result = runner.invoke(clamp, ['--x=-1.0']) + assert not result.exception + assert result.output == '0\n' + + def test_required_option(runner): @click.command() @click.option('--foo', required=True) From d54c323f98bc52d7fc3bf04ab270e3f843c9dee2 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Mon, 4 Apr 2016 19:57:48 -0400 Subject: [PATCH 2/3] Added Float Range in Changelog --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 8b865b93c..1be56d6e9 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ Version 7.0 - The exception objects now store unicode properly. - Added the ability to hide commands and options from help. +- Added Float Range in Types Version 6.3 ----------- From 3c0b98514e5595834fb48c91892ea30faa73de25 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Mon, 4 Apr 2016 20:02:44 -0400 Subject: [PATCH 3/3] Added period to changelog --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1be56d6e9..93a6383ed 100644 --- a/CHANGES +++ b/CHANGES @@ -10,7 +10,7 @@ Version 7.0 - The exception objects now store unicode properly. - Added the ability to hide commands and options from help. -- Added Float Range in Types +- Added Float Range in Types. Version 6.3 -----------