Skip to content

Instantly share code, notes, and snippets.

@wrouesnel
Created February 10, 2025 22:56
Show Gist options
  • Save wrouesnel/84d2b3c2e122fc261ff7d99fc00172ac to your computer and use it in GitHub Desktop.
Save wrouesnel/84d2b3c2e122fc261ff7d99fc00172ac to your computer and use it in GitHub Desktop.
Extended click parameter types
import csv
import typing
import typing as t
import click
from furl import furl
from timelength.timelength import TimeLength
class TimelengthType(click.ParamType):
name = "timelength"
def convert(
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
) -> t.Any:
return TimeLength(value)
class URLParamType(click.ParamType):
name = "url"
def convert(
self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
) -> t.Any:
return furl(value)
class DictParamType(click.ParamType):
"""Represents the dictionary type of a CLI parameter.
Validates and converts values from the command line string or Python into
a Python dict.
- All key-value pairs must be separated by one semicolon.
- Key and value must be separated by one equal sign.
- Converts sequences separeted by dots into a list: list value items
must be separated by commas.
- Converts numbers to int.
Usage:
>>> @click.option("--param", default=None, type=DictParamType())
... def command(param):
... ...
CLI: command --param='page=1; name=Items; rules=1, 2, three; extra=A,;'
Example:
>>> param_value = 'page=1; name=Items; rules=1, 2, three; extra=A,;'
>>> DictParamType().convert(param_value, None, None)
{'page': 1, 'name': 'Items', 'rules': [1, 2, 'three'], 'extra': ['A']}`
"""
name = "dictionary"
def convert(self, cli_value, param, ctx):
"""Converts CLI value to the dictionary structure.
Args:
cli_value (Any): The value to convert.
param (click.Parameter | None): The parameter that is using this
type to convert its value.
ctx (click.Context | None): The current context that arrived
at this value.
Returns:
dict: The validated and converted dictionary.
Raises:
click.BadParameter: If the validation is failed.
"""
if isinstance(cli_value, dict):
return cli_value
try:
keyvalue_pairs = cli_value.rstrip(";").split(";")
result_dict = {}
for pair in keyvalue_pairs:
key, values = [item.strip() for item in pair.split("=")]
converted_values = []
for value in values.split(","):
value = value.strip()
if value.isdigit():
value = int(value)
converted_values.append(value)
if len(converted_values) == 1:
result_dict[key] = converted_values[0]
elif len(converted_values) > 1 and converted_values[-1] == "":
result_dict[key] = converted_values[:-1]
else:
result_dict[key] = converted_values
return result_dict
except ValueError:
self.fail(
"All key-value pairs must be separated by one semicolon. "
"Key and value must be separated by one equal sign. "
"List value items must be separated by one comma. "
f"Key-value: {pair}.",
param,
ctx,
)
class ListParamType(click.ParamType):
"""
Represents the list type of a CLI parameter.
Validates and converts values from the command line string or Python into
a Python list.
- All values must be separated by one colon.
Usage
-----
@click.option("--param", default=None, type=ListParamType())
def command(param):
...
CLI: command --param='value1,value2,value3'
Example
-------
>>> param_value = 'value1,value2,value3'
>>> ListParamType().convert(param_value, None, None)
['value1', 'value2', 'value3']
>>> param_value = 'value1,"value,"'
>>> ListParamType().convert(param_value, None, None)
['value1', 'value,']
"""
name = "list"
def convert(self, cli_value, param, ctx):
"""Converts CLI value to the list structure.
Args:
cli_value (Any): The value to convert.
param (click.Parameter | None): The parameter that is using this
type to convert its value.
ctx (click.Context | None): The current context that arrived
at this value.
Returns:
list: The validated and converted list.
Raises:
click.BadParameter: If the validation is failed.
"""
if isinstance(cli_value, typing.Sequence) and not isinstance(
cli_value, (str, bytes)
):
return cli_value
# Use the CSV reader to parse - this gives us handling for commented values.
row = next(csv.reader([cli_value]))
return row
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment