diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 50ae8057..550ff458 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,17 +11,15 @@ repos: - id: debug-statements - id: destroyed-symlinks - id: trailing-whitespace - # will be uncommented when all the - # scripts have been revised once - # - repo: https://github.com/psf/black - # rev: 23.1.0 - # hooks: - # - id: black - # - repo: https://github.com/PyCQA/isort - # rev: 5.12.0 - # hooks: - # - id: isort - # - repo: https://github.com/PyCQA/flake8 - # rev: 6.0.0 - # hooks: - # - id: flake8 + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 diff --git a/docs/gen_docs.py b/docs/gen_docs.py index 3e1d790a..fdfbe088 100644 --- a/docs/gen_docs.py +++ b/docs/gen_docs.py @@ -1,15 +1,17 @@ """Generate docs.""" # standard -from shutil import copy, move, rmtree -from yaml import safe_load, safe_dump +from shutil import rmtree, move, copy from ast import parse, ImportFrom -from typing import Dict, List +from typing import List, Dict from os.path import getsize from subprocess import run from pathlib import Path from sys import argv +# external +from yaml import safe_load, safe_dump + def _write_ref_content(source: Path, module_name: str, func_name: str): """Write content.""" @@ -61,7 +63,7 @@ def generate_documentation(source: Path): # build docs as subprocess print(run(("mkdocs", "build"), capture_output=True).stderr.decode()) # restore mkdocs config - move(source / "mkdocs.bak.yml", source / "mkdocs.yml") + move(str(source / "mkdocs.bak.yml"), source / "mkdocs.yml") if __name__ == "__main__": diff --git a/poetry.lock b/poetry.lock index 32d21038..cff62878 100644 --- a/poetry.lock +++ b/poetry.lock @@ -865,6 +865,21 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "pyaml" +version = "21.10.1" +description = "PyYAML-based module to produce pretty and readable YAML-serialized data" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"}, + {file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"}, +] + +[package.dependencies] +PyYAML = "*" + [[package]] name = "pycodestyle" version = "2.9.1" @@ -1439,4 +1454,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "0d3d02b694099f712d6ebec660a6854bca91b1ebebad0f81be5805b1974c9923" +content-hash = "50370d78d455974d8323b523c02050900a5570ce08657757a1666a03c92fdca1" diff --git a/pyproject.toml b/pyproject.toml index 22794f5e..ceef7924 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ tox = "^4.4.7" mkdocs = "^1.4.2" mkdocs-material = "^9.1.3" mkdocstrings = { extras = ["python"], version = "^0.20.0" } +pyaml = "^21.10.1" [tool.poetry.group.sast.dependencies] bandit = "^1.7.5" @@ -60,38 +61,31 @@ build-backend = "poetry.core.masonry.api" [tool.black] line-length = 100 target-version = ["py38", "py39", "py310", "py311"] +extend-exclude = "i18n" [tool.bandit] -exclude_dirs = [ - ".github", - ".pytest_cache", - ".tox", - ".vscode", - "docs", - "site", - "tests", -] +exclude_dirs = [".github", ".pytest_cache", ".tox", ".vscode", "site", "tests"] [tool.isort] -extend_skip_glob = ["**/i18n/*"] ensure_newline_before_comments = true +extend_skip_glob = ["**/i18n/*"] force_grid_wrap = 0 force_sort_within_sections = true ignore_comments = true include_trailing_comma = true known_local_folder = ["validators"] -line_length = 100 length_sort = true +line_length = 100 multi_line_output = 3 profile = "black" -reverse_sort = true reverse_relative = true +reverse_sort = true skip_gitignore = true use_parentheses = true [tool.pyright] include = ["validators", "tests"] -exclude = ["**/__pycache__", ".pytest_cache/", ".tox/", "site/"] +exclude = ["**/__pycache__", ".pytest_cache/", ".tox/", "site/", "**/i18n/*"] pythonVersion = "3.8" pythonPlatform = "All" typeCheckingMode = "strict" @@ -102,25 +96,32 @@ legacy_tox_ini = """ requires = tox >= 4.0 env_list = py{38,39,310,311} - # format, lint, type, + fmt_black, fmt_isort, lint, type [testenv] description = run unit tests deps = pytest commands = pytest - # [testenv:format] - # description = run formatter - # deps = black - # commands = black - - # [testenv:lint] - # description = run linters - # deps = flake8 - # commands = flake8 - - # [testenv:type] - # description = run type checker - # deps = pyright - # commands = pyright + [testenv:fmt_black] + description = run formatter + deps = black + commands = black . + + [testenv:fmt_isort] + description = run formatter + deps = isort + commands = isort . + + [testenv:lint] + description = run linters + deps = flake8 + commands = flake8 + + [testenv:type] + description = run type checker + deps = + pyright + pytest + commands = pyright """ diff --git a/setup.cfg b/setup.cfg index 038b70ab..9c1f4026 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,6 @@ # until https://github.com/PyCQA/flake8/issues/234 is resolved [flake8] -max-line-length = 100 docstring-convention = google +exclude = __pycache__,.github,.pytest_cache,.tox,.vscode,site,i18n +max-line-length = 100 diff --git a/tests/test__extremes.py b/tests/test__extremes.py index 6b98ffc4..c575c650 100644 --- a/tests/test__extremes.py +++ b/tests/test__extremes.py @@ -7,8 +7,8 @@ # external import pytest -# project -from validators._extremes import AbsMax, AbsMin +# local +from validators._extremes import AbsMin, AbsMax abs_max = AbsMax() abs_min = AbsMin() diff --git a/tests/test_card.py b/tests/test_card.py index 95261305..132f847c 100644 --- a/tests/test_card.py +++ b/tests/test_card.py @@ -6,7 +6,6 @@ # local from validators import ( - ValidationFailure, card_number, mastercard, unionpay, @@ -15,6 +14,7 @@ visa, amex, jcb, + ValidationFailure, ) visa_cards = ["4242424242424242", "4000002760003184"] diff --git a/tests/test_hashes.py b/tests/test_hashes.py index b1c28178..77cf7515 100644 --- a/tests/test_hashes.py +++ b/tests/test_hashes.py @@ -5,8 +5,7 @@ import pytest # local -from validators import md5, sha1, sha224, sha256, sha512, ValidationFailure - +from validators import sha512, sha256, sha224, sha1, md5, ValidationFailure # ==> md5 <== # diff --git a/tests/test_ip_address.py b/tests/test_ip_address.py index 98a977fa..42bf6956 100644 --- a/tests/test_ip_address.py +++ b/tests/test_ip_address.py @@ -5,7 +5,7 @@ import pytest # local -from validators import ipv4, ipv6, ValidationFailure +from validators import ipv6, ipv4, ValidationFailure @pytest.mark.parametrize( diff --git a/tests/test_length.py b/tests/test_length.py index 3c389a36..a9278cd3 100644 --- a/tests/test_length.py +++ b/tests/test_length.py @@ -4,7 +4,7 @@ # external import pytest -# project +# local from validators import length, ValidationFailure diff --git a/tests/test_url.py b/tests/test_url.py index 15d9d471..a5d377fa 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -5,7 +5,7 @@ import pytest # local -from validators import ValidationFailure, url +from validators import url, ValidationFailure @pytest.mark.parametrize( diff --git a/tests/test_validation_failure.py b/tests/test_validation_failure.py index 75835eb7..1c035c5f 100644 --- a/tests/test_validation_failure.py +++ b/tests/test_validation_failure.py @@ -1,10 +1,9 @@ """Test validation Failure.""" # -*- coding: utf-8 -*- -# project +# local from validators import between - failed_obj_repr = "ValidationFailure(func=between" diff --git a/validators/__init__.py b/validators/__init__.py index 1ba5d816..2e65c350 100644 --- a/validators/__init__.py +++ b/validators/__init__.py @@ -1,22 +1,22 @@ """Validate Anything!""" # -*- coding: utf-8 -*- -from .between import between +from .card import card_number, mastercard, unionpay, discover, diners, visa, amex, jcb +from .hashes import sha512, sha256, sha224, sha1, md5 +from .utils import validator, ValidationFailure +from .i18n import fi_business_id, fi_ssn +from .mac_address import mac_address from .btc_address import btc_address -from .card import amex, card_number, diners, discover, jcb, mastercard, unionpay, visa -from .domain import domain -from .email import email -from .hashes import md5, sha1, sha224, sha256, sha512 +from .ip_address import ipv6, ipv4 from .hostname import hostname -from .i18n import fi_business_id, fi_ssn -from .iban import iban -from .ip_address import ipv4, ipv6 +from .between import between from .length import length -from .mac_address import mac_address +from .domain import domain +from .email import email +from .uuid import uuid from .slug import slug +from .iban import iban from .url import url -from .utils import ValidationFailure, validator -from .uuid import uuid __all__ = ( "amex", diff --git a/validators/between.py b/validators/between.py index 2e713e47..12fe54bb 100644 --- a/validators/between.py +++ b/validators/between.py @@ -6,7 +6,7 @@ from datetime import datetime # local -from ._extremes import AbsMax, AbsMin +from ._extremes import AbsMin, AbsMax from .utils import validator PossibleValueTypes = TypeVar("PossibleValueTypes", int, float, str, datetime) diff --git a/validators/btc_address.py b/validators/btc_address.py index 065ae1e0..e8267ddc 100644 --- a/validators/btc_address.py +++ b/validators/btc_address.py @@ -50,11 +50,12 @@ def btc_address(value: str, /): > *New in version 0.18.0*. """ - if value and type(value) is str: - return ( - # segwit pattern - re.compile(r"^(bc|tc)[0-3][02-9ac-hj-np-z]{14,74}$").match(value) - if value[:2] in ("bc", "tb") - else _validate_old_btc_address(value) - ) - return False + if not value: + return False + + return ( + # segwit pattern + re.compile(r"^(bc|tc)[0-3][02-9ac-hj-np-z]{14,74}$").match(value) + if value[:2] in ("bc", "tb") + else _validate_old_btc_address(value) + ) diff --git a/validators/card.py b/validators/card.py index 7c48e7ff..62ced1ed 100644 --- a/validators/card.py +++ b/validators/card.py @@ -34,6 +34,8 @@ def card_number(value: str, /): > *New in version 0.15.0*. """ + if not value: + return False try: digits = list(map(int, value)) odd_sum = sum(digits[-1::-2]) diff --git a/validators/domain.py b/validators/domain.py index d159a61b..3866ab4f 100644 --- a/validators/domain.py +++ b/validators/domain.py @@ -44,6 +44,8 @@ def domain(value: str, /, *, rfc_1034: bool = False, rfc_2782: bool = False): > *New in version 0.9.0*. """ + if not value: + return False try: return not re.search(r"\s", value) and re.match( # First character of the domain diff --git a/validators/hashes.py b/validators/hashes.py index 1767dfbc..13fe9e1d 100644 --- a/validators/hashes.py +++ b/validators/hashes.py @@ -30,7 +30,7 @@ def md5(value: str, /): > *New in version 0.12.1* """ - return re.match(r"^[0-9a-f]{32}$", value, re.IGNORECASE) + return re.match(r"^[0-9a-f]{32}$", value, re.IGNORECASE) if value else False @validator @@ -55,7 +55,7 @@ def sha1(value: str, /): > *New in version 0.12.1* """ - return re.match(r"^[0-9a-f]{40}$", value, re.IGNORECASE) + return re.match(r"^[0-9a-f]{40}$", value, re.IGNORECASE) if value else False @validator @@ -80,7 +80,7 @@ def sha224(value: str, /): > *New in version 0.12.1* """ - return re.match(r"^[0-9a-f]{56}$", value, re.IGNORECASE) + return re.match(r"^[0-9a-f]{56}$", value, re.IGNORECASE) if value else False @validator @@ -108,7 +108,7 @@ def sha256(value: str, /): > *New in version 0.12.1* """ - return re.match(r"^[0-9a-f]{64}$", value, re.IGNORECASE) + return re.match(r"^[0-9a-f]{64}$", value, re.IGNORECASE) if value else False @validator @@ -137,4 +137,4 @@ def sha512(value: str, /): > *New in version 0.12.1* """ - return re.match(r"^[0-9a-f]{128}$", value, re.IGNORECASE) + return re.match(r"^[0-9a-f]{128}$", value, re.IGNORECASE) if value else False diff --git a/validators/hostname.py b/validators/hostname.py index 50c17a58..071e88a9 100644 --- a/validators/hostname.py +++ b/validators/hostname.py @@ -6,7 +6,7 @@ import re # local -from .ip_address import ipv4, ipv6 +from .ip_address import ipv6, ipv4 from .utils import validator from .domain import domain @@ -40,6 +40,8 @@ def _port_validator(value: str): if _port_regex().match(f":{port_seg}"): return host_seg + return None + @validator def hostname( @@ -103,6 +105,9 @@ def hostname( > *New in version 0.21.0*. """ + if not value: + return False + if may_have_port and (host_seg := _port_validator(value)): return ( (_simple_hostname_regex().match(host_seg) if maybe_simple else False) diff --git a/validators/iban.py b/validators/iban.py index 0241912c..a7614fae 100644 --- a/validators/iban.py +++ b/validators/iban.py @@ -42,4 +42,8 @@ def iban(value: str, /): > *New in version 0.8.0* """ - return re.match(r"^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$", value) and _mod_check(value) + return ( + (re.match(r"^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$", value) and _mod_check(value)) + if value + else False + ) diff --git a/validators/ip_address.py b/validators/ip_address.py index 0f2e5a6a..b9e0e382 100644 --- a/validators/ip_address.py +++ b/validators/ip_address.py @@ -3,12 +3,12 @@ # standard from ipaddress import ( - AddressValueError, NetmaskValueError, - IPv4Address, - IPv4Network, - IPv6Address, + AddressValueError, IPv6Network, + IPv6Address, + IPv4Network, + IPv4Address, ) # local @@ -56,6 +56,8 @@ def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False): > *New in version 0.2.0* """ + if not value: + return False try: if cidr and value.count("/") == 1: return IPv4Network(value, strict=strict) @@ -104,6 +106,8 @@ def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False): > *New in version 0.2.0* """ + if not value: + return False try: if cidr and value.count("/") == 1: return IPv6Network(value, strict=strict) diff --git a/validators/length.py b/validators/length.py index 4b959017..2b8d756c 100644 --- a/validators/length.py +++ b/validators/length.py @@ -2,8 +2,8 @@ # -*- coding: utf-8 -*- # local -from .between import between from .utils import validator +from .between import between @validator @@ -36,4 +36,4 @@ def length(value: str, /, *, min_val: int = 0, max_val: int = 0): > *New in version 0.2.0*. """ - return between(len(value), min_val=min_val, max_val=max_val) + return between(len(value), min_val=min_val, max_val=max_val) if value else False diff --git a/validators/mac_address.py b/validators/mac_address.py index 65e76ccf..06f6285c 100644 --- a/validators/mac_address.py +++ b/validators/mac_address.py @@ -34,4 +34,4 @@ def mac_address(value: str, /): > *New in version 0.2.0*. """ - return re.match(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", value) + return re.match(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", value) if value else False diff --git a/validators/slug.py b/validators/slug.py index fe3d3169..a3fcc681 100644 --- a/validators/slug.py +++ b/validators/slug.py @@ -33,4 +33,4 @@ def slug(value: str, /): > *New in version 0.6.0*. """ - return re.match(r"^[a-z0-9]+(?:-[a-z0-9]+)*$", value) + return re.match(r"^[a-z0-9]+(?:-[a-z0-9]+)*$", value) if value else False diff --git a/validators/url.py b/validators/url.py index 37616372..116ddf4a 100644 --- a/validators/url.py +++ b/validators/url.py @@ -57,7 +57,7 @@ def _validate_auth_segment(value: str): return _username_regex().match(value) username, password = value.rsplit(":", 1) return _username_regex().match(username) and all( - char_to_avoid not in password for char_to_avoid in {"/", "?", "#", "@"} + char_to_avoid not in password for char_to_avoid in ("/", "?", "#", "@") ) @@ -107,7 +107,7 @@ def _validate_optionals(path: str, query: str, fragment: str): if query: optional_segments &= bool(_query_regex().match(query.encode("idna").decode("utf-8"))) if fragment: - optional_segments &= all(char_to_avoid not in fragment for char_to_avoid in {"/", "?"}) + optional_segments &= all(char_to_avoid not in fragment for char_to_avoid in ("/", "?")) return optional_segments @@ -155,7 +155,7 @@ def url( When URL string cannot contain an IPv4 address. may_have_port: URL string may contain port number. - maybe_simple: + simple_host: URL string maybe only hyphens and alpha-numerals. rfc_1034: Allow trailing dot in domain/host name. diff --git a/validators/utils.py b/validators/utils.py index e8060ec9..ede6f658 100644 --- a/validators/utils.py +++ b/validators/utils.py @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- # standard -from typing import Any, Callable, Dict +from typing import Callable, Dict, Any from inspect import getfullargspec -from functools import wraps from itertools import chain +from functools import wraps class ValidationFailure(Exception): diff --git a/validators/uuid.py b/validators/uuid.py index 69b943f7..fa012502 100644 --- a/validators/uuid.py +++ b/validators/uuid.py @@ -36,6 +36,8 @@ def uuid(value: Union[str, UUID], /): > *New in version 0.2.0*. """ + if not value: + return False if isinstance(value, UUID): return True try: