Skip to content

Commit cdea8fc

Browse files
committed
Merge pull request #538 from DenisCarriere/master
Add Float Range in Types
2 parents dac66dc + 3c0b985 commit cdea8fc

File tree

5 files changed

+92
-16
lines changed

5 files changed

+92
-16
lines changed

CHANGES

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Version 7.0
1010

1111
- The exception objects now store unicode properly.
1212
- Added the ability to hide commands and options from help.
13+
- Added Float Range in Types.
1314

1415
Version 6.5
1516
-----------

click/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
# Types
3030
from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
31-
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED
31+
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange
3232

3333
# Utilities
3434
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
@@ -66,7 +66,7 @@
6666

6767
# Types
6868
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING',
69-
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED',
69+
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', 'FloatRange'
7070

7171
# Utilities
7272
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',

click/types.py

+53-14
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,59 @@ def __repr__(self):
214214
return 'IntRange(%r, %r)' % (self.min, self.max)
215215

216216

217+
class FloatParamType(ParamType):
218+
name = 'float'
219+
220+
def convert(self, value, param, ctx):
221+
try:
222+
return float(value)
223+
except (UnicodeError, ValueError):
224+
self.fail('%s is not a valid floating point value' %
225+
value, param, ctx)
226+
227+
def __repr__(self):
228+
return 'FLOAT'
229+
230+
231+
class FloatRange(FloatParamType):
232+
"""A parameter that works similar to :data:`click.FLOAT` but restricts
233+
the value to fit into a range. The default behavior is to fail if the
234+
value falls outside the range, but it can also be silently clamped
235+
between the two edges.
236+
237+
See :ref:`ranges` for an example.
238+
"""
239+
name = 'float range'
240+
241+
def __init__(self, min=None, max=None, clamp=False):
242+
self.min = min
243+
self.max = max
244+
self.clamp = clamp
245+
246+
def convert(self, value, param, ctx):
247+
rv = FloatParamType.convert(self, value, param, ctx)
248+
if self.clamp:
249+
if self.min is not None and rv < self.min:
250+
return self.min
251+
if self.max is not None and rv > self.max:
252+
return self.max
253+
if self.min is not None and rv < self.min or \
254+
self.max is not None and rv > self.max:
255+
if self.min is None:
256+
self.fail('%s is bigger than the maximum valid value '
257+
'%s.' % (rv, self.max), param, ctx)
258+
elif self.max is None:
259+
self.fail('%s is smaller than the minimum valid value '
260+
'%s.' % (rv, self.min), param, ctx)
261+
else:
262+
self.fail('%s is not in the valid range of %s to %s.'
263+
% (rv, self.min, self.max), param, ctx)
264+
return rv
265+
266+
def __repr__(self):
267+
return 'FloatRange(%r, %r)' % (self.min, self.max)
268+
269+
217270
class BoolParamType(ParamType):
218271
name = 'boolean'
219272

@@ -231,20 +284,6 @@ def __repr__(self):
231284
return 'BOOL'
232285

233286

234-
class FloatParamType(ParamType):
235-
name = 'float'
236-
237-
def convert(self, value, param, ctx):
238-
try:
239-
return float(value)
240-
except (UnicodeError, ValueError):
241-
self.fail('%s is not a valid floating point value' %
242-
value, param, ctx)
243-
244-
def __repr__(self):
245-
return 'FLOAT'
246-
247-
248287
class UUIDParameterType(ParamType):
249288
name = 'uuid'
250289

docs/parameters.rst

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ different behavior and some are supported out of the box:
6767
.. autoclass:: IntRange
6868
:noindex:
6969

70+
.. autoclass:: FloatRange
71+
:noindex:
72+
7073
Custom parameter types can be implemented by subclassing
7174
:class:`click.ParamType`. For simple cases, passing a Python function that
7275
fails with a `ValueError` is also supported, though discouraged.

tests/test_basic.py

+33
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,39 @@ def clamp(x):
343343
assert result.output == '0\n'
344344

345345

346+
def test_float_range_option(runner):
347+
@click.command()
348+
@click.option('--x', type=click.FloatRange(0, 5))
349+
def cli(x):
350+
click.echo(x)
351+
352+
result = runner.invoke(cli, ['--x=5.0'])
353+
assert not result.exception
354+
assert result.output == '5.0\n'
355+
356+
result = runner.invoke(cli, ['--x=6.0'])
357+
assert result.exit_code == 2
358+
assert 'Invalid value for "--x": 6.0 is not in the valid range of 0 to 5.\n' \
359+
in result.output
360+
361+
@click.command()
362+
@click.option('--x', type=click.FloatRange(0, 5, clamp=True))
363+
def clamp(x):
364+
click.echo(x)
365+
366+
result = runner.invoke(clamp, ['--x=5.0'])
367+
assert not result.exception
368+
assert result.output == '5.0\n'
369+
370+
result = runner.invoke(clamp, ['--x=6.0'])
371+
assert not result.exception
372+
assert result.output == '5\n'
373+
374+
result = runner.invoke(clamp, ['--x=-1.0'])
375+
assert not result.exception
376+
assert result.output == '0\n'
377+
378+
346379
def test_required_option(runner):
347380
@click.command()
348381
@click.option('--foo', required=True)

0 commit comments

Comments
 (0)