Skip to content

Commit

Permalink
Merge pull request #538 from DenisCarriere/master
Browse files Browse the repository at this point in the history
Add Float Range in Types
  • Loading branch information
untitaker committed Apr 5, 2016
2 parents dac66dc + 3c0b985 commit cdea8fc
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -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.5
-----------
Expand Down
4 changes: 2 additions & 2 deletions click/__init__.py
Expand Up @@ -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, \
Expand Down Expand Up @@ -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',
Expand Down
67 changes: 53 additions & 14 deletions click/types.py
Expand Up @@ -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'

Expand All @@ -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'

Expand Down
3 changes: 3 additions & 0 deletions docs/parameters.rst
Expand Up @@ -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.
Expand Down
33 changes: 33 additions & 0 deletions tests/test_basic.py
Expand Up @@ -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)
Expand Down

0 comments on commit cdea8fc

Please sign in to comment.