From 611ff32ac3ee79d9f3f7d46054e48fa5c9f43c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20D=C3=B6rrer?= Date: Sun, 9 Mar 2025 22:09:54 +0100 Subject: [PATCH 1/2] tests mit dataset from docstrings --- tests/__init__.py | 62 +++++++++++++++++++++++++++++++++++++++++ tests/test_validator.py | 17 ++++++----- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..58be864 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,62 @@ +import inspect +import re +from typing import Generator, Tuple + +import validators + +import clicktypes + + +def docstrings() -> Generator[str, None, None]: + """docstrings from all clicktypes validators.""" + + yield from ( + inspect.getdoc(getattr(validators, validator)) + for validator in clicktypes.__all__ + if validator not in ("base58", "country_code") + ) + + +def testdata(doc: str) -> Generator[Tuple[str, str, int], None, None]: + + for match in re.findall( + r">>> (\w+)\(\'(.*?)\'\)$\s+# \w+: (.*?)$", + doc, + re.MULTILINE, + ): + yield (match[0], match[1], 0 if match[2] == "True" else 2) + + +def validator_data(): + for doc in docstrings(): + for validator, value, expected in testdata(doc): + yield (validator, value, {}, expected) + + for data, result in [ + ("cUSECaVvAiV3srWbFRvVPzm5YzcXJwPSwZfE7veYPHoXmR9h6YMQ", 0), + ("18KToMF5ckjXBYt2HAj77qsG3GPeej3PZn", 0), + ("n4FFXRNNEW1aA2WPscSuzHTCjzjs4TVE2Z", 0), + ("38XzQ9dPGb1uqbZsjPtUajp7omy8aefjqj", 0), + ("ThisIsAReallyLongStringThatIsDefinitelyNotBase58Encoded", 2), + ("abcABC!@#", 2), + ("InvalidBase58!", 2), + ]: + yield ("base58", data, {}, result) + + for data, kwarg, expected in [ + ("ISR", "auto", 0), + ("US", "alpha2", 0), + ("USA", "alpha3", 0), + ("840", "numeric", 0), + ("", "auto", 2), + ("123456", "auto", 2), + ("XY", "alpha2", 2), + ("PPP", "alpha3", 2), + ("123", "numeric", 2), + ("us", "auto", 2), + ("uSa", "auto", 2), + ("US ", "auto", 2), + ("U.S", "auto", 2), + ("1ND", "unknown", 2), + ]: + yield ("country_code", data, {"iso_format": kwarg}, expected) diff --git a/tests/test_validator.py b/tests/test_validator.py index 5392b5c..a1a323b 100644 --- a/tests/test_validator.py +++ b/tests/test_validator.py @@ -4,6 +4,8 @@ import clicktypes +from . import validator_data + def cli(validator: str, **kwargs): @click.command(help=validator) @@ -36,12 +38,13 @@ def test_none(validator): @pytest.mark.parametrize( - "validator,value", - [ - ("email", "fu@bar.com"), - ], + "validator,value,kwargs,expected", + sorted( + validator_data(), + ), ) -def test_cli(validator, value): +def test_dataset(validator, value, kwargs, expected): + runner = CliRunner() - result = runner.invoke(cli(validator), [value]) - assert result.exit_code == 0 + result = runner.invoke(cli(validator, **kwargs), [value]) + assert result.exit_code == expected From 760943e82534bcc8b28cf1bef60550840098b058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20D=C3=B6rrer?= Date: Tue, 11 Mar 2025 21:35:31 +0100 Subject: [PATCH 2/2] refactored package removed overloads for cleaner code --- .pre-commit-config.yaml | 25 +- README.md | 23 +- clicktypes/__init__.py | 627 +++++++++++++++++------------------ clicktypes/__main__.py | 59 ++-- docs/__main__.py | 1 - poetry.lock | 3 +- pyproject.toml | 11 +- templates/README.md.jinja2 | 81 +++++ templates/__init__.py.jinja2 | 28 +- tests/__init__.py | 2 +- 10 files changed, 485 insertions(+), 375 deletions(-) create mode 100644 templates/README.md.jinja2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d2a314..d83cfdb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,12 @@ repos: + - repo: local + hooks: + - id: clicktypes + name: render clicktypes/__init__.py + entry: poetry run python -m clicktypes + language: system + pass_filenames: false + always_run: true - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: @@ -45,11 +53,14 @@ repos: hooks: - id: flake8 args: ["--max-line-length", "88", "--ignore=E501"] - - repo: local + language_version: python3.8 + - repo: https://github.com/asottile/pyupgrade + rev: v3.19.1 hooks: - - id: clicktypes - name: render clicktypes/__init__.py - entry: poetry run python -m clicktypes - language: system - pass_filenames: false - always_run: true + - id: pyupgrade + args: ["--py38-plus"] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.14.1 + hooks: + - id: mypy + language_version: python3.8 diff --git a/README.md b/README.md index 9e41405..2d7e789 100644 --- a/README.md +++ b/README.md @@ -70,12 +70,12 @@ Additional `click` parameter types are built on top of the `validators` library, pip install click-validators ``` -to perform `validators.eth_address()` validation, you need to install the `eth-hash>=0.7.0` package. +for `clicktypes.eth_address()` validation, additional package `eth-hash[pycryptodome]>=0.7.0` is required. [![PyPI - eth-hash](https://img.shields.io/pypi/v/eth-hash?logo=pypi&logoColor=white&label=eth-hash[pycryptodome])](https://pypi.org/project/eth-hash/) ```cmd -pip install click-validators[crypto-eth-addresses] +pip install click-validators[eth] ``` ## Usage @@ -89,7 +89,7 @@ import clicktypes @click.command( - help=clicktypes.email.__doc__.split("\n", maxsplit=1)[0], + help="validate email address", ) @click.argument( "email", @@ -103,6 +103,23 @@ if __name__ == "__main__": main() ``` +### Example + +```cmd +$ main.py fu@bar.com + +valid email='fu@bar.com' +``` + +```cmd +$ main.py fu.bar.com + +Usage: main.py [OPTIONS] EMAIL +Try 'main.py --help' for help. + +Error: Invalid value for 'EMAIL': Invalid email='fu.bar.com'. +``` + ## Dependencies [![PyPI - click](https://img.shields.io/pypi/v/click?logo=pypi&logoColor=white&label=click)](https://pypi.org/project/click/) diff --git a/clicktypes/__init__.py b/clicktypes/__init__.py index 49d8d17..aa3b32b 100644 --- a/clicktypes/__init__.py +++ b/clicktypes/__init__.py @@ -2,7 +2,7 @@ .. include:: ../README.md """ -from typing import overload +from typing import Optional import click import validators @@ -10,519 +10,500 @@ from .decorator import click_validatortype -@overload def amex() -> click.ParamType: - """Return whether or not given value is a valid American Express card number.""" - ... + """ + Return whether or not given value is a valid American Express card number. + Returns a `click.ParamType` instance which wraps `validators.amex`. + """ + return click_validatortype(validators.amex)() -amex = click_validatortype(validators.amex) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.amex` function.""" - -@overload def base16() -> click.ParamType: - """Return whether or not given value is a valid base16 encoding.""" - ... - + """ + Return whether or not given value is a valid base16 encoding. -base16 = click_validatortype(validators.base16) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.base16` function.""" + Returns a `click.ParamType` instance which wraps `validators.base16`. + """ + return click_validatortype(validators.base16)() -@overload def base32() -> click.ParamType: - """Return whether or not given value is a valid base32 encoding.""" - ... + """ + Return whether or not given value is a valid base32 encoding. + Returns a `click.ParamType` instance which wraps `validators.base32`. + """ + return click_validatortype(validators.base32)() -base32 = click_validatortype(validators.base32) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.base32` function.""" - -@overload def base58() -> click.ParamType: - """Return whether or not given value is a valid base58 encoding.""" - ... - + """ + Return whether or not given value is a valid base58 encoding. -base58 = click_validatortype(validators.base58) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.base58` function.""" + Returns a `click.ParamType` instance which wraps `validators.base58`. + """ + return click_validatortype(validators.base58)() -@overload def base64() -> click.ParamType: - """Return whether or not given value is a valid base64 encoding.""" - ... + """ + Return whether or not given value is a valid base64 encoding. + Returns a `click.ParamType` instance which wraps `validators.base64`. + """ + return click_validatortype(validators.base64)() -base64 = click_validatortype(validators.base64) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.base64` function.""" - -@overload def bsc_address() -> click.ParamType: - """Return whether or not given value is a valid binance smart chain address.""" - ... - + """ + Return whether or not given value is a valid binance smart chain address. -bsc_address = click_validatortype(validators.bsc_address) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.bsc_address` function.""" + Returns a `click.ParamType` instance which wraps `validators.bsc_address`. + """ + return click_validatortype(validators.bsc_address)() -@overload def btc_address() -> click.ParamType: - """Return whether or not given value is a valid bitcoin address.""" - ... - + """ + Return whether or not given value is a valid bitcoin address. -btc_address = click_validatortype(validators.btc_address) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.btc_address` function.""" + Returns a `click.ParamType` instance which wraps `validators.btc_address`. + """ + return click_validatortype(validators.btc_address)() -@overload def calling_code() -> click.ParamType: - """Validates given calling code.""" - ... + """ + Validates given calling code. + Returns a `click.ParamType` instance which wraps `validators.calling_code`. + """ + return click_validatortype(validators.calling_code)() -calling_code = click_validatortype(validators.calling_code) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.calling_code` function.""" - -@overload def card_number() -> click.ParamType: - """Return whether or not given value is a valid generic card number.""" - ... - + """ + Return whether or not given value is a valid generic card number. -card_number = click_validatortype(validators.card_number) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.card_number` function.""" + Returns a `click.ParamType` instance which wraps `validators.card_number`. + """ + return click_validatortype(validators.card_number)() -@overload -def country_code(iso_format="auto", ignore_case=False) -> click.ParamType: - """Validates given country code.""" - ... - +def country_code( + *, + iso_format: str = "auto", + ignore_case: bool = False, +) -> click.ParamType: + """ + Validates given country code. -country_code = click_validatortype(validators.country_code) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.country_code` function.""" + Returns a `click.ParamType` instance which wraps `validators.country_code`. + """ + return click_validatortype(validators.country_code)(**locals()) -@overload def cron() -> click.ParamType: - """Return whether or not given value is a valid cron string.""" - ... + """ + Return whether or not given value is a valid cron string. + Returns a `click.ParamType` instance which wraps `validators.cron`. + """ + return click_validatortype(validators.cron)() -cron = click_validatortype(validators.cron) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.cron` function.""" - - -@overload -def currency(skip_symbols=True, ignore_case=False) -> click.ParamType: - """Validates given currency code.""" - ... +def currency( + *, + skip_symbols: bool = True, + ignore_case: bool = False, +) -> click.ParamType: + """ + Validates given currency code. -currency = click_validatortype(validators.currency) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.currency` function.""" + Returns a `click.ParamType` instance which wraps `validators.currency`. + """ + return click_validatortype(validators.currency)(**locals()) -@overload def cusip() -> click.ParamType: - """Return whether or not given value is a valid CUSIP.""" - ... + """ + Return whether or not given value is a valid CUSIP. + Returns a `click.ParamType` instance which wraps `validators.cusip`. + """ + return click_validatortype(validators.cusip)() -cusip = click_validatortype(validators.cusip) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.cusip` function.""" - -@overload def diners() -> click.ParamType: - """Return whether or not given value is a valid Diners Club card number.""" - ... - + """ + Return whether or not given value is a valid Diners Club card number. -diners = click_validatortype(validators.diners) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.diners` function.""" + Returns a `click.ParamType` instance which wraps `validators.diners`. + """ + return click_validatortype(validators.diners)() -@overload def discover() -> click.ParamType: - """Return whether or not given value is a valid Discover card number.""" - ... - - -discover = click_validatortype(validators.discover) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.discover` function.""" + """ + Return whether or not given value is a valid Discover card number. + Returns a `click.ParamType` instance which wraps `validators.discover`. + """ + return click_validatortype(validators.discover)() -@overload -def domain(consider_tld=False, rfc_1034=False, rfc_2782=False) -> click.ParamType: - """Return whether or not given value is a valid domain.""" - ... +def domain( + *, + consider_tld: bool = False, + rfc_1034: bool = False, + rfc_2782: bool = False, +) -> click.ParamType: + """ + Return whether or not given value is a valid domain. -domain = click_validatortype(validators.domain) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.domain` function.""" + Returns a `click.ParamType` instance which wraps `validators.domain`. + """ + return click_validatortype(validators.domain)(**locals()) -@overload def email( - ipv6_address=False, - ipv4_address=False, - simple_host=False, - rfc_1034=False, - rfc_2782=False, + *, + ipv6_address: bool = False, + ipv4_address: bool = False, + simple_host: bool = False, + rfc_1034: bool = False, + rfc_2782: bool = False, ) -> click.ParamType: - """Validate an email address.""" - ... - + """ + Validate an email address. -email = click_validatortype(validators.email) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.email` function.""" + Returns a `click.ParamType` instance which wraps `validators.email`. + """ + return click_validatortype(validators.email)(**locals()) -@overload def es_cif() -> click.ParamType: - """Validate a Spanish CIF.""" - ... - + """ + Validate a Spanish CIF. -es_cif = click_validatortype(validators.es_cif) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.es_cif` function.""" + Returns a `click.ParamType` instance which wraps `validators.es_cif`. + """ + return click_validatortype(validators.es_cif)() -@overload def es_doi() -> click.ParamType: - """Validate a Spanish DOI.""" - ... + """ + Validate a Spanish DOI. + Returns a `click.ParamType` instance which wraps `validators.es_doi`. + """ + return click_validatortype(validators.es_doi)() -es_doi = click_validatortype(validators.es_doi) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.es_doi` function.""" - -@overload def es_nie() -> click.ParamType: - """Validate a Spanish NIE.""" - ... - + """ + Validate a Spanish NIE. -es_nie = click_validatortype(validators.es_nie) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.es_nie` function.""" + Returns a `click.ParamType` instance which wraps `validators.es_nie`. + """ + return click_validatortype(validators.es_nie)() -@overload def es_nif() -> click.ParamType: - """Validate a Spanish NIF.""" - ... + """ + Validate a Spanish NIF. + Returns a `click.ParamType` instance which wraps `validators.es_nif`. + """ + return click_validatortype(validators.es_nif)() -es_nif = click_validatortype(validators.es_nif) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.es_nif` function.""" - -@overload def eth_address() -> click.ParamType: - """Return whether or not given value is a valid ethereum address.""" - ... - + """ + Return whether or not given value is a valid ethereum address. -eth_address = click_validatortype(validators.eth_address) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.eth_address` function.""" + Returns a `click.ParamType` instance which wraps `validators.eth_address`. + """ + return click_validatortype(validators.eth_address)() -@overload def fi_business_id() -> click.ParamType: - """Validate a Finnish Business ID.""" - ... + """ + Validate a Finnish Business ID. + Returns a `click.ParamType` instance which wraps `validators.fi_business_id`. + """ + return click_validatortype(validators.fi_business_id)() -fi_business_id = click_validatortype(validators.fi_business_id) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.fi_business_id` function.""" - - -@overload -def fi_ssn(allow_temporal_ssn=True) -> click.ParamType: - """Validate a Finnish Social Security Number.""" - ... +def fi_ssn( + *, + allow_temporal_ssn: bool = True, +) -> click.ParamType: + """ + Validate a Finnish Social Security Number. -fi_ssn = click_validatortype(validators.fi_ssn) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.fi_ssn` function.""" + Returns a `click.ParamType` instance which wraps `validators.fi_ssn`. + """ + return click_validatortype(validators.fi_ssn)(**locals()) -@overload def fr_department() -> click.ParamType: - """Validate a french department number.""" - ... + """ + Validate a french department number. + Returns a `click.ParamType` instance which wraps `validators.fr_department`. + """ + return click_validatortype(validators.fr_department)() -fr_department = click_validatortype(validators.fr_department) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.fr_department` function.""" - -@overload def fr_ssn() -> click.ParamType: - """Validate a french Social Security Number.""" - ... - + """ + Validate a french Social Security Number. -fr_ssn = click_validatortype(validators.fr_ssn) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.fr_ssn` function.""" + Returns a `click.ParamType` instance which wraps `validators.fr_ssn`. + """ + return click_validatortype(validators.fr_ssn)() -@overload def hostname( - skip_ipv6_addr=False, - skip_ipv4_addr=False, - may_have_port=True, - maybe_simple=True, - consider_tld=False, - private=None, - rfc_1034=False, - rfc_2782=False, + *, + skip_ipv6_addr: bool = False, + skip_ipv4_addr: bool = False, + may_have_port: bool = True, + maybe_simple: bool = True, + consider_tld: bool = False, + private: Optional[bool] = None, + rfc_1034: bool = False, + rfc_2782: bool = False, ) -> click.ParamType: - """Return whether or not given value is a valid hostname.""" - ... + """ + Return whether or not given value is a valid hostname. + Returns a `click.ParamType` instance which wraps `validators.hostname`. + """ + return click_validatortype(validators.hostname)(**locals()) -hostname = click_validatortype(validators.hostname) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.hostname` function.""" - -@overload def iban() -> click.ParamType: - """Return whether or not given value is a valid IBAN code.""" - ... - + """ + Return whether or not given value is a valid IBAN code. -iban = click_validatortype(validators.iban) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.iban` function.""" + Returns a `click.ParamType` instance which wraps `validators.iban`. + """ + return click_validatortype(validators.iban)() -@overload def ind_aadhar() -> click.ParamType: - """Validate an indian aadhar card number.""" - ... + """ + Validate an indian aadhar card number. + Returns a `click.ParamType` instance which wraps `validators.ind_aadhar`. + """ + return click_validatortype(validators.ind_aadhar)() -ind_aadhar = click_validatortype(validators.ind_aadhar) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.ind_aadhar` function.""" - -@overload def ind_pan() -> click.ParamType: - """Validate a pan card number.""" - ... - - -ind_pan = click_validatortype(validators.ind_pan) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.ind_pan` function.""" + """ + Validate a pan card number. + Returns a `click.ParamType` instance which wraps `validators.ind_pan`. + """ + return click_validatortype(validators.ind_pan)() -@overload -def ipv4(cidr=True, strict=False, private=None, host_bit=True) -> click.ParamType: - """Returns whether a given value is a valid IPv4 address.""" - ... +def ipv4( + *, + cidr: bool = True, + strict: bool = False, + private: Optional[bool] = None, + host_bit: bool = True, +) -> click.ParamType: + """ + Returns whether a given value is a valid IPv4 address. -ipv4 = click_validatortype(validators.ipv4) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.ipv4` function.""" - + Returns a `click.ParamType` instance which wraps `validators.ipv4`. + """ + return click_validatortype(validators.ipv4)(**locals()) -@overload -def ipv6(cidr=True, strict=False, host_bit=True) -> click.ParamType: - """Returns if a given value is a valid IPv6 address.""" - ... +def ipv6( + *, + cidr: bool = True, + strict: bool = False, + host_bit: bool = True, +) -> click.ParamType: + """ + Returns if a given value is a valid IPv6 address. -ipv6 = click_validatortype(validators.ipv6) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.ipv6` function.""" + Returns a `click.ParamType` instance which wraps `validators.ipv6`. + """ + return click_validatortype(validators.ipv6)(**locals()) -@overload def isin() -> click.ParamType: - """Return whether or not given value is a valid ISIN.""" - ... - + """ + Return whether or not given value is a valid ISIN. -isin = click_validatortype(validators.isin) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.isin` function.""" + Returns a `click.ParamType` instance which wraps `validators.isin`. + """ + return click_validatortype(validators.isin)() -@overload def jcb() -> click.ParamType: - """Return whether or not given value is a valid JCB card number.""" - ... + """ + Return whether or not given value is a valid JCB card number. + Returns a `click.ParamType` instance which wraps `validators.jcb`. + """ + return click_validatortype(validators.jcb)() -jcb = click_validatortype(validators.jcb) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.jcb` function.""" - -@overload def mac_address() -> click.ParamType: - """Return whether or not given value is a valid MAC address.""" - ... - + """ + Return whether or not given value is a valid MAC address. -mac_address = click_validatortype(validators.mac_address) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.mac_address` function.""" + Returns a `click.ParamType` instance which wraps `validators.mac_address`. + """ + return click_validatortype(validators.mac_address)() -@overload def mastercard() -> click.ParamType: - """Return whether or not given value is a valid Mastercard card number.""" - ... - + """ + Return whether or not given value is a valid Mastercard card number. -mastercard = click_validatortype(validators.mastercard) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.mastercard` function.""" + Returns a `click.ParamType` instance which wraps `validators.mastercard`. + """ + return click_validatortype(validators.mastercard)() -@overload def md5() -> click.ParamType: - """Return whether or not given value is a valid MD5 hash.""" - ... + """ + Return whether or not given value is a valid MD5 hash. + Returns a `click.ParamType` instance which wraps `validators.md5`. + """ + return click_validatortype(validators.md5)() -md5 = click_validatortype(validators.md5) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.md5` function.""" - -@overload def sedol() -> click.ParamType: - """Return whether or not given value is a valid SEDOL.""" - ... - + """ + Return whether or not given value is a valid SEDOL. -sedol = click_validatortype(validators.sedol) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.sedol` function.""" + Returns a `click.ParamType` instance which wraps `validators.sedol`. + """ + return click_validatortype(validators.sedol)() -@overload def sha1() -> click.ParamType: - """Return whether or not given value is a valid SHA1 hash.""" - ... + """ + Return whether or not given value is a valid SHA1 hash. + Returns a `click.ParamType` instance which wraps `validators.sha1`. + """ + return click_validatortype(validators.sha1)() -sha1 = click_validatortype(validators.sha1) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.sha1` function.""" - -@overload def sha224() -> click.ParamType: - """Return whether or not given value is a valid SHA224 hash.""" - ... - + """ + Return whether or not given value is a valid SHA224 hash. -sha224 = click_validatortype(validators.sha224) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.sha224` function.""" + Returns a `click.ParamType` instance which wraps `validators.sha224`. + """ + return click_validatortype(validators.sha224)() -@overload def sha256() -> click.ParamType: - """Return whether or not given value is a valid SHA256 hash.""" - ... + """ + Return whether or not given value is a valid SHA256 hash. + Returns a `click.ParamType` instance which wraps `validators.sha256`. + """ + return click_validatortype(validators.sha256)() -sha256 = click_validatortype(validators.sha256) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.sha256` function.""" - -@overload def sha384() -> click.ParamType: - """Return whether or not given value is a valid SHA384 hash.""" - ... - + """ + Return whether or not given value is a valid SHA384 hash. -sha384 = click_validatortype(validators.sha384) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.sha384` function.""" + Returns a `click.ParamType` instance which wraps `validators.sha384`. + """ + return click_validatortype(validators.sha384)() -@overload def sha512() -> click.ParamType: - """Return whether or not given value is a valid SHA512 hash.""" - ... - + """ + Return whether or not given value is a valid SHA512 hash. -sha512 = click_validatortype(validators.sha512) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.sha512` function.""" + Returns a `click.ParamType` instance which wraps `validators.sha512`. + """ + return click_validatortype(validators.sha512)() -@overload def slug() -> click.ParamType: - """Validate whether or not given value is valid slug.""" - ... + """ + Validate whether or not given value is valid slug. + Returns a `click.ParamType` instance which wraps `validators.slug`. + """ + return click_validatortype(validators.slug)() -slug = click_validatortype(validators.slug) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.slug` function.""" - -@overload def trx_address() -> click.ParamType: - """Return whether or not given value is a valid tron address.""" - ... - + """ + Return whether or not given value is a valid tron address. -trx_address = click_validatortype(validators.trx_address) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.trx_address` function.""" + Returns a `click.ParamType` instance which wraps `validators.trx_address`. + """ + return click_validatortype(validators.trx_address)() -@overload def unionpay() -> click.ParamType: - """Return whether or not given value is a valid UnionPay card number.""" - ... - + """ + Return whether or not given value is a valid UnionPay card number. -unionpay = click_validatortype(validators.unionpay) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.unionpay` function.""" + Returns a `click.ParamType` instance which wraps `validators.unionpay`. + """ + return click_validatortype(validators.unionpay)() -@overload def url( - skip_ipv6_addr=False, - skip_ipv4_addr=False, - may_have_port=True, - simple_host=False, - strict_query=True, - consider_tld=False, - private=None, - rfc_1034=False, - rfc_2782=False, + *, + skip_ipv6_addr: bool = False, + skip_ipv4_addr: bool = False, + may_have_port: bool = True, + simple_host: bool = False, + strict_query: bool = True, + consider_tld: bool = False, + private: Optional[bool] = None, + rfc_1034: bool = False, + rfc_2782: bool = False, ) -> click.ParamType: - """Return whether or not given value is a valid URL.""" - ... + """ + Return whether or not given value is a valid URL. + Returns a `click.ParamType` instance which wraps `validators.url`. + """ + return click_validatortype(validators.url)(**locals()) -url = click_validatortype(validators.url) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.url` function.""" - -@overload def uuid() -> click.ParamType: - """Return whether or not given value is a valid UUID-v4 string.""" - ... - + """ + Return whether or not given value is a valid UUID-v4 string. -uuid = click_validatortype(validators.uuid) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.uuid` function.""" + Returns a `click.ParamType` instance which wraps `validators.uuid`. + """ + return click_validatortype(validators.uuid)() -@overload def visa() -> click.ParamType: - """Return whether or not given value is a valid Visa card number.""" - ... - + """ + Return whether or not given value is a valid Visa card number. -visa = click_validatortype(validators.visa) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.visa` function.""" + Returns a `click.ParamType` instance which wraps `validators.visa`. + """ + return click_validatortype(validators.visa)() __all__ = [ diff --git a/clicktypes/__main__.py b/clicktypes/__main__.py index c90f025..78e98e7 100644 --- a/clicktypes/__main__.py +++ b/clicktypes/__main__.py @@ -11,17 +11,12 @@ def overload_info(func: Callable) -> Dict[str, Any]: """inspect function and return name, params and docstring.""" sig = inspect.signature(func) - params = ", ".join( - ( - f"{param.name} = {repr(param.default)}" - if param.default is not param.empty - else f"{param.name}" - ) - for param in sig.parameters.values() - if param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD) - and param.name != "value" # noqa: W503 - ) - docstring, _ = inspect.getdoc(func).split("\n", maxsplit=1) or ("", None) + + params = ", ".join(str(p) for p in sig.parameters.values() if p.name != "value") + params = (params.rstrip(",") + ",") if params else "" + + doc = inspect.getdoc(func) or "" + docstring, *_ = doc.split("\n", maxsplit=1) return { "name": func.__name__, @@ -52,28 +47,44 @@ def validators_info() -> List[Dict[str, Any]]: ) -def init() -> None: +def main() -> None: """Generate the __init__.py file.""" env = jinja2.Environment( loader=jinja2.FileSystemLoader("templates"), - autoescape=jinja2.select_autoescape(["py"]), + autoescape=jinja2.select_autoescape(["py", "md"]), + keep_trailing_newline=True, ) - file = Path("clicktypes/__init__.py") - - template = env.get_template(file.name + ".jinja2") + info = validators_info() - content = black.format_file_contents( - template.render( - validators=validators_info(), + for file in map( + Path, + ( + "clicktypes/__init__.py", + "README.md", ), - fast=False, - mode=black.FileMode(), - ) + ): + try: + template = env.get_template(file.name + ".jinja2") + content = template.render( + validators=info, + ) + + if file.suffix == ".py": + content = black.format_file_contents( + content, + fast=False, + mode=black.FileMode(), + ) - file.write_text(content, encoding="utf-8") + file.write_text(content, encoding="utf-8") + except Exception as e: + print(f"Error {file}: {e}") + continue + else: + print(f"Generated {file}") if __name__ == "__main__": - init() + main() diff --git a/docs/__main__.py b/docs/__main__.py index ff0ebfd..108b738 100644 --- a/docs/__main__.py +++ b/docs/__main__.py @@ -58,7 +58,6 @@ def main() -> int: modules = [ "clicktypes", - "click", "click.types", "validators", ] diff --git a/poetry.lock b/poetry.lock index fe6afc3..eb56d6b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -763,8 +763,9 @@ type = ["pytest-mypy"] [extras] crypto-eth-addresses = ["eth-hash"] +eth = ["eth-hash"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "c7ee50f37cb129956dcaaf81167b769d3ab5927bff5ff195124c05d9734ee2f2" +content-hash = "b11583a7e3b0f20fc109ffa0013aa1ad5a9762bad2349d3da249a489d08be7f1" diff --git a/pyproject.toml b/pyproject.toml index 585d772..faf0f61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ eth-hash = { version = ">=0.7.0", extras = [ "pycryptodome" ], optional = true } [tool.poetry.extras] crypto-eth-addresses = [ "eth-hash" ] +eth = [ "eth-hash" ] [tool.poetry.group.dev.dependencies] jinja2 = "^3.1.6" @@ -74,13 +75,7 @@ addopts = [ ] [tool.coverage.report] -exclude_lines = [ - "@overload", -] +exclude_lines = [ "@overload" ] [tool.coverage.run] -omit = [ - "*/__main__.py", - "*/tests/*", - "*/docs/*", -] +omit = [ "*/__main__.py", "*/tests/*", "*/docs/*" ] diff --git a/templates/README.md.jinja2 b/templates/README.md.jinja2 new file mode 100644 index 0000000..66b023e --- /dev/null +++ b/templates/README.md.jinja2 @@ -0,0 +1,81 @@ +# click-validators + +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/click-validators)](https://pypi.org/project/click-validators/) +[![PyPI - Version](https://img.shields.io/pypi/v/click-validators)](https://pypi.org/project/click-validators/) +[![PyPI - Downloads](https://img.shields.io/pypi/dm/click-validators)](https://pypi.org/project/click-validators/) +[![PyPI - License](https://img.shields.io/pypi/l/click-validators)](https://raw.githubusercontent.com/d-chris/click-validators/main/LICENSE) +[![GitHub - Pytest](https://img.shields.io/github/actions/workflow/status/d-chris/click-validators/pytest.yml?logo=github&label=pytest)](https://github.com/d-chris/click-validators/actions/workflows/pytest.yml) +[![GitHub - Page](https://img.shields.io/website?url=https%3A%2F%2Fd-chris.github.io%2Fclick-validators&up_message=pdoc&logo=github&label=documentation)](https://d-chris.github.io/click-validators) +[![GitHub - Release](https://img.shields.io/github/v/tag/d-chris/click-validators?logo=github&label=github)](https://github.com/d-chris/click-validators) +[![codecov](https://codecov.io/gh/d-chris/click-validators/graph/badge.svg?token=WY062DFVTR)](https://codecov.io/gh/d-chris/click-validators) +[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://raw.githubusercontent.com/d-chris/click-validators/main/.pre-commit-config.yaml) + +--- + +Additional `click` parameter types are built on top of the `validators` library, providing a wide range of validation options for various data types, including email addresses, IP addresses, credit card numbers, and more. This package simplifies the process of adding robust validation to your Click-based CLI applications. + +{% for validator in validators -%} +- `clicktypes.{{ validator.name }}()` +{% endfor %} +## Install + +```cmd +pip install click-validators +``` + +for `clicktypes.eth_address()` validation, additional package `eth-hash[pycryptodome]>=0.7.0` is required. + +[![PyPI - eth-hash](https://img.shields.io/pypi/v/eth-hash?logo=pypi&logoColor=white&label=eth-hash[pycryptodome])](https://pypi.org/project/eth-hash/) + +```cmd +pip install click-validators[eth] +``` + +## Usage + +import the module `clicktypes` and use the validators as types in click commands. + +```python +import click + +import clicktypes + + +@click.command( + help="validate email address", +) +@click.argument( + "email", + type=clicktypes.email(), +) +def main(email): + click.echo(f"valid {email=}") + + +if __name__ == "__main__": + main() +``` + +### Example + +```cmd +$ main.py fu@bar.com + +valid email='fu@bar.com' +``` + +```cmd +$ main.py fu.bar.com + +Usage: main.py [OPTIONS] EMAIL +Try 'main.py --help' for help. + +Error: Invalid value for 'EMAIL': Invalid email='fu.bar.com'. +``` + +## Dependencies + +[![PyPI - click](https://img.shields.io/pypi/v/click?logo=pypi&logoColor=white&label=click)](https://pypi.org/project/click/) +[![PyPI - validators](https://img.shields.io/pypi/v/validators?logo=pypi&logoColor=white&label=validators)](https://pypi.org/project/validators/) + +--- diff --git a/templates/__init__.py.jinja2 b/templates/__init__.py.jinja2 index 21ec4ed..9660dff 100644 --- a/templates/__init__.py.jinja2 +++ b/templates/__init__.py.jinja2 @@ -2,7 +2,7 @@ .. include:: ../README.md """ -from typing import overload +from typing import Optional import click import validators @@ -10,13 +10,27 @@ import validators from .decorator import click_validatortype {% for validator in validators %} -@overload -def {{ validator.name }}({{ validator.params }}) -> click.ParamType: - """{{ validator.docstring }}""" - ... -{{ validator.name }} = click_validatortype(validators.{{ validator.name }}) -"""A custom parameter type derived from `click.types.ParamType` that uses the `validators.{{ validator.name }}` function.""" +{% if validator.params %} +def {{ validator.name }}( + *, + {{ validator.params }} +) -> click.ParamType: + """ + {{ validator.docstring }} + + Returns a `click.ParamType` instance which wraps `validators.{{ validator.name }}`. + """ + return click_validatortype(validators.{{ validator.name }})(**locals()) +{% else %} +def {{ validator.name }}() -> click.ParamType: + """ + {{ validator.docstring }} + + Returns a `click.ParamType` instance which wraps `validators.{{ validator.name }}`. + """ + return click_validatortype(validators.{{ validator.name }})() +{% endif %} {% endfor %} __all__ = [ diff --git a/tests/__init__.py b/tests/__init__.py index 58be864..f7b1062 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -11,7 +11,7 @@ def docstrings() -> Generator[str, None, None]: """docstrings from all clicktypes validators.""" yield from ( - inspect.getdoc(getattr(validators, validator)) + inspect.getdoc(getattr(validators, validator)) or "" for validator in clicktypes.__all__ if validator not in ("base58", "country_code") )