├── .github
└── workflows
│ ├── docs.yaml.bkp
│ ├── package.yaml.bkp
│ ├── pycqa.yaml
│ └── sast.yaml
├── .gitignore
├── CHANGES.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── SECURITY.md
├── docs
├── Makefile
├── api
│ ├── between.md
│ ├── between.rst
│ ├── card.md
│ ├── card.rst
│ ├── country.md
│ ├── country.rst
│ ├── cron.md
│ ├── cron.rst
│ ├── crypto_addresses.md
│ ├── crypto_addresses.rst
│ ├── domain.md
│ ├── domain.rst
│ ├── email.md
│ ├── email.rst
│ ├── encoding.md
│ ├── encoding.rst
│ ├── finance.md
│ ├── finance.rst
│ ├── hashes.md
│ ├── hashes.rst
│ ├── hostname.md
│ ├── hostname.rst
│ ├── i18n.md
│ ├── i18n.rst
│ ├── iban.md
│ ├── iban.rst
│ ├── ip_address.md
│ ├── ip_address.rst
│ ├── length.md
│ ├── length.rst
│ ├── mac_address.md
│ ├── mac_address.rst
│ ├── slug.md
│ ├── slug.rst
│ ├── url.md
│ ├── url.rst
│ ├── utils.md
│ ├── utils.rst
│ ├── uuid.md
│ └── uuid.rst
├── assets
│ └── icons
│ │ └── favicon.svg
├── conf.py
├── index.md
├── index.rst
├── install_and_use.md
├── install_and_use.rst
└── make.bat
├── mkdocs.yaml
├── package
├── export
│ ├── __init__.py
│ └── __main__.py
├── requirements.sphinx.txt
├── requirements.testing.txt
├── requirements.tooling.txt
├── roll.ps1
└── roll.sh
├── pdm.lock
├── pyproject.toml
├── src
├── __init__.py
└── validators
│ ├── __init__.py
│ ├── _extremes.py
│ ├── _tld.txt
│ ├── between.py
│ ├── card.py
│ ├── country.py
│ ├── cron.py
│ ├── crypto_addresses
│ ├── __init__.py
│ ├── bsc_address.py
│ ├── btc_address.py
│ ├── eth_address.py
│ └── trx_address.py
│ ├── domain.py
│ ├── email.py
│ ├── encoding.py
│ ├── finance.py
│ ├── hashes.py
│ ├── hostname.py
│ ├── i18n
│ ├── __init__.py
│ ├── es.py
│ ├── fi.py
│ ├── fr.py
│ ├── ind.py
│ └── ru.py
│ ├── iban.py
│ ├── ip_address.py
│ ├── length.py
│ ├── mac_address.py
│ ├── py.typed
│ ├── slug.py
│ ├── uri.py
│ ├── url.py
│ ├── utils.py
│ └── uuid.py
└── tests
├── crypto_addresses
├── test_bsc_address.py
├── test_btc_address.py
├── test_eth_address.py
└── test_trx_address.py
├── i18n
├── test_es.py
├── test_fi.py
├── test_fr.py
├── test_ind.py
└── test_ru.py
├── test__extremes.py
├── test_between.py
├── test_card.py
├── test_country.py
├── test_cron.py
├── test_domain.py
├── test_email.py
├── test_encoding.py
├── test_finance.py
├── test_hashes.py
├── test_hostname.py
├── test_iban.py
├── test_ip_address.py
├── test_length.py
├── test_mac_address.py
├── test_slug.py
├── test_url.py
├── test_uuid.py
└── test_validation_failure.py
/.github/workflows/docs.yaml.bkp:
--------------------------------------------------------------------------------
1 | # Documentation
2 | name: docs
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: ["master"]
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | contents: read
12 | env:
13 | BUILD_PATH: .
14 | PIP_DISABLE_PIP_VERSION_CHECK: 1
15 | steps:
16 | # checkout repository
17 | - name: Checkout repository
18 | uses: actions/checkout@v4
19 | # set up specific python version
20 | - name: Set up Python v3.12
21 | uses: actions/setup-python@v5
22 | with:
23 | python-version: "3.12"
24 | # building
25 | - name: Install 'documentation' dependencies
26 | run: |
27 | pip install .
28 | pip install -r package/requirements.mkdocs.txt
29 | - name: Build documentation
30 | run: python package/export doc
31 | # set up Pages
32 | - name: Set up Pages
33 | uses: actions/configure-pages@v4
34 | # upload static page
35 | - name: Upload artifact
36 | uses: actions/upload-pages-artifact@v3
37 | with:
38 | path: ${{ env.BUILD_PATH }}/site
39 | deploy:
40 | needs: build
41 | runs-on: ubuntu-latest
42 | permissions:
43 | pages: write
44 | id-token: write
45 | environment:
46 | name: github-pages
47 | url: ${{ steps.deployment.outputs.page_url }}
48 | # deploy documentation
49 | name: Deploy
50 | steps:
51 | - name: Deploy to GitHub Pages
52 | id: deployment
53 | uses: actions/deploy-pages@v4
54 |
--------------------------------------------------------------------------------
/.github/workflows/package.yaml.bkp:
--------------------------------------------------------------------------------
1 | # Publish to PyPI
2 | name: package
3 | on:
4 | workflow_dispatch:
5 | # uncomment the following lines to enable workflow on package release
6 | # release:
7 | # types: [published]
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: read
13 | env:
14 | BUILD_PATH: .
15 | PIP_DISABLE_PIP_VERSION_CHECK: 1
16 | steps:
17 | # checkout repository
18 | - name: Checkout repository
19 | uses: actions/checkout@v4
20 | # set up specific python version
21 | - name: Set up Python v3.8
22 | uses: actions/setup-python@v5
23 | with:
24 | python-version: "3.8"
25 | # install dependencies
26 | - name: Install dependencies
27 | run: |
28 | python -m venv .venv
29 | ./.venv/bin/python -m pip install --upgrade pip
30 | ./.venv/bin/pip install .
31 | ./.venv/bin/pip install -r package/requirements.sphinx.txt
32 | ./.venv/bin/pip install build
33 | # build package
34 | - name: Build package
35 | run: |
36 | . ./.venv/bin/activate
37 | python package/export pkg
38 | deactivate
39 | # upload package as artifact
40 | - name: Upload artifact
41 | uses: actions/upload-pages-artifact@v3
42 | with:
43 | path: ${{ env.BUILD_PATH }}/dist
44 | publish:
45 | needs: build
46 | runs-on: ubuntu-latest
47 | permissions:
48 | id-token: write
49 | steps:
50 | # publish package
51 | - name: Publish to PyPI
52 | uses: pypa/gh-action-pypi-publish@release/v1
53 | with:
54 | password: ${{ secrets.PYPI_API_TOKEN }}
55 |
--------------------------------------------------------------------------------
/.github/workflows/pycqa.yaml:
--------------------------------------------------------------------------------
1 | # Python Code Quality Analysis
2 | name: pycqa
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: [master]
7 | pull_request:
8 | branches: [master]
9 | jobs:
10 | tooling:
11 | runs-on: ubuntu-latest
12 | env:
13 | PIP_DISABLE_PIP_VERSION_CHECK: 1
14 | steps:
15 | # checkout repository
16 | - name: Checkout repository
17 | uses: actions/checkout@v4
18 | # set up specific python version
19 | - name: Set up Python v3.9
20 | uses: actions/setup-python@v5
21 | with:
22 | python-version: "3.9"
23 | # tooling
24 | - name: Install 'tooling' dependencies
25 | run: pip install -r package/requirements.tooling.txt
26 | - name: Tooling
27 | run: |
28 | ruff format .
29 | ruff check .
30 | pyright .
31 | testing:
32 | strategy:
33 | fail-fast: true
34 | matrix:
35 | os: [ubuntu-latest, macos-latest, windows-latest]
36 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
37 | runs-on: ${{ matrix.os }}
38 | steps:
39 | # checkout repository again!
40 | # ref: https://github.com/actions/checkout/issues/19
41 | - name: Checkout repository
42 | uses: actions/checkout@v4
43 | # set up specific python version
44 | - name: Set up Python v${{ matrix.python-version }}
45 | uses: actions/setup-python@v5
46 | with:
47 | python-version: ${{ matrix.python-version }}
48 | cache: "pip"
49 | # testing
50 | - name: Install 'testing' dependencies
51 | run: |
52 | pip install -r package/requirements.testing.txt
53 | pip install .
54 | - name: Testing
55 | run: pytest .
56 |
--------------------------------------------------------------------------------
/.github/workflows/sast.yaml:
--------------------------------------------------------------------------------
1 | # Static Application Security Testing
2 | name: sast
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: ["master"]
7 | pull_request:
8 | branches: ["master"]
9 | schedule:
10 | - cron: "00 00 * * 0"
11 | jobs:
12 | sast:
13 | permissions:
14 | contents: read # for actions/checkout to fetch code
15 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
16 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: Bandit
21 | uses: mdegis/bandit-action@85fcc340c3b0bf5d86029abb49b9aac916d807b2
22 | with:
23 | # exit with 0, even with results found
24 | # exit_zero: true # optional, default is DEFAULT
25 | # Github token of the repository (automatically created by Github)
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information.
27 | # File or directory to run bandit on
28 | path: ./src/validators # optional, default is .
29 | # Report only issues of a given severity level or higher. Can be LOW, MEDIUM or HIGH. Default is UNDEFINED (everything)
30 | # level: # optional, default is UNDEFINED
31 | # Report only issues of a given confidence level or higher. Can be LOW, MEDIUM or HIGH. Default is UNDEFINED (everything)
32 | # confidence: # optional, default is UNDEFINED
33 | # comma-separated list of paths (glob patterns supported) to exclude from scan (note that these are in addition to the excluded paths provided in the config file) (default: .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg)
34 | excluded_paths: .github,.pytest_cache,.venv,.vscode,site,tests # optional, default is DEFAULT
35 | # comma-separated list of test IDs to skip
36 | # skips: # optional, default is DEFAULT
37 | # path to a .bandit file that supplies command line arguments
38 | # ini_path: # optional, default is DEFAULT
39 | # https://github.com/marketplace/actions/bandit-scan is ISC licensed, by abirismyname
40 | # https://pypi.org/project/bandit/ is Apache v2.0 licensed, by PyCQA
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 | docs/*.1
74 |
75 | # PyBuilder
76 | .pybuilder/
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | # For a library or package, you might want to ignore these files since the code is
88 | # intended to run in multiple environments; otherwise, check them in:
89 | # .python-version
90 |
91 | # pipenv
92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
95 | # install all needed dependencies.
96 | #Pipfile.lock
97 |
98 | # poetry
99 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
100 | # This is especially recommended for binary packages to ensure reproducibility, and is more
101 | # commonly ignored for libraries.
102 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
103 | #poetry.lock
104 |
105 | # pdm
106 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
107 | #pdm.lock
108 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
109 | # in version control.
110 | # https://pdm.fming.dev/#use-with-ide
111 | .pdm.toml
112 | .pdm-python
113 | .pdm-build/
114 |
115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
116 | __pypackages__/
117 |
118 | # Celery stuff
119 | celerybeat-schedule
120 | celerybeat.pid
121 |
122 | # SageMath parsed files
123 | *.sage.py
124 |
125 | # Environments
126 | .env
127 | .venv
128 | env/
129 | venv/
130 | ENV/
131 | env.bak/
132 | venv.bak/
133 | .venv.dev/
134 |
135 | # Spyder project settings
136 | .spyderproject
137 | .spyproject
138 |
139 | # Rope project settings
140 | .ropeproject
141 |
142 | # mkdocs documentation
143 | site/
144 |
145 | # mypy
146 | .mypy_cache/
147 | .dmypy.json
148 | dmypy.json
149 |
150 | # Pyre type checker
151 | .pyre/
152 |
153 | # pytype static type analyzer
154 | .pytype/
155 |
156 | # Cython debug symbols
157 | cython_debug/
158 |
159 | # PyCharm
160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
162 | # and can be added to the global gitignore or merged into this file. For a more nuclear
163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
164 | .idea/
165 |
166 | # VSCode
167 | .vscode/
168 |
169 | # asdf
170 | .tool-versions
171 |
172 | # rtx/mise
173 | .rtx.toml
174 | .mise.toml
175 | mise.toml
176 |
177 | # ruff
178 | .ruff_cache
179 |
180 | # taplo
181 | .taplo.toml
182 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to `validators`
2 |
3 | Hi, to start, you need the following installed on your system.
4 |
5 | 1. [Git](https://git-scm.com)
6 | 2. [Python](https://www.python.org) v3.8 or later
7 | 3. [PDM](https://pdm-project.org) for easy dependency management
8 | 4. (Optional/Recommended) [`NodeJS`](https://nodejs.org/en) for type checking
9 | 5. (Optional/Recommended) [`mise`](https://github.com/jdx/mise) to manage multiple versions of Python, NodeJS and other such tools.
10 |
11 | First [fork this repository](https://github.com/python-validators/validators/fork). (If you are or intend to be a collaborator, uncheck "fork only `master`", because for versioned docs you'll need `gh-pages` branch too.) Clone it to your system and install the development dependencies.
12 |
13 | ```sh
14 | # clone repository
15 | $ git clone "https://github.com/YOUR-USERNAME/validators.git"
16 | # change directory
17 | $ cd validators
18 | # install development dependencies
19 | $ pdm install
20 | ```
21 |
22 | Activate the virtual environment and run `tox` to verify test cases.
23 |
24 | ```sh
25 | # activate virtual environment
26 | $ . ./.venv/bin/activate # replace `/bin/` with `/Scripts/` if you're on Windows.
27 | # run tox for linting, type checking, formatting etc.
28 | $ tox
29 | ```
30 |
31 | Create a git branch and make changes to the source code. If needed, test your changes by running `pytest`. Execute `tox` to format, lint and type-check your code before committing. Commit, push and create a pull request. If you're in doubt, feel free to start a discussion [here](https://github.com/python-validators/validators/discussions).
32 |
33 | ## Documentation
34 |
35 | > Documentation is extracted from the source code. Please follow [Google's Python Doc Style](https://google.github.io/styleguide/pyguide.html).
36 |
37 | If you're adding/removing a module, you must update the `nav` key in `mkdocs.yml`.
38 | Then export documentation as follows:
39 |
40 | ```sh
41 | $ . ./.venv/bin/activate
42 | # generate documentation
43 | $ python package/export TYPE # where TYPE is any of `doc`, `man` or `web`.
44 |
45 | # doc - generates docs found here: https://yozachar.github.io/pyvalidators
46 | # man - generates sphinx based manpages
47 | # web - generates sphinx based web docs
48 | ```
49 |
50 | You can preview the generated documentation:
51 |
52 | ```sh
53 | $ . ./.venv/bin/activate
54 | # To preview mkdocs
55 | $ mkdocs serve
56 | # To preview man pages
57 | $ man docs/_build/man/validators.1
58 | # To preview sphinx webpages
59 | $ python -m http.server -d docs/_build/web
60 | ```
61 |
62 | ## Versioning, Packaging & Releasing (for collaborators)
63 |
64 | > You must be familiar with [semantic versioning](https://semver.org) and [Python packaging](https://packaging.python.org).
65 |
66 | ### Tagging
67 |
68 | 1. Take a look at [`CHANGES.md`](CHANGES.md). They are generated with [GitHub's releaser](https://github.com/python-validators/validators/releases/new), and then modified to fit the shown style.
69 | 2. Update the [changelog](CHANGES.md). Version number must be updated in both [`SECURITY.md`](SECURITY.md) and [`src/validators/__init__.py`](src/validators/__init__.py).
70 | 3. The final merge commit on the upstream (i.e. this repo) is tagged.
71 |
72 | ```sh
73 | # syncing with upstream
74 | $ git pull upstream master
75 | $ git push
76 | # tagging that final merge commit before release
77 | $ GIT_COMMITTER_DATE=$(git log -n1 --pretty=%aD) git tag -a -m "vMAJOR.MINOR.PATCH" MAJOR.MINOR.PATCH
78 | # pushing tag to remote
79 | $ git push --tag
80 | $ git push upstream --tag
81 | ```
82 |
83 | ### Versioned documentation
84 |
85 | 1. To preview versioned docs, run `mike serve` (`mike` is a dev dependency).
86 | 2. Then (look at )
87 | - to publish stable docs run `mike deploy -p -u VERSION stable` after checking out to a stable tag name like `0.28.3` (note: document `VERSION = 0.29 if tag_name == 0.29.1`).
88 | - to publish bleeding-edge docs run `mike deploy -p -u dev master` after checking out to the `master` branch.
89 | 3. This will deploy docs to the `gh-pages` branch (see: )
90 |
91 | ### Packaging and releasing
92 |
93 | 1. Run `./package/roll.sh` (or `./package/roll.ps1`) to generate both `sdist` and `bdist`.
94 | 2. Install [`twine`](https://pypi.org/project/twine) using [`pipx`](https://pipx.pypa.io) to upload package to PyPI.
95 |
96 | ```sh
97 | # publishing
98 | $ twine check dist/*
99 | $ twine upload dist/*
100 | ```
101 |
102 | 3. Create a GitHub release with the contents from the [changelog](CHANGES.md). Upload the wheel from `dist/` along with the shasum file generated with:
103 |
104 | ```sh
105 | # generate sha256sum
106 | $ sha256sum dist/validators-VERSION-py3-none-any.whl > dist/validators-VERSION-py3-none-any.whl.sha256
107 | ```
108 |
109 | ---
110 |
111 | Thank your for taking interest in this library!
112 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 - 2025 Konsta Vesterinen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | # MANIFEST.in stays as long as pyproject.toml does not officially support it
2 | # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
3 |
4 | # include
5 | include CHANGES.md
6 | include mkdocs.yaml
7 |
8 |
9 | # global-include
10 |
11 |
12 | # recursive-include
13 | recursive-include tests *
14 | recursive-include docs *
15 |
16 |
17 | # graft
18 |
19 |
20 | # exclude
21 |
22 |
23 | # global-exclude
24 |
25 |
26 | # recursive-exclude
27 |
28 |
29 | # prune
30 | prune docs/_build
31 | prune **/__pycache__
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # validators - Python Data Validation for Humans™
2 |
3 | [![PyCQA][pycqa-badge]][pycqa-link] [![SAST][sast-badge]][sast-link] [![Docs][docs-badge]][docs-link] [![Version][vs-badge]][vs-link] [![Downloads][dw-badge]][dw-link]
4 |
5 |
6 |
7 | Python has all kinds of data validation tools, but every one of them seems to
8 | require defining a schema or form. I wanted to create a simple validation
9 | library where validating a simple value does not require defining a form or a
10 | schema.
11 |
12 | ```shell
13 | pip install validators
14 | ```
15 |
16 | Then,
17 |
18 | ```python
19 | >>> import validators
20 | >>>
21 | >>> validators.email('someone@example.com')
22 | True
23 | ```
24 |
25 | ## Resources
26 |
27 |
28 |
29 |
30 | - [Documentation](https://yozachar.github.io/pyvalidators)
31 | - [Bugtracker](https://github.com/python-validators/validators/issues)
32 | - [Security](https://github.com/python-validators/validators/blob/master/SECURITY.md)
33 | - [Code](https://github.com/python-validators/validators/)
34 |
35 |
36 |
37 | ---
38 |
39 | > **_Python 3.9 [reaches EOL in](https://endoflife.date/python) October 2025._**
40 |
41 |
42 | [sast-badge]: https://github.com/python-validators/validators/actions/workflows/sast.yaml/badge.svg
43 | [sast-link]: https://github.com/python-validators/validators/actions/workflows/sast.yaml
44 | [pycqa-badge]: https://github.com/python-validators/validators/actions/workflows/pycqa.yaml/badge.svg
45 | [pycqa-link]: https://github.com/python-validators/validators/actions/workflows/pycqa.yaml
46 | [docs-badge]: https://github.com/yozachar/pyvalidators/actions/workflows/pages/pages-build-deployment/badge.svg
47 | [docs-link]: https://github.com/yozachar/pyvalidators/actions/workflows/pages/pages-build-deployment
48 | [vs-badge]: https://img.shields.io/pypi/v/validators?logo=pypi&logoColor=white&label=version&color=blue
49 | [vs-link]: https://pypi.python.org/pypi/validators/
50 | [dw-badge]: https://img.shields.io/pypi/dm/validators?logo=pypi&logoColor=white&color=blue
51 | [dw-link]: https://pypi.python.org/pypi/validators/
52 |
53 |
55 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | | Version | Supported |
6 | | ---------- | ------------------ |
7 | | `>=0.35.0` | :white_check_mark: |
8 |
9 | ## Reporting a Vulnerability
10 |
11 | Please read [CVD](https://resources.sei.cmu.edu/asset_files/SpecialReport/2017_003_001_503340.pdf) before reporting vulnerabilities.
12 |
13 | - We do our best to write safe code.
14 | - [@kvesteri](https://github.com/kvesteri) is the author of `validators`.
15 | - You can raise security concerns [here](https://github.com/python-validators/validators/discussions/categories/security).
16 | - None of us can promise any response time-frame, but we'll try.
17 |
18 | That said, use the package at your own risk. The source code is open, we encourage you to read. _Spammers will be banned._
19 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/api/between.md:
--------------------------------------------------------------------------------
1 | # between
2 |
3 | ::: validators.between.between
4 |
--------------------------------------------------------------------------------
/docs/api/between.rst:
--------------------------------------------------------------------------------
1 | between
2 | -------
3 |
4 | .. module:: validators.between
5 | .. autofunction:: between
6 |
--------------------------------------------------------------------------------
/docs/api/card.md:
--------------------------------------------------------------------------------
1 | # card
2 |
3 | ::: validators.card.amex
4 | ::: validators.card.card_number
5 | ::: validators.card.diners
6 | ::: validators.card.discover
7 | ::: validators.card.jcb
8 | ::: validators.card.mastercard
9 | ::: validators.card.mir
10 | ::: validators.card.unionpay
11 | ::: validators.card.visa
12 |
--------------------------------------------------------------------------------
/docs/api/card.rst:
--------------------------------------------------------------------------------
1 | card
2 | ----
3 |
4 | .. module:: validators.card
5 | .. autofunction:: amex
6 | .. autofunction:: card_number
7 | .. autofunction:: diners
8 | .. autofunction:: discover
9 | .. autofunction:: jcb
10 | .. autofunction:: mastercard
11 | .. autofunction:: mir
12 | .. autofunction:: unionpay
13 | .. autofunction:: visa
14 |
--------------------------------------------------------------------------------
/docs/api/country.md:
--------------------------------------------------------------------------------
1 | # country
2 |
3 | ::: validators.country.calling_code
4 | ::: validators.country.country_code
5 | ::: validators.country.currency
6 |
--------------------------------------------------------------------------------
/docs/api/country.rst:
--------------------------------------------------------------------------------
1 | country
2 | -------
3 |
4 | .. module:: validators.country
5 | .. autofunction:: calling_code
6 | .. autofunction:: country_code
7 | .. autofunction:: currency
8 |
--------------------------------------------------------------------------------
/docs/api/cron.md:
--------------------------------------------------------------------------------
1 | # cron
2 |
3 | ::: validators.cron.cron
4 |
--------------------------------------------------------------------------------
/docs/api/cron.rst:
--------------------------------------------------------------------------------
1 | cron
2 | ----
3 |
4 | .. module:: validators.cron
5 | .. autofunction:: cron
6 |
--------------------------------------------------------------------------------
/docs/api/crypto_addresses.md:
--------------------------------------------------------------------------------
1 | # crypto_addresses
2 |
3 | ::: validators.crypto_addresses.bsc_address
4 | ::: validators.crypto_addresses.btc_address
5 | ::: validators.crypto_addresses.eth_address
6 | ::: validators.crypto_addresses.trx_address
7 |
--------------------------------------------------------------------------------
/docs/api/crypto_addresses.rst:
--------------------------------------------------------------------------------
1 | crypto_addresses
2 | ----------------
3 |
4 | .. module:: validators.crypto_addresses
5 | .. autofunction:: bsc_address
6 | .. autofunction:: btc_address
7 | .. autofunction:: eth_address
8 | .. autofunction:: trx_address
9 |
--------------------------------------------------------------------------------
/docs/api/domain.md:
--------------------------------------------------------------------------------
1 | # domain
2 |
3 | ::: validators.domain.domain
4 |
--------------------------------------------------------------------------------
/docs/api/domain.rst:
--------------------------------------------------------------------------------
1 | domain
2 | ------
3 |
4 | .. module:: validators.domain
5 | .. autofunction:: domain
6 |
--------------------------------------------------------------------------------
/docs/api/email.md:
--------------------------------------------------------------------------------
1 | # email
2 |
3 | ::: validators.email.email
4 |
--------------------------------------------------------------------------------
/docs/api/email.rst:
--------------------------------------------------------------------------------
1 | email
2 | -----
3 |
4 | .. module:: validators.email
5 | .. autofunction:: email
6 |
--------------------------------------------------------------------------------
/docs/api/encoding.md:
--------------------------------------------------------------------------------
1 | # encoding
2 |
3 | ::: validators.encoding.base16
4 | ::: validators.encoding.base32
5 | ::: validators.encoding.base58
6 | ::: validators.encoding.base64
7 |
--------------------------------------------------------------------------------
/docs/api/encoding.rst:
--------------------------------------------------------------------------------
1 | encoding
2 | --------
3 |
4 | .. module:: validators.encoding
5 | .. autofunction:: base16
6 | .. autofunction:: base32
7 | .. autofunction:: base58
8 | .. autofunction:: base64
9 |
--------------------------------------------------------------------------------
/docs/api/finance.md:
--------------------------------------------------------------------------------
1 | # finance
2 |
3 | ::: validators.finance.cusip
4 | ::: validators.finance.isin
5 | ::: validators.finance.sedol
6 |
--------------------------------------------------------------------------------
/docs/api/finance.rst:
--------------------------------------------------------------------------------
1 | finance
2 | -------
3 |
4 | .. module:: validators.finance
5 | .. autofunction:: cusip
6 | .. autofunction:: isin
7 | .. autofunction:: sedol
8 |
--------------------------------------------------------------------------------
/docs/api/hashes.md:
--------------------------------------------------------------------------------
1 | # hashes
2 |
3 | ::: validators.hashes.md5
4 | ::: validators.hashes.sha1
5 | ::: validators.hashes.sha224
6 | ::: validators.hashes.sha256
7 | ::: validators.hashes.sha384
8 | ::: validators.hashes.sha512
9 |
--------------------------------------------------------------------------------
/docs/api/hashes.rst:
--------------------------------------------------------------------------------
1 | hashes
2 | ------
3 |
4 | .. module:: validators.hashes
5 | .. autofunction:: md5
6 | .. autofunction:: sha1
7 | .. autofunction:: sha224
8 | .. autofunction:: sha256
9 | .. autofunction:: sha384
10 | .. autofunction:: sha512
11 |
--------------------------------------------------------------------------------
/docs/api/hostname.md:
--------------------------------------------------------------------------------
1 | # hostname
2 |
3 | ::: validators.hostname.hostname
4 |
--------------------------------------------------------------------------------
/docs/api/hostname.rst:
--------------------------------------------------------------------------------
1 | hostname
2 | --------
3 |
4 | .. module:: validators.hostname
5 | .. autofunction:: hostname
6 |
--------------------------------------------------------------------------------
/docs/api/i18n.md:
--------------------------------------------------------------------------------
1 | # i18n
2 |
3 | ::: validators.i18n.es_cif
4 | ::: validators.i18n.es_doi
5 | ::: validators.i18n.es_nie
6 | ::: validators.i18n.es_nif
7 | ::: validators.i18n.fi_business_id
8 | ::: validators.i18n.fi_ssn
9 | ::: validators.i18n.fr_department
10 | ::: validators.i18n.fr_ssn
11 | ::: validators.i18n.ind_aadhar
12 | ::: validators.i18n.ind_pan
13 | ::: validators.i18n.ru_inn
14 |
--------------------------------------------------------------------------------
/docs/api/i18n.rst:
--------------------------------------------------------------------------------
1 | i18n
2 | ----
3 |
4 | .. module:: validators.i18n
5 | .. autofunction:: es_cif
6 | .. autofunction:: es_doi
7 | .. autofunction:: es_nie
8 | .. autofunction:: es_nif
9 | .. autofunction:: fi_business_id
10 | .. autofunction:: fi_ssn
11 | .. autofunction:: fr_department
12 | .. autofunction:: fr_ssn
13 | .. autofunction:: ind_aadhar
14 | .. autofunction:: ind_pan
15 | .. autofunction:: ru_inn
16 |
--------------------------------------------------------------------------------
/docs/api/iban.md:
--------------------------------------------------------------------------------
1 | # iban
2 |
3 | ::: validators.iban.iban
4 |
--------------------------------------------------------------------------------
/docs/api/iban.rst:
--------------------------------------------------------------------------------
1 | iban
2 | ----
3 |
4 | .. module:: validators.iban
5 | .. autofunction:: iban
6 |
--------------------------------------------------------------------------------
/docs/api/ip_address.md:
--------------------------------------------------------------------------------
1 | # ip_address
2 |
3 | ::: validators.ip_address.ipv4
4 | ::: validators.ip_address.ipv6
5 |
--------------------------------------------------------------------------------
/docs/api/ip_address.rst:
--------------------------------------------------------------------------------
1 | ip_address
2 | ----------
3 |
4 | .. module:: validators.ip_address
5 | .. autofunction:: ipv4
6 | .. autofunction:: ipv6
7 |
--------------------------------------------------------------------------------
/docs/api/length.md:
--------------------------------------------------------------------------------
1 | # length
2 |
3 | ::: validators.length.length
4 |
--------------------------------------------------------------------------------
/docs/api/length.rst:
--------------------------------------------------------------------------------
1 | length
2 | ------
3 |
4 | .. module:: validators.length
5 | .. autofunction:: length
6 |
--------------------------------------------------------------------------------
/docs/api/mac_address.md:
--------------------------------------------------------------------------------
1 | # mac_address
2 |
3 | ::: validators.mac_address.mac_address
4 |
--------------------------------------------------------------------------------
/docs/api/mac_address.rst:
--------------------------------------------------------------------------------
1 | mac_address
2 | -----------
3 |
4 | .. module:: validators.mac_address
5 | .. autofunction:: mac_address
6 |
--------------------------------------------------------------------------------
/docs/api/slug.md:
--------------------------------------------------------------------------------
1 | # slug
2 |
3 | ::: validators.slug.slug
4 |
--------------------------------------------------------------------------------
/docs/api/slug.rst:
--------------------------------------------------------------------------------
1 | slug
2 | ----
3 |
4 | .. module:: validators.slug
5 | .. autofunction:: slug
6 |
--------------------------------------------------------------------------------
/docs/api/url.md:
--------------------------------------------------------------------------------
1 | # url
2 |
3 | ::: validators.url.url
4 |
--------------------------------------------------------------------------------
/docs/api/url.rst:
--------------------------------------------------------------------------------
1 | url
2 | ---
3 |
4 | .. module:: validators.url
5 | .. autofunction:: url
6 |
--------------------------------------------------------------------------------
/docs/api/utils.md:
--------------------------------------------------------------------------------
1 | # utils
2 |
3 | ::: validators.utils.ValidationError
4 | ::: validators.utils.validator
5 |
--------------------------------------------------------------------------------
/docs/api/utils.rst:
--------------------------------------------------------------------------------
1 | utils
2 | -----
3 |
4 | .. module:: validators.utils
5 | .. autofunction:: ValidationError
6 | .. autofunction:: validator
7 |
--------------------------------------------------------------------------------
/docs/api/uuid.md:
--------------------------------------------------------------------------------
1 | # uuid
2 |
3 | ::: validators.uuid.uuid
4 |
--------------------------------------------------------------------------------
/docs/api/uuid.rst:
--------------------------------------------------------------------------------
1 | uuid
2 | ----
3 |
4 | .. module:: validators.uuid
5 | .. autofunction:: uuid
6 |
--------------------------------------------------------------------------------
/docs/assets/icons/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | """Configuration file for the Sphinx documentation builder.
2 |
3 | For the full list of built-in configuration values, see the documentation:
4 | https://www.sphinx-doc.org/en/master/usage/configuration.html
5 | """
6 |
7 | # standard
8 | from datetime import datetime
9 | from importlib.metadata import metadata
10 |
11 | # -- Project information ----------------------------------------------------------
12 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
13 | _metadata = metadata("validators")
14 |
15 | _author_name = _metadata["author-email"].split(" <")[0]
16 | # _author_email = _metadata["author-email"].split(" <")[1].rstrip(">")
17 |
18 | project: str = _metadata["name"]
19 | author: str = _author_name
20 | project_copyright = f"2013 - {datetime.now().year}, {_author_name}"
21 | version: str = _metadata["version"]
22 | release = version
23 |
24 | # -- General configuration ---------------------------------------------------
25 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
26 | extensions = [
27 | "sphinx.ext.autodoc",
28 | "sphinx.ext.napoleon",
29 | "myst_parser",
30 | ]
31 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**/*.md", "*.md"]
32 |
33 |
34 | # -- Options for HTML output -------------------------------------------------
35 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
36 | html_theme = "furo"
37 | html_favicon = "./assets/icons/favicon.svg"
38 |
39 | # -- Options for manpage generation -------------------------------------------------
40 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-man_pages
41 | man_pages = [("index", project, _metadata["summary"], [author], 1)]
42 |
43 | # -- Options for docstring parsing -------------------------------------------------
44 | # https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html
45 | napoleon_numpy_docstring = False
46 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # validators - Python Data Validation for Humans™
2 |
3 | [![PyCQA][pycqa-badge]][pycqa-link] [![SAST][sast-badge]][sast-link] [![Docs][docs-badge]][docs-link] [![Version][vs-badge]][vs-link] [![Downloads][dw-badge]][dw-link]
4 |
5 |
6 |
7 | Python has all kinds of data validation tools, but every one of them seems to
8 | require defining a schema or form. I wanted to create a simple validation
9 | library where validating a simple value does not require defining a form or a
10 | schema.
11 |
12 | ```shell
13 | pip install validators
14 | ```
15 |
16 | Then,
17 |
18 | ```python
19 | >>> import validators
20 | >>>
21 | >>> validators.email('someone@example.com')
22 | True
23 | ```
24 |
25 | ## Resources
26 |
27 |
28 |
29 |
30 | - [Documentation](https://yozachar.github.io/pyvalidators)
31 | - [Bugtracker](https://github.com/python-validators/validators/issues)
32 | - [Security](https://github.com/python-validators/validators/blob/master/SECURITY.md)
33 | - [Code](https://github.com/python-validators/validators/)
34 |
35 |
36 |
37 | ---
38 |
39 | > **_Python 3.9 [reaches EOL in](https://endoflife.date/python) October 2025._**
40 |
41 |
42 | [sast-badge]: https://github.com/python-validators/validators/actions/workflows/sast.yaml/badge.svg
43 | [sast-link]: https://github.com/python-validators/validators/actions/workflows/sast.yaml
44 | [pycqa-badge]: https://github.com/python-validators/validators/actions/workflows/pycqa.yaml/badge.svg
45 | [pycqa-link]: https://github.com/python-validators/validators/actions/workflows/pycqa.yaml
46 | [docs-badge]: https://github.com/yozachar/pyvalidators/actions/workflows/pages/pages-build-deployment/badge.svg
47 | [docs-link]: https://github.com/yozachar/pyvalidators/actions/workflows/pages/pages-build-deployment
48 | [vs-badge]: https://img.shields.io/pypi/v/validators?logo=pypi&logoColor=white&label=version&color=blue
49 | [vs-link]: https://pypi.python.org/pypi/validators/
50 | [dw-badge]: https://img.shields.io/pypi/dm/validators?logo=pypi&logoColor=white&color=blue
51 | [dw-link]: https://pypi.python.org/pypi/validators/
52 |
53 |
55 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | validators - Python Data Validation for Humans™
2 | ===============================================
3 |
4 | |PyCQA| |SAST| |Docs| |Version| |Downloads|
5 |
6 | .. raw:: html
7 |
8 |
9 |
10 | Python has all kinds of data validation tools, but every one of them
11 | seems to require defining a schema or form. I wanted to create a simple
12 | validation library where validating a simple value does not require
13 | defining a form or a schema.
14 |
15 | .. code:: shell
16 |
17 | pip install validators
18 |
19 | Then,
20 |
21 | .. code:: python
22 |
23 | >>> import validators
24 | >>>
25 | >>> validators.email('someone@example.com')
26 | True
27 |
28 | Resources
29 | ---------
30 |
31 | .. raw:: html
32 |
33 |
34 |
35 | .. raw:: html
36 |
37 |
38 |
39 | - `Documentation `__
40 | - `Bugtracker `__
41 | - `Security `__
42 | - `Code `__
43 |
44 | .. raw:: html
45 |
46 |
47 |
48 | --------------
49 |
50 | **Python 3.9** `reaches EOL in `__
51 | **October 2025.**
52 |
53 | .. raw:: html
54 |
55 |
56 |
57 | .. raw:: html
58 |
59 |
61 |
62 | .. |PyCQA| image:: https://github.com/python-validators/validators/actions/workflows/pycqa.yaml/badge.svg
63 | :target: https://github.com/python-validators/validators/actions/workflows/pycqa.yaml
64 | .. |SAST| image:: https://github.com/python-validators/validators/actions/workflows/sast.yaml/badge.svg
65 | :target: https://github.com/python-validators/validators/actions/workflows/sast.yaml
66 | .. |Docs| image:: https://github.com/yozachar/pyvalidators/actions/workflows/pages/pages-build-deployment/badge.svg
67 | :target: https://github.com/yozachar/pyvalidators/actions/workflows/pages/pages-build-deployment
68 | .. |Version| image:: https://img.shields.io/pypi/v/validators?logo=pypi&logoColor=white&label=version&color=blue
69 | :target: https://pypi.python.org/pypi/validators/
70 | .. |Downloads| image:: https://img.shields.io/pypi/dm/validators?logo=pypi&logoColor=white&color=blue
71 | :target: https://pypi.python.org/pypi/validators/
72 |
73 |
74 | .. toctree::
75 | :hidden:
76 | :maxdepth: 2
77 | :caption: Quick Start:
78 | :glob:
79 |
80 | install_and_use
81 |
82 | .. toctree::
83 | :hidden:
84 | :maxdepth: 2
85 | :caption: API Reference:
86 | :glob:
87 |
88 | api/*
89 |
--------------------------------------------------------------------------------
/docs/install_and_use.md:
--------------------------------------------------------------------------------
1 | # Install and Use
2 |
3 | ## Installation
4 |
5 | Execute the following command:
6 |
7 | ```text
8 | pip install validators
9 | ```
10 |
11 | > It's preferable to use `pip` within a virtual environment.
12 |
13 | ## Usage
14 |
15 | ```python
16 | import validators
17 | print(validators.email('someone@example.com'))
18 | ```
19 |
20 | ### To raise validation error
21 |
22 | 1. Either set the environment variable `RAISE_VALIDATION_ERROR` to `True`
23 |
24 | ```console
25 | $ export RAISE_VALIDATION_ERROR=True
26 | $ python -c "from validators import url; print(url('https//bad_url'))"
27 | Traceback (most recent call last):
28 | File "", line 1, in
29 | File "/path/to/lib/validators/utils.py", line 87, in wrapper
30 | raise ValidationError(func, _func_args_as_dict(func, *args, **kwargs))
31 | validators.utils.ValidationError: ValidationError(func=url, args={'value': 'https//bad_url'})
32 | ```
33 |
34 | 2. Or pass `r_ve=True` to each caller function:
35 |
36 | ```console
37 | $ python -c "from validators.card import visa; print(visa('bad_visa_number', r_ve=True))"
38 | Traceback (most recent call last):
39 | File "", line 1, in
40 | File "/path/to/lib/validators/utils.py", line 87, in wrapper
41 | raise ValidationError(func, _func_args_as_dict(func, *args, **kwargs))
42 | validators.utils.ValidationError: ValidationError(func=visa, args={'value': 'bad_visa_number'})
43 | ```
44 |
--------------------------------------------------------------------------------
/docs/install_and_use.rst:
--------------------------------------------------------------------------------
1 | Install and Use
2 | ===============
3 |
4 | Installation
5 | ------------
6 |
7 | Execute the following command:
8 |
9 | .. code:: text
10 |
11 | pip install validators
12 |
13 | ..
14 |
15 | It's preferable to use ``pip`` within a virtual environment.
16 |
17 | Usage
18 | -----
19 |
20 | .. code:: python
21 |
22 | import validators
23 | print(validators.email('someone@example.com'))
24 |
25 | To raise validation error
26 | ~~~~~~~~~~~~~~~~~~~~~~~~~
27 |
28 | 1. Either set the environment variable ``RAISE_VALIDATION_ERROR`` to
29 | ``True``
30 |
31 | .. code:: console
32 |
33 | $ export RAISE_VALIDATION_ERROR=True
34 | $ python -c "from validators import url; print(url('https//bad_url'))"
35 | Traceback (most recent call last):
36 | File "", line 1, in
37 | File "/path/to/lib/validators/utils.py", line 87, in wrapper
38 | raise ValidationError(func, _func_args_as_dict(func, *args, **kwargs))
39 | validators.utils.ValidationError: ValidationError(func=url, args={'value': 'https//bad_url'})
40 |
41 | 2. Or pass ``r_ve=True`` to each caller function:
42 |
43 | .. code:: console
44 |
45 | $ python -c "from validators.card import visa; print(visa('bad_visa_number', r_ve=True))"
46 | Traceback (most recent call last):
47 | File "", line 1, in
48 | File "/path/to/lib/validators/utils.py", line 87, in wrapper
49 | raise ValidationError(func, _func_args_as_dict(func, *args, **kwargs))
50 | validators.utils.ValidationError: ValidationError(func=visa, args={'value': 'bad_visa_number'})
51 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/mkdocs.yaml:
--------------------------------------------------------------------------------
1 | site_name: "validators"
2 | site_description: "Automatic documentation from sources, for MkDocs."
3 | site_url: "https://yozachar.github.io/pyvalidators/"
4 | repo_url: "https://github.com/python-validators/validators/"
5 | edit_uri: "edit/master/docs/"
6 | repo_name: "validators/validators"
7 | site_dir: "site"
8 | watch: [README.md, src/validators/]
9 |
10 | theme:
11 | name: material
12 | icon:
13 | logo: material/marker-check
14 | favicon: assets/icons/favicon.svg
15 | font:
16 | text: Inter
17 | code: "Fira Code"
18 | features:
19 | - content.code.copy
20 | palette:
21 | - media: "(prefers-color-scheme: light)"
22 | scheme: default
23 | primary: white
24 | accent: teal
25 | toggle:
26 | icon: material/toggle-switch
27 | name: Switch to dark mode
28 | - media: "(prefers-color-scheme: dark)"
29 | scheme: slate
30 | primary: black
31 | accent: teal
32 | toggle:
33 | icon: material/toggle-switch-off-outline
34 | name: Switch to light mode
35 |
36 | markdown_extensions:
37 | - toc:
38 | permalink: true
39 | - pymdownx.superfences
40 | - pymdownx.highlight:
41 | use_pygments: true
42 | - pymdownx.inlinehilite
43 |
44 | plugins:
45 | - search
46 | - mkdocstrings:
47 | handlers:
48 | python:
49 | options:
50 | show_root_heading: true
51 | import:
52 | - https://docs.python-requests.org/en/master/objects.inv
53 | - git-revision-date-localized
54 | - mike
55 |
56 | extra:
57 | social:
58 | - icon: fontawesome/brands/github
59 | link: https://github.com/python-validators
60 | - icon: fontawesome/brands/python
61 | link: https://pypi.org/project/validators
62 | version:
63 | provider: mike
64 | default: stable
65 |
66 | copyright: Copyright © 2013 - 2025 Konsta Vesterinen
67 |
68 | nav:
69 | - Home: index.md
70 | - Install and Use: install_and_use.md
71 | - API:
72 | - api/between.md
73 | - api/crypto_addresses.md
74 | - api/card.md
75 | - api/country.md
76 | - api/cron.md
77 | - api/domain.md
78 | - api/email.md
79 | - api/encoding.md
80 | - api/finance.md
81 | - api/hashes.md
82 | - api/hostname.md
83 | - api/i18n.md
84 | - api/iban.md
85 | - api/ip_address.md
86 | - api/length.md
87 | - api/mac_address.md
88 | - api/slug.md
89 | - api/url.md
90 | - api/utils.md
91 | - api/uuid.md
92 |
--------------------------------------------------------------------------------
/package/export/__init__.py:
--------------------------------------------------------------------------------
1 | """Export."""
2 |
--------------------------------------------------------------------------------
/package/export/__main__.py:
--------------------------------------------------------------------------------
1 | """Generate docs."""
2 |
3 | # standard
4 | from ast import ImportFrom, parse
5 | from os import getenv
6 | from os.path import getsize
7 | from pathlib import Path
8 | from shutil import copy
9 | from subprocess import Popen # nosec
10 |
11 |
12 | def _write_ref_content(source: Path, module_name: str, func_name: str):
13 | """Write content."""
14 | with open(source, "at") as ref:
15 | ref.write(
16 | (
17 | (f"# {module_name}\n\n" if getsize(source) == 0 else "")
18 | + f"::: validators.{module_name}.{func_name}\n"
19 | )
20 | if f"{source}".endswith(".md")
21 | else (
22 | (
23 | f"{module_name}\n{len(module_name) * '-'}\n\n"
24 | + f".. module:: validators.{module_name}\n"
25 | if getsize(source) == 0
26 | else ""
27 | )
28 | + f".. autofunction:: {func_name}\n"
29 | )
30 | )
31 |
32 |
33 | def _parse_package(source: Path):
34 | """Parse validators package."""
35 | v_ast = parse(source.read_text(), source)
36 | for namespace in (node for node in v_ast.body if isinstance(node, ImportFrom)):
37 | if not namespace.module:
38 | continue
39 | yield (namespace.module, namespace.names)
40 |
41 |
42 | def _gen_md_docs(source: Path, refs_path: Path):
43 | """Generate Markdown docs."""
44 | # remove existing markdown files
45 | for md_files in (source / "docs/api").glob("*.md"):
46 | md_files.unlink()
47 | # generate md reference documentation
48 | for module_name, aliases in _parse_package(source / "src/validators/__init__.py"):
49 | for alias in aliases:
50 | _write_ref_content(refs_path / f"{module_name}.md", module_name, alias.name)
51 | # build mkdocs as subprocess
52 | mkdocs_build = Popen(("mkdocs", "build")) # nosec
53 | mkdocs_build.communicate()
54 | return mkdocs_build.returncode
55 |
56 |
57 | def _gen_rst_docs(source: Path, refs_path: Path, only_web: bool = False, only_man: bool = False):
58 | """Generate reStructuredText docs."""
59 | # external
60 | from pypandoc import convert_file # type: ignore
61 |
62 | # remove existing rST files
63 | for rst_files in (source / "docs/api").glob("*.rst"):
64 | rst_files.unlink()
65 |
66 | with open(source / "docs/index.rst", "wt") as idx_f:
67 | idx_f.write(
68 | convert_file(source_file=source / "docs/index.md", format="md", to="rst").replace(
69 | "\r\n",
70 | "\n", # remove carriage return in windows
71 | )
72 | + "\n\n.. toctree::"
73 | + "\n :hidden:"
74 | + "\n :maxdepth: 2"
75 | + "\n :caption: Quick Start:"
76 | + "\n :glob:\n"
77 | + "\n install_and_use"
78 | + "\n\n.. toctree::"
79 | + "\n :hidden:"
80 | + "\n :maxdepth: 2"
81 | + "\n :caption: API Reference:"
82 | + "\n :glob:\n"
83 | + "\n api/*\n"
84 | )
85 |
86 | with open(source / "docs/install_and_use.rst", "wt") as iau_f:
87 | iau_f.write(
88 | convert_file(source_file=source / "docs/install_and_use.md", format="md", to="rst")
89 | .replace("\r\n", "\n") # remove carriage return in windows
90 | .replace("’", "'")
91 | )
92 |
93 | # generate rST reference documentation
94 | for module_name, aliases in _parse_package(source / "src/validators/__init__.py"):
95 | for alias in aliases:
96 | _write_ref_content(refs_path / f"{module_name}.rst", module_name, alias.name)
97 | exit_code = 0
98 | if not only_man:
99 | # build sphinx web pages as subprocess
100 | web_build = Popen(("sphinx-build", "docs", "docs/_build/web"), shell=False) # nosec
101 | web_build.communicate()
102 | exit_code = web_build.returncode
103 | print("Run `python -m http.server -d docs/_build/web` to preview.")
104 | if not only_web:
105 | # build sphinx man pages as subprocess
106 | man_build = Popen( # nosec
107 | ("sphinx-build", "-b", "man", "docs", "docs/_build/man"), shell=False
108 | )
109 | man_build.communicate()
110 | copy(source / "docs/_build/man/validators.1", source / "docs/validators.1")
111 | print(f"Man page copied to: {source / 'docs/validators.1'}")
112 | exit_code = man_build.returncode if exit_code == 0 else exit_code
113 | return exit_code
114 |
115 |
116 | def _generate_documentation(
117 | source: Path,
118 | only_md: bool = False,
119 | only_rst_web: bool = False,
120 | only_rst_man: bool = False,
121 | ):
122 | """Generate documentation."""
123 | if only_md is only_rst_web is only_rst_man is True:
124 | return
125 | if only_md is only_rst_web is only_rst_man is False:
126 | return
127 | # copy readme as docs index file
128 | copy(source / "README.md", source / "docs/index.md")
129 | # clean destination
130 | refs_path = source / "docs/api"
131 | # if refs_path.is_dir():
132 | # rmtree(refs_path)
133 | refs_path.mkdir(exist_ok=True)
134 | exit_code = 0 if (only_rst_web or only_rst_man) else _gen_md_docs(source, refs_path)
135 | if not only_md:
136 | exit_code = (
137 | _gen_rst_docs(source, refs_path, only_rst_web, only_rst_man)
138 | if exit_code == 0
139 | else exit_code
140 | )
141 | return exit_code
142 |
143 |
144 | def package(source: Path):
145 | """Package the source code."""
146 | _generate_documentation(source, only_rst_man=True)
147 | if getenv("CI", "false") == "true":
148 | process = Popen(("./.venv/bin/python", "-m", "build"), shell=False) # nosec
149 | else:
150 | process = Popen(("pdm", "build"), shell=False) # nosec
151 | process.communicate()
152 | return process.returncode
153 |
154 |
155 | if __name__ == "__main__":
156 | project_root = Path(__file__).parent.parent.parent
157 | exit_code = 0
158 |
159 | # standard
160 | from sys import argv
161 |
162 | if len(argv) != 2:
163 | print("Expected one of these augments: `pkg` `doc` `man` or `web`")
164 | quit(1)
165 |
166 | if argv[1] == "pkg":
167 | exit_code = package(project_root)
168 | elif argv[1] == "doc":
169 | exit_code = _generate_documentation(project_root, only_md=True)
170 | elif argv[1] == "man":
171 | exit_code = _generate_documentation(project_root, only_rst_man=True)
172 | elif argv[1] == "web":
173 | exit_code = _generate_documentation(project_root, only_rst_web=True)
174 | quit(exit_code)
175 |
176 | # TODO: Address all '# nosec'
177 |
--------------------------------------------------------------------------------
/package/requirements.testing.txt:
--------------------------------------------------------------------------------
1 | # This file is @generated by PDM.
2 | # Please do not edit it manually.
3 |
4 | colorama==0.4.6 \
5 | --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
6 | --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
7 | eth-hash[pycryptodome]==0.7.0 \
8 | --hash=sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f \
9 | --hash=sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a
10 | exceptiongroup==1.2.1; python_version < "3.11" \
11 | --hash=sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad \
12 | --hash=sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16
13 | iniconfig==2.0.0 \
14 | --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
15 | --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
16 | packaging==24.1 \
17 | --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
18 | --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
19 | pluggy==1.5.0 \
20 | --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \
21 | --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669
22 | pycryptodome==3.20.0 \
23 | --hash=sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7 \
24 | --hash=sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4 \
25 | --hash=sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5 \
26 | --hash=sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab \
27 | --hash=sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a \
28 | --hash=sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25 \
29 | --hash=sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea \
30 | --hash=sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a \
31 | --hash=sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c \
32 | --hash=sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72 \
33 | --hash=sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9 \
34 | --hash=sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6 \
35 | --hash=sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044 \
36 | --hash=sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04 \
37 | --hash=sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c \
38 | --hash=sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e \
39 | --hash=sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b \
40 | --hash=sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e \
41 | --hash=sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2 \
42 | --hash=sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3 \
43 | --hash=sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128
44 | pytest==8.3.2 \
45 | --hash=sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5 \
46 | --hash=sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce
47 | tomli==2.0.1; python_version < "3.11" \
48 | --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
49 | --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
50 |
--------------------------------------------------------------------------------
/package/requirements.tooling.txt:
--------------------------------------------------------------------------------
1 | # This file is @generated by PDM.
2 | # Please do not edit it manually.
3 |
4 | colorama==0.4.6 \
5 | --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
6 | --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
7 | eth-hash[pycryptodome]==0.7.0 \
8 | --hash=sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f \
9 | --hash=sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a
10 | exceptiongroup==1.2.1; python_version < "3.11" \
11 | --hash=sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad \
12 | --hash=sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16
13 | iniconfig==2.0.0 \
14 | --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
15 | --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
16 | nodeenv==1.9.1 \
17 | --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
18 | --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
19 | packaging==24.1 \
20 | --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
21 | --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
22 | pluggy==1.5.0 \
23 | --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \
24 | --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669
25 | pycryptodome==3.20.0 \
26 | --hash=sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7 \
27 | --hash=sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4 \
28 | --hash=sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5 \
29 | --hash=sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab \
30 | --hash=sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a \
31 | --hash=sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25 \
32 | --hash=sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea \
33 | --hash=sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a \
34 | --hash=sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c \
35 | --hash=sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72 \
36 | --hash=sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9 \
37 | --hash=sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6 \
38 | --hash=sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044 \
39 | --hash=sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04 \
40 | --hash=sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c \
41 | --hash=sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e \
42 | --hash=sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b \
43 | --hash=sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e \
44 | --hash=sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2 \
45 | --hash=sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3 \
46 | --hash=sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128
47 | pypandoc-binary==1.13 \
48 | --hash=sha256:11a2497320eb3dccb74de3c67b6df3e5d3f66cdc2a36a67e9a871708f7e48412 \
49 | --hash=sha256:21ef0345726d36fc45a50211320614daf2caede684b0d0963ce8738292809746 \
50 | --hash=sha256:2915f52e4632bd2d0a8fcd2f7e7dfc2ea19b4e1a280fcbc2ddcd142713c4ff12 \
51 | --hash=sha256:3881aa7c84faec2007c0ae4466d3a1cfc93171206b8540f2defa8ea971bf6fd6 \
52 | --hash=sha256:67c0c7af811bcf3cd4f3221be756a4975ec35b2d7df89d8de4313a8caa2cd54f \
53 | --hash=sha256:9455fdd9521cbf4b56d79a56b806afa94c8c22f3c8ef878536e58d941a70f6d6 \
54 | --hash=sha256:946666388eb79b307d7f497b3b33045ef807750f8e5ef3440e0ba3bbab698044
55 | pyright==1.1.378 \
56 | --hash=sha256:78a043be2876d12d0af101d667e92c7734f3ebb9db71dccc2c220e7e7eb89ca2 \
57 | --hash=sha256:8853776138b01bc284da07ac481235be7cc89d3176b073d2dba73636cb95be79
58 | pytest==8.3.2 \
59 | --hash=sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5 \
60 | --hash=sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce
61 | ruff==0.6.3 \
62 | --hash=sha256:14a9528a8b70ccc7a847637c29e56fd1f9183a9db743bbc5b8e0c4ad60592a82 \
63 | --hash=sha256:183b99e9edd1ef63be34a3b51fee0a9f4ab95add123dbf89a71f7b1f0c991983 \
64 | --hash=sha256:34e2824a13bb8c668c71c1760a6ac7d795ccbd8d38ff4a0d8471fdb15de910b1 \
65 | --hash=sha256:3b061e49b5cf3a297b4d1c27ac5587954ccb4ff601160d3d6b2f70b1622194dc \
66 | --hash=sha256:42844ff678f9b976366b262fa2d1d1a3fe76f6e145bd92c84e27d172e3c34500 \
67 | --hash=sha256:47021dff5445d549be954eb275156dfd7c37222acc1e8014311badcb9b4ec8c1 \
68 | --hash=sha256:500f166d03fc6d0e61c8e40a3ff853fa8a43d938f5d14c183c612df1b0d6c58a \
69 | --hash=sha256:65a533235ed55f767d1fc62193a21cbf9e3329cf26d427b800fdeacfb77d296f \
70 | --hash=sha256:70452a10eb2d66549de8e75f89ae82462159855e983ddff91bc0bce6511d0470 \
71 | --hash=sha256:746af39356fee2b89aada06c7376e1aa274a23493d7016059c3a72e3b296befb \
72 | --hash=sha256:7a62d3b5b0d7f9143d94893f8ba43aa5a5c51a0ffc4a401aa97a81ed76930521 \
73 | --hash=sha256:7d7bd20dc07cebd68cc8bc7b3f5ada6d637f42d947c85264f94b0d1cd9d87384 \
74 | --hash=sha256:97f58fda4e309382ad30ede7f30e2791d70dd29ea17f41970119f55bdb7a45c3 \
75 | --hash=sha256:bddfbb8d63c460f4b4128b6a506e7052bad4d6f3ff607ebbb41b0aa19c2770d1 \
76 | --hash=sha256:ced3eeb44df75353e08ab3b6a9e113b5f3f996bea48d4f7c027bc528ba87b672 \
77 | --hash=sha256:d2e2c23cef30dc3cbe9cc5d04f2899e7f5e478c40d2e0a633513ad081f7361b5 \
78 | --hash=sha256:d8a136aa7d228975a6aee3dd8bea9b28e2b43e9444aa678fb62aeb1956ff2351 \
79 | --hash=sha256:f92fe93bc72e262b7b3f2bba9879897e2d58a989b4714ba6a5a7273e842ad2f8
80 | tomli==2.0.1; python_version < "3.11" \
81 | --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
82 | --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
83 |
--------------------------------------------------------------------------------
/package/roll.ps1:
--------------------------------------------------------------------------------
1 | #!/bin/pwsh
2 |
3 | $ErrorActionPreference = "Stop"
4 |
5 | # Check if CI environment variable is set to "false"
6 | if ($null -eq $env:CI || "false" -eq $env:CI) {
7 | # testing
8 | pdm export --group testing,crypto-eth-addresses -f requirements -o package/requirements.testing.txt
9 | # tooling
10 | pdm export --group tooling,crypto-eth-addresses -f requirements -o package/requirements.tooling.txt
11 | # mkdocs
12 | # pdm export --group docs-online -f requirements -o package/requirements.mkdocs.txt
13 | # sphinx
14 | pdm export --group docs-offline,crypto-eth-addresses -f requirements -o package/requirements.sphinx.txt
15 |
16 | # create environment variable
17 | $env:CI = "true";
18 | }
19 |
20 | # Cleanup directories
21 | $venv_dir = "./.venv.dev"
22 | $directories = @($venv_dir, "./build", "./dist")
23 | foreach ($dir in $directories) {
24 | if (Test-Path $dir -PathType Container) {
25 | Remove-Item $dir -Recurse -Force
26 | }
27 | }
28 |
29 | # Create venv
30 | python -m venv $venv_dir
31 |
32 |
33 | $bin_path = "Scripts"
34 | if ($IsLinux || $IsMacOS) {
35 | $bin_path = "bin"
36 | }
37 |
38 | # Upgrade pip
39 | & $venv_dir\$bin_path\python -m pip install --upgrade pip
40 |
41 | # Install the current package
42 | & $venv_dir\$bin_path\pip install .
43 |
44 | # Install sphinx requirements
45 | & $venv_dir\$bin_path\pip install -r package/requirements.sphinx.txt
46 |
47 | # Install build tool
48 | & $venv_dir\$bin_path\pip install build
49 |
50 | # Activate virtual environment
51 | . $venv_dir\$bin_path\Activate.ps1
52 |
53 | # Run export script
54 | python package/export pkg
55 |
56 | # Deactivate virtual environment
57 | deactivate
58 |
59 | # delete environment variable
60 | $env:CI = "";
61 |
--------------------------------------------------------------------------------
/package/roll.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | # Check if CI environment variable is set to "false"
6 | # or set to empty string or not set at all.
7 | # Using the wrong way see: https://stackoverflow.com/a/13864829
8 | if [ -z "$CI" ] || [ "$CI" = "false" ]; then
9 | # testing
10 | pdm export --group testing,crypto-eth-addresses -f requirements -o package/requirements.testing.txt
11 | # tooling
12 | pdm export --group tooling,crypto-eth-addresses -f requirements -o package/requirements.tooling.txt
13 | # mkdocs
14 | # pdm export --group docs-online -f requirements -o package/requirements.mkdocs.txt
15 | # sphinx
16 | pdm export --group docs-offline,crypto-eth-addresses -f requirements -o package/requirements.sphinx.txt
17 | export CI=true
18 | fi
19 |
20 | # Cleanup directories
21 | venv_dir="./.venv.dev"
22 | directories=("$venv_dir" "./build" "./dist")
23 | for dir in "${directories[@]}"; do
24 | if [ -d "$dir" ]; then
25 | rm -rf "$dir"
26 | fi
27 | done
28 |
29 | # Create venv
30 | python -m venv $venv_dir
31 |
32 | # Upgrade pip
33 | $venv_dir/bin/python -m pip install --upgrade pip
34 |
35 | # Install the current package
36 | $venv_dir/bin/pip install .
37 |
38 | # Install sphinx requirements
39 | $venv_dir/bin/pip install -r package/requirements.sphinx.txt
40 |
41 | # Install build tool
42 | $venv_dir/bin/pip install build
43 |
44 | # Activate virtual environment
45 | . $venv_dir/bin/activate
46 |
47 | # Run export script
48 | python package/export pkg
49 |
50 | # Deactivate virtual environment
51 | deactivate
52 |
53 | # delete environment variable
54 | unset CI
55 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | ####################
2 | # Build System #
3 | ####################
4 |
5 | [build-system]
6 | requires = ["setuptools"]
7 | build-backend = "setuptools.build_meta"
8 |
9 | ####################
10 | # Metadata #
11 | ####################
12 |
13 | [project]
14 | name = "validators"
15 | description = "Python Data Validation for Humans™"
16 | authors = [{ name = "Konsta Vesterinen", email = "konsta@fastmonkeys.com" }]
17 | license = { text = "MIT" }
18 | readme = "README.md"
19 | keywords = ["validation", "validator", "python-validator"]
20 | classifiers = [
21 | "Development Status :: 4 - Beta",
22 | "Environment :: Web Environment",
23 | "Intended Audience :: Developers",
24 | "License :: OSI Approved :: MIT License",
25 | "Operating System :: OS Independent",
26 | "Programming Language :: Python",
27 | "Programming Language :: Python :: 3 :: Only",
28 | "Programming Language :: Python :: 3.9",
29 | "Programming Language :: Python :: 3.10",
30 | "Programming Language :: Python :: 3.11",
31 | "Programming Language :: Python :: 3.12",
32 | "Programming Language :: Python :: 3.13",
33 | "Programming Language :: Python :: Implementation :: CPython",
34 | "Topic :: Software Development :: Libraries :: Python Modules",
35 | ]
36 | requires-python = ">=3.9"
37 | dynamic = ["version"]
38 | dependencies = []
39 |
40 | [project.urls]
41 | Homepage = "https://python-validators.github.io/validators"
42 | Documentation = "https://yozachar.github.io/pyvalidators"
43 | Repository = "https://github.com/python-validators/validators"
44 | Changelog = "https://github.com/python-validators/validators/blob/master/CHANGES.md"
45 |
46 | ###########################
47 | # Optional Dependencies #
48 | ###########################
49 |
50 | [project.optional-dependencies]
51 | crypto-eth-addresses = ["eth-hash[pycryptodome]>=0.7.0"]
52 |
53 | ##############################
54 | # Development Dependencies #
55 | ##############################
56 |
57 | [tool.pdm.dev-dependencies]
58 | docs-offline = [
59 | "myst-parser>=3.0.1",
60 | "pypandoc-binary>=1.13",
61 | "sphinx>=7.1.2",
62 | "furo>=2024.8.6",
63 | ]
64 | docs-online = [
65 | "mkdocs>=1.6.1",
66 | "mkdocs-git-revision-date-localized-plugin>=1.2.7",
67 | "mkdocs-material>=9.5.34",
68 | "mkdocstrings[python]>=0.26.0",
69 | "mike>=2.1.3",
70 | ]
71 | package = ["build>=1.2.1"]
72 | runner = ["tox>=4.18.0"]
73 | sast = ["bandit[toml]>=1.7.9"]
74 | testing = ["pytest>=8.3.2"]
75 | tooling = [
76 | "ruff>=0.6.3",
77 | "pyright>=1.1.378",
78 | "pytest>=8.3.2",
79 | "pypandoc-binary>=1.13", # helps with type checking
80 | ]
81 |
82 | ####################
83 | # Configurations #
84 | ####################
85 |
86 | [tool.setuptools.packages.find]
87 | where = ["src"]
88 | include = ["validators*"]
89 | namespaces = false
90 |
91 | [tool.setuptools.package-data]
92 | validators = ["py.typed", "_tld.txt"]
93 |
94 | [tool.setuptools.dynamic]
95 | version = { attr = "validators.__version__" }
96 |
97 | [tool.bandit]
98 | exclude_dirs = [
99 | ".github",
100 | ".pytest_cache",
101 | ".tox",
102 | ".venv",
103 | ".venv.dev",
104 | ".vscode",
105 | "site",
106 | "tests",
107 | ]
108 |
109 | [tool.pyright]
110 | extraPaths = ["src"]
111 | exclude = [
112 | "**/__pycache__/",
113 | ".pytest_cache/",
114 | ".tox/",
115 | ".venv/",
116 | ".venv.dev/",
117 | "site/",
118 | ]
119 | pythonVersion = "3.9"
120 | pythonPlatform = "All"
121 | typeCheckingMode = "strict"
122 |
123 | [tool.pytest.ini_options]
124 | minversion = "6.0"
125 | pythonpath = ["src"]
126 | testpaths = "tests"
127 | addopts = ["--doctest-modules"]
128 |
129 |
130 | [tool.ruff]
131 | lint.select = [
132 | # Pyflakes
133 | "F",
134 | # pycodestyle
135 | "W",
136 | "E",
137 | # mccabe
138 | # C90
139 | # isort
140 | "I",
141 | # pep8-naming
142 | "N",
143 | # pydocstyle
144 | "D",
145 | ]
146 | line-length = 100
147 | target-version = "py39"
148 | extend-exclude = ["**/__pycache__", ".pytest_cache", "site"]
149 |
150 | [tool.ruff.lint.isort]
151 | # case-sensitive = true
152 | combine-as-imports = true
153 | force-sort-within-sections = true
154 | force-wrap-aliases = true
155 | known-local-folder = ["src"]
156 | relative-imports-order = "closest-to-furthest"
157 |
158 | [tool.ruff.lint.pydocstyle]
159 | convention = "google"
160 |
161 | [tool.tox]
162 | legacy_tox_ini = """
163 | [tox]
164 | requires =
165 | tox>=4
166 | env_list = lint, type, format, sast, py{39,310,311,312,313}
167 |
168 | [testenv:lint]
169 | description = ruff linter
170 | deps =
171 | ruff
172 | commands = ruff check .
173 |
174 | [testenv:type]
175 | description = pyright type checker
176 | deps =
177 | pyright
178 | pypandoc-binary
179 | pytest
180 | .[crypto-eth-addresses]
181 | commands = pyright .
182 |
183 | [testenv:format]
184 | description = code formatter
185 | deps =
186 | ruff
187 | commands = ruff format .
188 |
189 | [testenv:sast]
190 | deps =
191 | bandit[toml]
192 | commands = bandit -c pyproject.toml -r .
193 |
194 | [testenv]
195 | description = unit tests
196 | deps =
197 | pytest
198 | .[crypto-eth-addresses]
199 | commands = pytest .
200 | """
201 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
1 | """Validators."""
2 |
--------------------------------------------------------------------------------
/src/validators/__init__.py:
--------------------------------------------------------------------------------
1 | """Validate Anything!"""
2 |
3 | # local
4 | from .between import between
5 | from .card import amex, card_number, diners, discover, jcb, mastercard, mir, unionpay, visa
6 | from .country import calling_code, country_code, currency
7 | from .cron import cron
8 | from .crypto_addresses import bsc_address, btc_address, eth_address, trx_address
9 | from .domain import domain
10 | from .email import email
11 | from .encoding import base16, base32, base58, base64
12 | from .finance import cusip, isin, sedol
13 | from .hashes import md5, sha1, sha224, sha256, sha384, sha512
14 | from .hostname import hostname
15 | from .i18n import (
16 | es_cif,
17 | es_doi,
18 | es_nie,
19 | es_nif,
20 | fi_business_id,
21 | fi_ssn,
22 | fr_department,
23 | fr_ssn,
24 | ind_aadhar,
25 | ind_pan,
26 | ru_inn,
27 | )
28 | from .iban import iban
29 | from .ip_address import ipv4, ipv6
30 | from .length import length
31 | from .mac_address import mac_address
32 | from .slug import slug
33 | from .url import url
34 | from .utils import ValidationError, validator
35 | from .uuid import uuid
36 |
37 | __all__ = (
38 | # ...
39 | "between",
40 | # crypto_addresses
41 | "bsc_address",
42 | "btc_address",
43 | "eth_address",
44 | "trx_address",
45 | # cards
46 | "amex",
47 | "card_number",
48 | "diners",
49 | "discover",
50 | "jcb",
51 | "mastercard",
52 | "unionpay",
53 | "visa",
54 | "mir",
55 | # country
56 | "calling_code",
57 | "country_code",
58 | "currency",
59 | # ...
60 | "cron",
61 | # ...
62 | "domain",
63 | # ...
64 | "email",
65 | # encodings
66 | "base16",
67 | "base32",
68 | "base58",
69 | "base64",
70 | # finance
71 | "cusip",
72 | "isin",
73 | "sedol",
74 | # hashes
75 | "md5",
76 | "sha1",
77 | "sha224",
78 | "sha256",
79 | "sha384",
80 | "sha512",
81 | # ...
82 | "hostname",
83 | # i18n
84 | "es_cif",
85 | "es_doi",
86 | "es_nie",
87 | "es_nif",
88 | "fi_business_id",
89 | "fi_ssn",
90 | "fr_department",
91 | "fr_ssn",
92 | "ind_aadhar",
93 | "ind_pan",
94 | "ru_inn",
95 | # ...
96 | "iban",
97 | # ip_addresses
98 | "ipv4",
99 | "ipv6",
100 | # ...
101 | "length",
102 | # ...
103 | "mac_address",
104 | # ...
105 | "slug",
106 | # ...
107 | "url",
108 | # ...
109 | "uuid",
110 | # utils
111 | "ValidationError",
112 | "validator",
113 | )
114 |
115 | __version__ = "0.35.0"
116 |
--------------------------------------------------------------------------------
/src/validators/_extremes.py:
--------------------------------------------------------------------------------
1 | """Extremes."""
2 |
3 | # standard
4 | from functools import total_ordering
5 | from typing import Any
6 |
7 |
8 | @total_ordering
9 | class AbsMax:
10 | """An object that is greater than any other object (except itself).
11 |
12 | Inspired by https://pypi.python.org/pypi/Extremes.
13 |
14 | Examples:
15 | >>> from sys import maxsize
16 | >>> AbsMax() > AbsMin()
17 | True
18 | >>> AbsMax() > maxsize
19 | True
20 | >>> AbsMax() > 99999999999999999
21 | True
22 | """
23 |
24 | def __ge__(self, other: Any):
25 | """GreaterThanOrEqual."""
26 | return other is not AbsMax
27 |
28 |
29 | @total_ordering
30 | class AbsMin:
31 | """An object that is less than any other object (except itself).
32 |
33 | Inspired by https://pypi.python.org/pypi/Extremes.
34 |
35 | Examples:
36 | >>> from sys import maxsize
37 | >>> AbsMin() < -maxsize
38 | True
39 | >>> AbsMin() < None
40 | True
41 | >>> AbsMin() < ''
42 | True
43 | """
44 |
45 | def __le__(self, other: Any):
46 | """LessThanOrEqual."""
47 | return other is not AbsMin
48 |
--------------------------------------------------------------------------------
/src/validators/between.py:
--------------------------------------------------------------------------------
1 | """Between."""
2 |
3 | # standard
4 | from datetime import datetime
5 | from typing import TypeVar, Union
6 |
7 | # local
8 | from ._extremes import AbsMax, AbsMin
9 | from .utils import validator
10 |
11 | PossibleValueTypes = TypeVar("PossibleValueTypes", int, float, str, datetime, None)
12 |
13 |
14 | @validator
15 | def between(
16 | value: PossibleValueTypes,
17 | /,
18 | *,
19 | min_val: Union[PossibleValueTypes, AbsMin, None] = None,
20 | max_val: Union[PossibleValueTypes, AbsMax, None] = None,
21 | ):
22 | """Validate that a number is between minimum and/or maximum value.
23 |
24 | This will work with any comparable type, such as floats, decimals and dates
25 | not just integers. This validator is originally based on [WTForms-NumberRange-Validator][1].
26 |
27 | [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py#L166-L220
28 |
29 | Examples:
30 | >>> from datetime import datetime
31 | >>> between(5, min_val=2)
32 | True
33 | >>> between(13.2, min_val=13, max_val=14)
34 | True
35 | >>> between(500, max_val=400)
36 | ValidationError(func=between, args={'value': 500, 'max_val': 400})
37 | >>> between(
38 | ... datetime(2000, 11, 11),
39 | ... min_val=datetime(1999, 11, 11)
40 | ... )
41 | True
42 |
43 | Args:
44 | value:
45 | Value which is to be compared.
46 | min_val:
47 | The minimum required value of the number.
48 | If not provided, minimum value will not be checked.
49 | max_val:
50 | The maximum value of the number.
51 | If not provided, maximum value will not be checked.
52 |
53 | Returns:
54 | (Literal[True]): If `value` is in between the given conditions.
55 | (ValidationError): If `value` is not in between the given conditions.
56 |
57 | Raises:
58 | (ValueError): If `min_val` is greater than `max_val`.
59 | (TypeError): If there's a type mismatch during comparison.
60 |
61 | Note:
62 | - `PossibleValueTypes` = `TypeVar("PossibleValueTypes", int, float, str, datetime)`
63 | - If neither `min_val` nor `max_val` is provided, result will always be `True`.
64 | """
65 | if value is None:
66 | return False
67 |
68 | if max_val is None:
69 | max_val = AbsMax()
70 | if min_val is None:
71 | min_val = AbsMin()
72 |
73 | try:
74 | if min_val > max_val:
75 | raise ValueError("`min_val` cannot be greater than `max_val`")
76 | except TypeError as err:
77 | raise TypeError("Comparison type mismatch") from err
78 |
79 | return min_val <= value <= max_val
80 |
--------------------------------------------------------------------------------
/src/validators/card.py:
--------------------------------------------------------------------------------
1 | """Card."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from .utils import validator
8 |
9 |
10 | @validator
11 | def card_number(value: str, /):
12 | """Return whether or not given value is a valid generic card number.
13 |
14 | This validator is based on [Luhn's algorithm][1].
15 |
16 | [1]: https://github.com/mmcloughlin/luhn
17 |
18 | Examples:
19 | >>> card_number('4242424242424242')
20 | True
21 | >>> card_number('4242424242424241')
22 | ValidationError(func=card_number, args={'value': '4242424242424241'})
23 |
24 | Args:
25 | value:
26 | Generic card number string to validate
27 |
28 | Returns:
29 | (Literal[True]): If `value` is a valid generic card number.
30 | (ValidationError): If `value` is an invalid generic card number.
31 | """
32 | if not value:
33 | return False
34 | try:
35 | digits = list(map(int, value))
36 | odd_sum = sum(digits[-1::-2])
37 | even_sum = sum(sum(divmod(2 * d, 10)) for d in digits[-2::-2])
38 | return (odd_sum + even_sum) % 10 == 0
39 | except ValueError:
40 | return False
41 |
42 |
43 | @validator
44 | def visa(value: str, /):
45 | """Return whether or not given value is a valid Visa card number.
46 |
47 | Examples:
48 | >>> visa('4242424242424242')
49 | True
50 | >>> visa('2223003122003222')
51 | ValidationError(func=visa, args={'value': '2223003122003222'})
52 |
53 | Args:
54 | value:
55 | Visa card number string to validate
56 |
57 | Returns:
58 | (Literal[True]): If `value` is a valid Visa card number.
59 | (ValidationError): If `value` is an invalid Visa card number.
60 | """
61 | pattern = re.compile(r"^4")
62 | return card_number(value) and len(value) == 16 and pattern.match(value)
63 |
64 |
65 | @validator
66 | def mastercard(value: str, /):
67 | """Return whether or not given value is a valid Mastercard card number.
68 |
69 | Examples:
70 | >>> mastercard('5555555555554444')
71 | True
72 | >>> mastercard('4242424242424242')
73 | ValidationError(func=mastercard, args={'value': '4242424242424242'})
74 |
75 | Args:
76 | value:
77 | Mastercard card number string to validate
78 |
79 | Returns:
80 | (Literal[True]): If `value` is a valid Mastercard card number.
81 | (ValidationError): If `value` is an invalid Mastercard card number.
82 | """
83 | pattern = re.compile(r"^(51|52|53|54|55|22|23|24|25|26|27)")
84 | return card_number(value) and len(value) == 16 and pattern.match(value)
85 |
86 |
87 | @validator
88 | def amex(value: str, /):
89 | """Return whether or not given value is a valid American Express card number.
90 |
91 | Examples:
92 | >>> amex('378282246310005')
93 | True
94 | >>> amex('4242424242424242')
95 | ValidationError(func=amex, args={'value': '4242424242424242'})
96 |
97 | Args:
98 | value:
99 | American Express card number string to validate
100 |
101 | Returns:
102 | (Literal[True]): If `value` is a valid American Express card number.
103 | (ValidationError): If `value` is an invalid American Express card number.
104 | """
105 | pattern = re.compile(r"^(34|37)")
106 | return card_number(value) and len(value) == 15 and pattern.match(value)
107 |
108 |
109 | @validator
110 | def unionpay(value: str, /):
111 | """Return whether or not given value is a valid UnionPay card number.
112 |
113 | Examples:
114 | >>> unionpay('6200000000000005')
115 | True
116 | >>> unionpay('4242424242424242')
117 | ValidationError(func=unionpay, args={'value': '4242424242424242'})
118 |
119 | Args:
120 | value:
121 | UnionPay card number string to validate
122 |
123 | Returns:
124 | (Literal[True]): If `value` is a valid UnionPay card number.
125 | (ValidationError): If `value` is an invalid UnionPay card number.
126 | """
127 | pattern = re.compile(r"^62")
128 | return card_number(value) and len(value) == 16 and pattern.match(value)
129 |
130 |
131 | @validator
132 | def diners(value: str, /):
133 | """Return whether or not given value is a valid Diners Club card number.
134 |
135 | Examples:
136 | >>> diners('3056930009020004')
137 | True
138 | >>> diners('4242424242424242')
139 | ValidationError(func=diners, args={'value': '4242424242424242'})
140 |
141 | Args:
142 | value:
143 | Diners Club card number string to validate
144 |
145 | Returns:
146 | (Literal[True]): If `value` is a valid Diners Club card number.
147 | (ValidationError): If `value` is an invalid Diners Club card number.
148 | """
149 | pattern = re.compile(r"^(30|36|38|39)")
150 | return card_number(value) and len(value) in {14, 16} and pattern.match(value)
151 |
152 |
153 | @validator
154 | def jcb(value: str, /):
155 | """Return whether or not given value is a valid JCB card number.
156 |
157 | Examples:
158 | >>> jcb('3566002020360505')
159 | True
160 | >>> jcb('4242424242424242')
161 | ValidationError(func=jcb, args={'value': '4242424242424242'})
162 |
163 | Args:
164 | value:
165 | JCB card number string to validate
166 |
167 | Returns:
168 | (Literal[True]): If `value` is a valid JCB card number.
169 | (ValidationError): If `value` is an invalid JCB card number.
170 | """
171 | pattern = re.compile(r"^35")
172 | return card_number(value) and len(value) == 16 and pattern.match(value)
173 |
174 |
175 | @validator
176 | def discover(value: str, /):
177 | """Return whether or not given value is a valid Discover card number.
178 |
179 | Examples:
180 | >>> discover('6011111111111117')
181 | True
182 | >>> discover('4242424242424242')
183 | ValidationError(func=discover, args={'value': '4242424242424242'})
184 |
185 | Args:
186 | value:
187 | Discover card number string to validate
188 |
189 | Returns:
190 | (Literal[True]): If `value` is a valid Discover card number.
191 | (ValidationError): If `value` is an invalid Discover card number.
192 | """
193 | pattern = re.compile(r"^(60|64|65)")
194 | return card_number(value) and len(value) == 16 and pattern.match(value)
195 |
196 |
197 | @validator
198 | def mir(value: str, /):
199 | """Return whether or not given value is a valid Mir card number.
200 |
201 | Examples:
202 | >>> mir('2200123456789019')
203 | True
204 | >>> mir('4242424242424242')
205 | ValidationError(func=mir, args={'value': '4242424242424242'})
206 |
207 | Args:
208 | value:
209 | Mir card number string to validate.
210 |
211 | Returns:
212 | (Literal[True]): If `value` is a valid Mir card number.
213 | (ValidationError): If `value` is an invalid Mir card number.
214 | """
215 | pattern = re.compile(r"^(220[0-4])")
216 | return card_number(value) and len(value) == 16 and pattern.match(value)
217 |
--------------------------------------------------------------------------------
/src/validators/cron.py:
--------------------------------------------------------------------------------
1 | """Cron."""
2 |
3 | # local
4 | from .utils import validator
5 |
6 |
7 | def _validate_cron_component(component: str, min_val: int, max_val: int):
8 | if component == "*":
9 | return True
10 |
11 | if component.isdecimal():
12 | return min_val <= int(component) <= max_val
13 |
14 | if "/" in component:
15 | parts = component.split("/")
16 | if len(parts) != 2 or not parts[1].isdecimal() or int(parts[1]) < 1:
17 | return False
18 | if parts[0] == "*":
19 | return True
20 | return parts[0].isdecimal() and min_val <= int(parts[0]) <= max_val
21 |
22 | if "-" in component:
23 | parts = component.split("-")
24 | if len(parts) != 2 or not parts[0].isdecimal() or not parts[1].isdecimal():
25 | return False
26 | start, end = int(parts[0]), int(parts[1])
27 | return min_val <= start <= max_val and min_val <= end <= max_val and start <= end
28 |
29 | if "," in component:
30 | for item in component.split(","):
31 | if not _validate_cron_component(item, min_val, max_val):
32 | return False
33 | return True
34 | # return all(
35 | # _validate_cron_component(item, min_val, max_val) for item in component.split(",")
36 | # ) # throws type error. why?
37 |
38 | return False
39 |
40 |
41 | @validator
42 | def cron(value: str, /):
43 | """Return whether or not given value is a valid cron string.
44 |
45 | Examples:
46 | >>> cron('*/5 * * * *')
47 | True
48 | >>> cron('30-20 * * * *')
49 | ValidationError(func=cron, args={'value': '30-20 * * * *'})
50 |
51 | Args:
52 | value:
53 | Cron string to validate.
54 |
55 | Returns:
56 | (Literal[True]): If `value` is a valid cron string.
57 | (ValidationError): If `value` is an invalid cron string.
58 | """
59 | if not value:
60 | return False
61 |
62 | try:
63 | minutes, hours, days, months, weekdays = value.strip().split()
64 | except ValueError as err:
65 | raise ValueError("Badly formatted cron string") from err
66 |
67 | if not _validate_cron_component(minutes, 0, 59):
68 | return False
69 | if not _validate_cron_component(hours, 0, 23):
70 | return False
71 | if not _validate_cron_component(days, 1, 31):
72 | return False
73 | if not _validate_cron_component(months, 1, 12):
74 | return False
75 | if not _validate_cron_component(weekdays, 0, 6):
76 | return False
77 |
78 | return True
79 |
--------------------------------------------------------------------------------
/src/validators/crypto_addresses/__init__.py:
--------------------------------------------------------------------------------
1 | """Crypto addresses."""
2 |
3 | # local
4 | from .bsc_address import bsc_address
5 | from .btc_address import btc_address
6 | from .eth_address import eth_address
7 | from .trx_address import trx_address
8 |
9 | __all__ = ("bsc_address", "btc_address", "eth_address", "trx_address")
10 |
--------------------------------------------------------------------------------
/src/validators/crypto_addresses/bsc_address.py:
--------------------------------------------------------------------------------
1 | """BSC Address."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from validators.utils import validator
8 |
9 |
10 | @validator
11 | def bsc_address(value: str, /):
12 | """Return whether or not given value is a valid binance smart chain address.
13 |
14 | Full validation is implemented for BSC addresses.
15 |
16 | Examples:
17 | >>> bsc_address('0x4e5acf9684652BEa56F2f01b7101a225Ee33d23f')
18 | True
19 | >>> bsc_address('0x4g5acf9684652BEa56F2f01b7101a225Eh33d23z')
20 | ValidationError(func=bsc_address, args={'value': '0x4g5acf9684652BEa56F2f01b7101a225Eh33d23z'})
21 |
22 | Args:
23 | value:
24 | BSC address string to validate.
25 |
26 | Returns:
27 | (Literal[True]): If `value` is a valid bsc address.
28 | (ValidationError): If `value` is an invalid bsc address.
29 | """ # noqa: E501
30 | if not value:
31 | return False
32 |
33 | if not re.fullmatch(r"0x[a-fA-F0-9]{40}", value):
34 | return False
35 |
36 | return True
37 |
--------------------------------------------------------------------------------
/src/validators/crypto_addresses/btc_address.py:
--------------------------------------------------------------------------------
1 | """BTC Address."""
2 |
3 | # standard
4 | from hashlib import sha256
5 | import re
6 |
7 | # local
8 | from validators.utils import validator
9 |
10 |
11 | def _decode_base58(addr: str):
12 | """Decode base58."""
13 | alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
14 | return sum((58**enm) * alphabet.index(idx) for enm, idx in enumerate(addr[::-1]))
15 |
16 |
17 | def _validate_old_btc_address(addr: str):
18 | """Validate P2PKH and P2SH type address."""
19 | if len(addr) not in range(25, 35):
20 | return False
21 | decoded_bytes = _decode_base58(addr).to_bytes(25, "big")
22 | header, checksum = decoded_bytes[:-4], decoded_bytes[-4:]
23 | return checksum == sha256(sha256(header).digest()).digest()[:4]
24 |
25 |
26 | @validator
27 | def btc_address(value: str, /):
28 | """Return whether or not given value is a valid bitcoin address.
29 |
30 | Full validation is implemented for P2PKH and P2SH addresses.
31 | For segwit addresses a regexp is used to provide a reasonable
32 | estimate on whether the address is valid.
33 |
34 | Examples:
35 | >>> btc_address('3Cwgr2g7vsi1bXDUkpEnVoRLA9w4FZfC69')
36 | True
37 | >>> btc_address('1BvBMsEYstWetqTFn5Au4m4GFg7xJaNVN2')
38 | ValidationError(func=btc_address, args={'value': '1BvBMsEYstWetqTFn5Au4m4GFg7xJaNVN2'})
39 |
40 | Args:
41 | value:
42 | Bitcoin address string to validate.
43 |
44 | Returns:
45 | (Literal[True]): If `value` is a valid bitcoin address.
46 | (ValidationError): If `value` is an invalid bitcoin address.
47 | """
48 | if not value:
49 | return False
50 |
51 | return (
52 | # segwit pattern
53 | re.compile(r"^(bc|tc)[0-3][02-9ac-hj-np-z]{14,74}$").match(value)
54 | if value[:2] in ("bc", "tb")
55 | else _validate_old_btc_address(value)
56 | )
57 |
--------------------------------------------------------------------------------
/src/validators/crypto_addresses/eth_address.py:
--------------------------------------------------------------------------------
1 | """ETH Address."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from validators.utils import validator
8 |
9 | _keccak_flag = True
10 | try:
11 | # external
12 | from eth_hash.auto import keccak
13 | except ImportError:
14 | _keccak_flag = False
15 |
16 |
17 | def _validate_eth_checksum_address(addr: str):
18 | """Validate ETH type checksum address."""
19 | addr = addr.replace("0x", "")
20 | addr_hash = keccak.new(addr.lower().encode("ascii")).digest().hex() # type: ignore
21 |
22 | if len(addr) != 40:
23 | return False
24 |
25 | for i in range(0, 40):
26 | if (int(addr_hash[i], 16) > 7 and addr[i].upper() != addr[i]) or (
27 | int(addr_hash[i], 16) <= 7 and addr[i].lower() != addr[i]
28 | ):
29 | return False
30 | return True
31 |
32 |
33 | @validator
34 | def eth_address(value: str, /):
35 | """Return whether or not given value is a valid ethereum address.
36 |
37 | Full validation is implemented for ERC20 addresses.
38 |
39 | Examples:
40 | >>> eth_address('0x9cc14ba4f9f68ca159ea4ebf2c292a808aaeb598')
41 | True
42 | >>> eth_address('0x8Ba1f109551bD432803012645Ac136ddd64DBa72')
43 | ValidationError(func=eth_address, args={'value': '0x8Ba1f109551bD432803012645Ac136ddd64DBa72'})
44 |
45 | Args:
46 | value:
47 | Ethereum address string to validate.
48 |
49 | Returns:
50 | (Literal[True]): If `value` is a valid ethereum address.
51 | (ValidationError): If `value` is an invalid ethereum address.
52 | """ # noqa: E501
53 | if not _keccak_flag:
54 | raise ImportError(
55 | "Do `pip install validators[crypto-eth-addresses]` to perform `eth_address` validation."
56 | )
57 |
58 | if not value:
59 | return False
60 |
61 | return re.compile(r"^0x[0-9a-f]{40}$|^0x[0-9A-F]{40}$").match(
62 | value
63 | ) or _validate_eth_checksum_address(value)
64 |
--------------------------------------------------------------------------------
/src/validators/crypto_addresses/trx_address.py:
--------------------------------------------------------------------------------
1 | """TRX Address."""
2 |
3 | # standard
4 | import hashlib
5 | import re
6 |
7 | # local
8 | from validators.utils import validator
9 |
10 |
11 | def _base58_decode(addr: str) -> bytes:
12 | """Decode a base58 encoded address."""
13 | alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
14 | num = 0
15 | for char in addr:
16 | num = num * 58 + alphabet.index(char)
17 | return num.to_bytes(25, byteorder="big")
18 |
19 |
20 | def _validate_trx_checksum_address(addr: str) -> bool:
21 | """Validate TRX type checksum address."""
22 | if len(addr) != 34:
23 | return False
24 |
25 | try:
26 | address = _base58_decode(addr)
27 | except ValueError:
28 | return False
29 |
30 | if len(address) != 25 or address[0] != 0x41:
31 | return False
32 |
33 | check_sum = hashlib.sha256(hashlib.sha256(address[:-4]).digest()).digest()[:4]
34 | return address[-4:] == check_sum
35 |
36 |
37 | @validator
38 | def trx_address(value: str, /):
39 | """Return whether or not given value is a valid tron address.
40 |
41 | Full validation is implemented for TRC20 tron addresses.
42 |
43 | Examples:
44 | >>> trx_address('TLjfbTbpZYDQ4EoA4N5CLNgGjfbF8ZWz38')
45 | True
46 | >>> trx_address('TR2G7Rm4vFqF8EpY4U5xdLdQ7XgJ2U8Vd')
47 | ValidationError(func=trx_address, args={'value': 'TR2G7Rm4vFqF8EpY4U5xdLdQ7XgJ2U8Vd'})
48 |
49 | Args:
50 | value:
51 | Tron address string to validate.
52 |
53 | Returns:
54 | (Literal[True]): If `value` is a valid tron address.
55 | (ValidationError): If `value` is an invalid tron address.
56 | """
57 | if not value:
58 | return False
59 |
60 | return re.compile(r"^[T][a-km-zA-HJ-NP-Z1-9]{33}$").match(
61 | value
62 | ) and _validate_trx_checksum_address(value)
63 |
--------------------------------------------------------------------------------
/src/validators/domain.py:
--------------------------------------------------------------------------------
1 | """Domain."""
2 |
3 | # standard
4 | from os import environ
5 | from pathlib import Path
6 | import re
7 | from typing import Optional, Set
8 |
9 | # local
10 | from .utils import validator
11 |
12 |
13 | class _IanaTLD:
14 | """Read IANA TLDs, and optionally cache them."""
15 |
16 | _full_cache: Optional[Set[str]] = None
17 | # source: https://www.statista.com/statistics/265677
18 | _popular_cache = {"COM", "ORG", "RU", "DE", "NET", "BR", "UK", "JP", "FR", "IT"}
19 | _popular_cache.add("ONION")
20 |
21 | @classmethod
22 | def _retrieve(cls):
23 | with Path(__file__).parent.joinpath("_tld.txt").open() as tld_f:
24 | _ = next(tld_f) # ignore the first line
25 | for line in tld_f:
26 | yield line.strip()
27 |
28 | @classmethod
29 | def check(cls, tld: str):
30 | if tld in cls._popular_cache:
31 | return True
32 | if cls._full_cache is None:
33 | if environ.get("PYVLD_CACHE_TLD") == "True":
34 | cls._full_cache = set(cls._retrieve())
35 | else:
36 | return tld in cls._retrieve()
37 | return tld in cls._full_cache
38 |
39 |
40 | @validator
41 | def domain(
42 | value: str, /, *, consider_tld: bool = False, rfc_1034: bool = False, rfc_2782: bool = False
43 | ):
44 | """Return whether or not given value is a valid domain.
45 |
46 | Examples:
47 | >>> domain('example.com')
48 | True
49 | >>> domain('example.com/')
50 | ValidationError(func=domain, args={'value': 'example.com/'})
51 | >>> # Supports IDN domains as well::
52 | >>> domain('xn----gtbspbbmkef.xn--p1ai')
53 | True
54 |
55 | Args:
56 | value:
57 | Domain string to validate.
58 | consider_tld:
59 | Restrict domain to TLDs allowed by IANA.
60 | rfc_1034:
61 | Allows optional trailing dot in the domain name.
62 | Ref: [RFC 1034](https://www.rfc-editor.org/rfc/rfc1034).
63 | rfc_2782:
64 | Domain name is of type service record.
65 | Allows optional underscores in the domain name.
66 | Ref: [RFC 2782](https://www.rfc-editor.org/rfc/rfc2782).
67 |
68 |
69 | Returns:
70 | (Literal[True]): If `value` is a valid domain name.
71 | (ValidationError): If `value` is an invalid domain name.
72 |
73 | Raises:
74 | (UnicodeError): If `value` cannot be encoded into `idna` or decoded into `utf-8`.
75 | """
76 | if not value:
77 | return False
78 |
79 | if consider_tld and not _IanaTLD.check(value.rstrip(".").rsplit(".", 1)[-1].upper()):
80 | return False
81 |
82 | try:
83 | service_record = r"_" if rfc_2782 else ""
84 | trailing_dot = r"\.?$" if rfc_1034 else r"$"
85 |
86 | return not re.search(r"\s|__+", value) and re.match(
87 | # First character of the domain
88 | rf"^(?:[a-z0-9{service_record}]"
89 | # Sub-domain
90 | + rf"(?:[a-z0-9-{service_record}]{{0,61}}"
91 | # Hostname
92 | + rf"[a-z0-9{service_record}])?\.)"
93 | # First 61 characters of the gTLD
94 | + r"+[a-z0-9][a-z0-9-_]{0,61}"
95 | # Last character of the gTLD
96 | + rf"[a-z]{trailing_dot}",
97 | value.encode("idna").decode("utf-8"),
98 | re.IGNORECASE,
99 | )
100 | except UnicodeError as err:
101 | raise UnicodeError(f"Unable to encode/decode {value}") from err
102 |
--------------------------------------------------------------------------------
/src/validators/email.py:
--------------------------------------------------------------------------------
1 | """eMail."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from .hostname import hostname
8 | from .utils import validator
9 |
10 |
11 | @validator
12 | def email(
13 | value: str,
14 | /,
15 | *,
16 | ipv6_address: bool = False,
17 | ipv4_address: bool = False,
18 | simple_host: bool = False,
19 | rfc_1034: bool = False,
20 | rfc_2782: bool = False,
21 | ):
22 | """Validate an email address.
23 |
24 | This was inspired from [Django's email validator][1].
25 | Also ref: [RFC 1034][2], [RFC 5321][3] and [RFC 5322][4].
26 |
27 | [1]: https://github.com/django/django/blob/main/django/core/validators.py#L174
28 | [2]: https://www.rfc-editor.org/rfc/rfc1034
29 | [3]: https://www.rfc-editor.org/rfc/rfc5321
30 | [4]: https://www.rfc-editor.org/rfc/rfc5322
31 |
32 | Examples:
33 | >>> email('someone@example.com')
34 | True
35 | >>> email('bogus@@')
36 | ValidationError(func=email, args={'value': 'bogus@@'})
37 |
38 | Args:
39 | value:
40 | eMail string to validate.
41 | ipv6_address:
42 | When the domain part is an IPv6 address.
43 | ipv4_address:
44 | When the domain part is an IPv4 address.
45 | simple_host:
46 | When the domain part is a simple hostname.
47 | rfc_1034:
48 | Allow trailing dot in domain name.
49 | Ref: [RFC 1034](https://www.rfc-editor.org/rfc/rfc1034).
50 | rfc_2782:
51 | Domain name is of type service record.
52 | Ref: [RFC 2782](https://www.rfc-editor.org/rfc/rfc2782).
53 |
54 | Returns:
55 | (Literal[True]): If `value` is a valid eMail.
56 | (ValidationError): If `value` is an invalid eMail.
57 | """
58 | if not value or value.count("@") != 1:
59 | return False
60 |
61 | username_part, domain_part = value.rsplit("@", 1)
62 |
63 | if len(username_part) > 64 or len(domain_part) > 253:
64 | # ref: RFC 1034 and 5231
65 | return False
66 |
67 | if ipv6_address or ipv4_address:
68 | if domain_part.startswith("[") and domain_part.endswith("]"):
69 | # ref: RFC 5321
70 | domain_part = domain_part.lstrip("[").rstrip("]")
71 | else:
72 | return False
73 |
74 | return (
75 | bool(
76 | hostname(
77 | domain_part,
78 | skip_ipv6_addr=not ipv6_address,
79 | skip_ipv4_addr=not ipv4_address,
80 | may_have_port=False,
81 | maybe_simple=simple_host,
82 | rfc_1034=rfc_1034,
83 | rfc_2782=rfc_2782,
84 | )
85 | )
86 | if re.match(
87 | # extended latin
88 | r"(^[\u0100-\u017F\u0180-\u024F\u00A0-\u00FF]"
89 | # dot-atom
90 | + r"|[\u0100-\u017F\u0180-\u024F\u00A0-\u00FF0-9a-z!#$%&'*+/=?^_`{}|~\-]+"
91 | + r"(\.[\u0100-\u017F\u0180-\u024F\u00A0-\u00FF0-9a-z!#$%&'*+/=?^_`{}|~\-]+)*$"
92 | # quoted-string
93 | + r'|^"('
94 | + r"[\u0100-\u017F\u0180-\u024F\u00A0-\u00FF\001-\010\013\014\016-\037"
95 | + r"!#-\[\]-\177]|\\[\011.]"
96 | + r')*")$',
97 | username_part,
98 | re.IGNORECASE,
99 | )
100 | else False
101 | )
102 |
--------------------------------------------------------------------------------
/src/validators/encoding.py:
--------------------------------------------------------------------------------
1 | """Encoding."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from .utils import validator
8 |
9 |
10 | @validator
11 | def base16(value: str, /):
12 | """Return whether or not given value is a valid base16 encoding.
13 |
14 | Examples:
15 | >>> base16('a3f4b2')
16 | True
17 | >>> base16('a3f4Z1')
18 | ValidationError(func=base16, args={'value': 'a3f4Z1'})
19 |
20 | Args:
21 | value:
22 | base16 string to validate.
23 |
24 | Returns:
25 | (Literal[True]): If `value` is a valid base16 encoding.
26 | (ValidationError): If `value` is an invalid base16 encoding.
27 | """
28 | return re.match(r"^[0-9A-Fa-f]+$", value) if value else False
29 |
30 |
31 | @validator
32 | def base32(value: str, /):
33 | """Return whether or not given value is a valid base32 encoding.
34 |
35 | Examples:
36 | >>> base32('MFZWIZLTOQ======')
37 | True
38 | >>> base32('MfZW3zLT9Q======')
39 | ValidationError(func=base32, args={'value': 'MfZW3zLT9Q======'})
40 |
41 | Args:
42 | value:
43 | base32 string to validate.
44 |
45 | Returns:
46 | (Literal[True]): If `value` is a valid base32 encoding.
47 | (ValidationError): If `value` is an invalid base32 encoding.
48 | """
49 | return re.match(r"^[A-Z2-7]+=*$", value) if value else False
50 |
51 |
52 | @validator
53 | def base58(value: str, /):
54 | """Return whether or not given value is a valid base58 encoding.
55 |
56 | Examples:
57 | >>> base58('14pq6y9H2DLGahPsM4s7ugsNSD2uxpHsJx')
58 | True
59 | >>> base58('cUSECm5YzcXJwP')
60 | True
61 |
62 | Args:
63 | value:
64 | base58 string to validate.
65 |
66 | Returns:
67 | (Literal[True]): If `value` is a valid base58 encoding.
68 | (ValidationError): If `value` is an invalid base58 encoding.
69 | """
70 | return re.match(r"^[1-9A-HJ-NP-Za-km-z]+$", value) if value else False
71 |
72 |
73 | @validator
74 | def base64(value: str, /):
75 | """Return whether or not given value is a valid base64 encoding.
76 |
77 | Examples:
78 | >>> base64('Y2hhcmFjdGVyIHNldA==')
79 | True
80 | >>> base64('cUSECm5YzcXJwP')
81 | ValidationError(func=base64, args={'value': 'cUSECm5YzcXJwP'})
82 |
83 | Args:
84 | value:
85 | base64 string to validate.
86 |
87 | Returns:
88 | (Literal[True]): If `value` is a valid base64 encoding.
89 | (ValidationError): If `value` is an invalid base64 encoding.
90 | """
91 | return (
92 | re.match(r"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", value)
93 | if value
94 | else False
95 | )
96 |
--------------------------------------------------------------------------------
/src/validators/finance.py:
--------------------------------------------------------------------------------
1 | """Finance."""
2 |
3 | from .utils import validator
4 |
5 |
6 | def _cusip_checksum(cusip: str):
7 | check, val = 0, None
8 |
9 | for idx in range(9):
10 | c = cusip[idx]
11 | if c >= "0" and c <= "9":
12 | val = ord(c) - ord("0")
13 | elif c >= "A" and c <= "Z":
14 | val = 10 + ord(c) - ord("A")
15 | elif c >= "a" and c <= "z":
16 | val = 10 + ord(c) - ord("a")
17 | elif c == "*":
18 | val = 36
19 | elif c == "@":
20 | val = 37
21 | elif c == "#":
22 | val = 38
23 | else:
24 | return False
25 |
26 | if idx & 1:
27 | val += val
28 |
29 | check = check + (val // 10) + (val % 10)
30 |
31 | return (check % 10) == 0
32 |
33 |
34 | def _isin_checksum(value: str):
35 | check, val = 0, None
36 |
37 | for idx in range(12):
38 | c = value[idx]
39 | if c >= "0" and c <= "9" and idx > 1:
40 | val = ord(c) - ord("0")
41 | elif c >= "A" and c <= "Z":
42 | val = 10 + ord(c) - ord("A")
43 | elif c >= "a" and c <= "z":
44 | val = 10 + ord(c) - ord("a")
45 | else:
46 | return False
47 |
48 | if idx & 1:
49 | val += val
50 |
51 | return (check % 10) == 0
52 |
53 |
54 | @validator
55 | def cusip(value: str):
56 | """Return whether or not given value is a valid CUSIP.
57 |
58 | Checks if the value is a valid [CUSIP][1].
59 | [1]: https://en.wikipedia.org/wiki/CUSIP
60 |
61 | Examples:
62 | >>> cusip('037833DP2')
63 | True
64 | >>> cusip('037833DP3')
65 | ValidationError(func=cusip, args={'value': '037833DP3'})
66 |
67 | Args:
68 | value: CUSIP string to validate.
69 |
70 | Returns:
71 | (Literal[True]): If `value` is a valid CUSIP string.
72 | (ValidationError): If `value` is an invalid CUSIP string.
73 | """
74 | return len(value) == 9 and _cusip_checksum(value)
75 |
76 |
77 | @validator
78 | def isin(value: str):
79 | """Return whether or not given value is a valid ISIN.
80 |
81 | Checks if the value is a valid [ISIN][1].
82 | [1]: https://en.wikipedia.org/wiki/International_Securities_Identification_Number
83 |
84 | Examples:
85 | >>> isin('037833DP2')
86 | ValidationError(func=isin, args={'value': '037833DP2'})
87 | >>> isin('037833DP3')
88 | ValidationError(func=isin, args={'value': '037833DP3'})
89 |
90 | Args:
91 | value: ISIN string to validate.
92 |
93 | Returns:
94 | (Literal[True]): If `value` is a valid ISIN string.
95 | (ValidationError): If `value` is an invalid ISIN string.
96 | """
97 | return len(value) == 12 and _isin_checksum(value)
98 |
99 |
100 | @validator
101 | def sedol(value: str):
102 | """Return whether or not given value is a valid SEDOL.
103 |
104 | Checks if the value is a valid [SEDOL][1].
105 | [1]: https://en.wikipedia.org/wiki/SEDOL
106 |
107 | Examples:
108 | >>> sedol('2936921')
109 | True
110 | >>> sedol('29A6922')
111 | ValidationError(func=sedol, args={'value': '29A6922'})
112 |
113 | Args:
114 | value: SEDOL string to validate.
115 |
116 | Returns:
117 | (Literal[True]): If `value` is a valid SEDOL string.
118 | (ValidationError): If `value` is an invalid SEDOL string.
119 | """
120 | if len(value) != 7:
121 | return False
122 |
123 | weights = [1, 3, 1, 7, 3, 9, 1]
124 | check = 0
125 | for idx in range(7):
126 | c = value[idx]
127 | if c in "AEIOU":
128 | return False
129 |
130 | val = None
131 | if c >= "0" and c <= "9":
132 | val = ord(c) - ord("0")
133 | elif c >= "A" and c <= "Z":
134 | val = 10 + ord(c) - ord("A")
135 | else:
136 | return False
137 | check += val * weights[idx]
138 |
139 | return (check % 10) == 0
140 |
--------------------------------------------------------------------------------
/src/validators/hashes.py:
--------------------------------------------------------------------------------
1 | """Hashes."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from .utils import validator
8 |
9 |
10 | @validator
11 | def md5(value: str, /):
12 | """Return whether or not given value is a valid MD5 hash.
13 |
14 | Examples:
15 | >>> md5('d41d8cd98f00b204e9800998ecf8427e')
16 | True
17 | >>> md5('900zz11')
18 | ValidationError(func=md5, args={'value': '900zz11'})
19 |
20 | Args:
21 | value:
22 | MD5 string to validate.
23 |
24 | Returns:
25 | (Literal[True]): If `value` is a valid MD5 hash.
26 | (ValidationError): If `value` is an invalid MD5 hash.
27 | """
28 | return re.match(r"^[0-9a-f]{32}$", value, re.IGNORECASE) if value else False
29 |
30 |
31 | @validator
32 | def sha1(value: str, /):
33 | """Return whether or not given value is a valid SHA1 hash.
34 |
35 | Examples:
36 | >>> sha1('da39a3ee5e6b4b0d3255bfef95601890afd80709')
37 | True
38 | >>> sha1('900zz11')
39 | ValidationError(func=sha1, args={'value': '900zz11'})
40 |
41 | Args:
42 | value:
43 | SHA1 string to validate.
44 |
45 | Returns:
46 | (Literal[True]): If `value` is a valid SHA1 hash.
47 | (ValidationError): If `value` is an invalid SHA1 hash.
48 | """
49 | return re.match(r"^[0-9a-f]{40}$", value, re.IGNORECASE) if value else False
50 |
51 |
52 | @validator
53 | def sha224(value: str, /):
54 | """Return whether or not given value is a valid SHA224 hash.
55 |
56 | Examples:
57 | >>> sha224('d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f')
58 | True
59 | >>> sha224('900zz11')
60 | ValidationError(func=sha224, args={'value': '900zz11'})
61 |
62 | Args:
63 | value:
64 | SHA224 string to validate.
65 |
66 | Returns:
67 | (Literal[True]): If `value` is a valid SHA224 hash.
68 | (ValidationError): If `value` is an invalid SHA224 hash.
69 | """
70 | return re.match(r"^[0-9a-f]{56}$", value, re.IGNORECASE) if value else False
71 |
72 |
73 | @validator
74 | def sha256(value: str, /):
75 | """Return whether or not given value is a valid SHA256 hash.
76 |
77 | Examples:
78 | >>> sha256(
79 | ... 'e3b0c44298fc1c149afbf4c8996fb924'
80 | ... '27ae41e4649b934ca495991b7852b855'
81 | ... )
82 | True
83 | >>> sha256('900zz11')
84 | ValidationError(func=sha256, args={'value': '900zz11'})
85 |
86 | Args:
87 | value:
88 | SHA256 string to validate.
89 |
90 | Returns:
91 | (Literal[True]): If `value` is a valid SHA256 hash.
92 | (ValidationError): If `value` is an invalid SHA256 hash.
93 | """
94 | return re.match(r"^[0-9a-f]{64}$", value, re.IGNORECASE) if value else False
95 |
96 |
97 | @validator
98 | def sha384(value: str, /):
99 | """Return whether or not given value is a valid SHA384 hash.
100 |
101 | Examples:
102 | >>> sha384(
103 | ... 'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded163'
104 | ... '1a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7'
105 | ... )
106 | True
107 | >>> sha384('900zz11')
108 | ValidationError(func=sha384, args={'value': '900zz11'})
109 |
110 | Args:
111 | value:
112 | SHA384 string to validate.
113 |
114 | Returns:
115 | (Literal[True]): If `value` is a valid SHA384 hash.
116 | (ValidationError): If `value` is an invalid SHA384 hash.
117 | """
118 | return re.match(r"^[0-9a-f]{96}$", value, re.IGNORECASE) if value else False
119 |
120 |
121 | @validator
122 | def sha512(value: str, /):
123 | """Return whether or not given value is a valid SHA512 hash.
124 |
125 | Examples:
126 | >>> sha512(
127 | ... 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce'
128 | ... '9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af9'
129 | ... '27da3e'
130 | ... )
131 | True
132 | >>> sha512('900zz11')
133 | ValidationError(func=sha512, args={'value': '900zz11'})
134 |
135 | Args:
136 | value:
137 | SHA512 string to validate.
138 |
139 | Returns:
140 | (Literal[True]): If `value` is a valid SHA512 hash.
141 | (ValidationError): If `value` is an invalid SHA512 hash.
142 | """
143 | return re.match(r"^[0-9a-f]{128}$", value, re.IGNORECASE) if value else False
144 |
--------------------------------------------------------------------------------
/src/validators/hostname.py:
--------------------------------------------------------------------------------
1 | """Hostname."""
2 |
3 | # standard
4 | from functools import lru_cache
5 | import re
6 | from typing import Optional
7 |
8 | from .domain import domain
9 |
10 | # local
11 | from .ip_address import ipv4, ipv6
12 | from .utils import validator
13 |
14 |
15 | @lru_cache
16 | def _port_regex():
17 | """Port validation regex."""
18 | return re.compile(
19 | r"^\:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|"
20 | + r"6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3})$",
21 | )
22 |
23 |
24 | @lru_cache
25 | def _simple_hostname_regex():
26 | """Simple hostname validation regex."""
27 | # {0,59} because two characters are already matched at
28 | # the beginning and at the end, making the range {1, 61}
29 | return re.compile(r"^(?!-)[a-z0-9](?:[a-z0-9-]{0,59}[a-z0-9])?(?>> hostname("ubuntu-pc:443")
67 | True
68 | >>> hostname("this-pc")
69 | True
70 | >>> hostname("xn----gtbspbbmkef.xn--p1ai:65535")
71 | True
72 | >>> hostname("_example.com")
73 | ValidationError(func=hostname, args={'value': '_example.com'})
74 | >>> hostname("123.5.77.88:31000")
75 | True
76 | >>> hostname("12.12.12.12")
77 | True
78 | >>> hostname("[::1]:22")
79 | True
80 | >>> hostname("dead:beef:0:0:0:0000:42:1")
81 | True
82 | >>> hostname("[0:0:0:0:0:ffff:1.2.3.4]:-65538")
83 | ValidationError(func=hostname, args={'value': '[0:0:0:0:0:ffff:1.2.3.4]:-65538'})
84 | >>> hostname("[0:&:b:c:@:e:f::]:9999")
85 | ValidationError(func=hostname, args={'value': '[0:&:b:c:@:e:f::]:9999'})
86 |
87 | Args:
88 | value:
89 | Hostname string to validate.
90 | skip_ipv6_addr:
91 | When hostname string cannot be an IPv6 address.
92 | skip_ipv4_addr:
93 | When hostname string cannot be an IPv4 address.
94 | may_have_port:
95 | Hostname string may contain port number.
96 | maybe_simple:
97 | Hostname string maybe only hyphens and alpha-numerals.
98 | consider_tld:
99 | Restrict domain to TLDs allowed by IANA.
100 | private:
101 | Embedded IP address is public if `False`, private/local if `True`.
102 | rfc_1034:
103 | Allow trailing dot in domain/host name.
104 | Ref: [RFC 1034](https://www.rfc-editor.org/rfc/rfc1034).
105 | rfc_2782:
106 | Domain/Host name is of type service record.
107 | Ref: [RFC 2782](https://www.rfc-editor.org/rfc/rfc2782).
108 |
109 | Returns:
110 | (Literal[True]): If `value` is a valid hostname.
111 | (ValidationError): If `value` is an invalid hostname.
112 | """
113 | if not value:
114 | return False
115 |
116 | if may_have_port and (host_seg := _port_validator(value)):
117 | return (
118 | (_simple_hostname_regex().match(host_seg) if maybe_simple else False)
119 | or domain(host_seg, consider_tld=consider_tld, rfc_1034=rfc_1034, rfc_2782=rfc_2782)
120 | or (False if skip_ipv4_addr else ipv4(host_seg, cidr=False, private=private))
121 | or (False if skip_ipv6_addr else ipv6(host_seg, cidr=False))
122 | )
123 |
124 | return (
125 | (_simple_hostname_regex().match(value) if maybe_simple else False)
126 | or domain(value, consider_tld=consider_tld, rfc_1034=rfc_1034, rfc_2782=rfc_2782)
127 | or (False if skip_ipv4_addr else ipv4(value, cidr=False, private=private))
128 | or (False if skip_ipv6_addr else ipv6(value, cidr=False))
129 | )
130 |
--------------------------------------------------------------------------------
/src/validators/i18n/__init__.py:
--------------------------------------------------------------------------------
1 | """i18n."""
2 |
3 | # local
4 | from .es import es_cif, es_doi, es_nie, es_nif
5 | from .fi import fi_business_id, fi_ssn
6 | from .fr import fr_department, fr_ssn
7 | from .ind import ind_aadhar, ind_pan
8 | from .ru import ru_inn
9 |
10 | __all__ = (
11 | "fi_business_id",
12 | "fi_ssn",
13 | "es_cif",
14 | "es_doi",
15 | "es_nie",
16 | "es_nif",
17 | "fr_department",
18 | "fr_ssn",
19 | "ind_aadhar",
20 | "ind_pan",
21 | "ru_inn",
22 | )
23 |
--------------------------------------------------------------------------------
/src/validators/i18n/es.py:
--------------------------------------------------------------------------------
1 | """Spain."""
2 |
3 | # standard
4 | from typing import Dict
5 |
6 | # local
7 | from validators.utils import validator
8 |
9 |
10 | def _nif_nie_validation(value: str, number_by_letter: Dict[str, str]):
11 | """Validate if the doi is a NIF or a NIE."""
12 | if len(value) != 9:
13 | return False
14 | value = value.upper()
15 | table = "TRWAGMYFPDXBNJZSQVHLCKE"
16 | # If it is not a DNI, convert the first
17 | # letter to the corresponding digit
18 | numbers = number_by_letter.get(value[0], value[0]) + value[1:8]
19 | # doi[8] is control
20 | return numbers.isdigit() and value[8] == table[int(numbers) % 23]
21 |
22 |
23 | @validator
24 | def es_cif(value: str, /):
25 | """Validate a Spanish CIF.
26 |
27 | Each company in Spain prior to 2008 had a distinct CIF and has been
28 | discontinued. For more information see [wikipedia.org/cif][1].
29 |
30 | The new replacement is to use NIF for absolutely everything. The issue is
31 | that there are "types" of NIFs now: company, person [citizen or resident]
32 | all distinguished by the first character of the DOI. For this reason we
33 | will continue to call CIFs NIFs, that are used for companies.
34 |
35 | This validator is based on [generadordni.es][2].
36 |
37 | [1]: https://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal
38 | [2]: https://generadordni.es/
39 |
40 | Examples:
41 | >>> es_cif('B25162520')
42 | True
43 | >>> es_cif('B25162529')
44 | ValidationError(func=es_cif, args={'value': 'B25162529'})
45 |
46 | Args:
47 | value:
48 | DOI string which is to be validated.
49 |
50 | Returns:
51 | (Literal[True]): If `value` is a valid DOI string.
52 | (ValidationError): If `value` is an invalid DOI string.
53 | """
54 | if not value or len(value) != 9:
55 | return False
56 | value = value.upper()
57 | table = "JABCDEFGHI"
58 | first_chr = value[0]
59 | doi_body = value[1:8]
60 | control = value[8]
61 | if not doi_body.isdigit():
62 | return False
63 | res = (
64 | 10
65 | - sum(
66 | # Multiply each positionally even doi
67 | # digit by 2 and sum it all together
68 | sum(map(int, str(int(char) * 2))) if index % 2 == 0 else int(char)
69 | for index, char in enumerate(doi_body)
70 | )
71 | % 10
72 | ) % 10
73 | if first_chr in "ABEH": # Number type
74 | return str(res) == control
75 | if first_chr in "PSQW": # Letter type
76 | return table[res] == control
77 | return control in {str(res), table[res]} if first_chr in "CDFGJNRUV" else False
78 |
79 |
80 | @validator
81 | def es_nif(value: str, /):
82 | """Validate a Spanish NIF.
83 |
84 | Each entity, be it person or company in Spain has a distinct NIF. Since
85 | we've designated CIF to be a company NIF, this NIF is only for person.
86 | For more information see [wikipedia.org/nif][1]. This validator
87 | is based on [generadordni.es][2].
88 |
89 | [1]: https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal
90 | [2]: https://generadordni.es/
91 |
92 | Examples:
93 | >>> es_nif('26643189N')
94 | True
95 | >>> es_nif('26643189X')
96 | ValidationError(func=es_nif, args={'value': '26643189X'})
97 |
98 | Args:
99 | value:
100 | DOI string which is to be validated.
101 |
102 | Returns:
103 | (Literal[True]): If `value` is a valid DOI string.
104 | (ValidationError): If `value` is an invalid DOI string.
105 | """
106 | number_by_letter = {"L": "0", "M": "0", "K": "0"}
107 | return _nif_nie_validation(value, number_by_letter)
108 |
109 |
110 | @validator
111 | def es_nie(value: str, /):
112 | """Validate a Spanish NIE.
113 |
114 | The NIE is a tax identification number in Spain, known in Spanish
115 | as the NIE, or more formally the Número de identidad de extranjero.
116 | For more information see [wikipedia.org/nie][1]. This validator
117 | is based on [generadordni.es][2].
118 |
119 | [1]: https://es.wikipedia.org/wiki/N%C3%BAmero_de_identidad_de_extranjero
120 | [2]: https://generadordni.es/
121 |
122 | Examples:
123 | >>> es_nie('X0095892M')
124 | True
125 | >>> es_nie('X0095892X')
126 | ValidationError(func=es_nie, args={'value': 'X0095892X'})
127 |
128 | Args:
129 | value:
130 | DOI string which is to be validated.
131 |
132 | Returns:
133 | (Literal[True]): If `value` is a valid DOI string.
134 | (ValidationError): If `value` is an invalid DOI string.
135 | """
136 | number_by_letter = {"X": "0", "Y": "1", "Z": "2"}
137 | # NIE must must start with X Y or Z
138 | if value and value[0] in number_by_letter:
139 | return _nif_nie_validation(value, number_by_letter)
140 | return False
141 |
142 |
143 | @validator
144 | def es_doi(value: str, /):
145 | """Validate a Spanish DOI.
146 |
147 | A DOI in spain is all NIF / CIF / NIE / DNI -- a digital ID.
148 | For more information see [wikipedia.org/doi][1]. This validator
149 | is based on [generadordni.es][2].
150 |
151 | [1]: https://es.wikipedia.org/wiki/Identificador_de_objeto_digital
152 | [2]: https://generadordni.es/
153 |
154 | Examples:
155 | >>> es_doi('X0095892M')
156 | True
157 | >>> es_doi('X0095892X')
158 | ValidationError(func=es_doi, args={'value': 'X0095892X'})
159 |
160 | Args:
161 | value:
162 | DOI string which is to be validated.
163 |
164 | Returns:
165 | (Literal[True]): If `value` is a valid DOI string.
166 | (ValidationError): If `value` is an invalid DOI string.
167 | """
168 | return es_nie(value) or es_nif(value) or es_cif(value)
169 |
--------------------------------------------------------------------------------
/src/validators/i18n/fi.py:
--------------------------------------------------------------------------------
1 | """Finland."""
2 |
3 | # standard
4 | from functools import lru_cache
5 | import re
6 |
7 | # local
8 | from validators.utils import validator
9 |
10 |
11 | @lru_cache
12 | def _business_id_pattern():
13 | """Business ID Pattern."""
14 | return re.compile(r"^[0-9]{7}-[0-9]$")
15 |
16 |
17 | @lru_cache
18 | def _ssn_pattern(ssn_check_marks: str):
19 | """SSN Pattern."""
20 | return re.compile(
21 | r"""^
22 | (?P(0[1-9]|[1-2]\d|3[01])
23 | (0[1-9]|1[012])
24 | (\d{{2}}))
25 | [ABCDEFYXWVU+-]
26 | (?P(\d{{3}}))
27 | (?P[{check_marks}])$""".format(check_marks=ssn_check_marks),
28 | re.VERBOSE,
29 | )
30 |
31 |
32 | @validator
33 | def fi_business_id(value: str, /):
34 | """Validate a Finnish Business ID.
35 |
36 | Each company in Finland has a distinct business id. For more
37 | information see [Finnish Trade Register][1]
38 |
39 | [1]: http://en.wikipedia.org/wiki/Finnish_Trade_Register
40 |
41 | Examples:
42 | >>> fi_business_id('0112038-9') # Fast Monkeys Ltd
43 | True
44 | >>> fi_business_id('1234567-8') # Bogus ID
45 | ValidationError(func=fi_business_id, args={'value': '1234567-8'})
46 |
47 | Args:
48 | value:
49 | Business ID string to be validated.
50 |
51 | Returns:
52 | (Literal[True]): If `value` is a valid finnish business id.
53 | (ValidationError): If `value` is an invalid finnish business id.
54 | """
55 | if not value:
56 | return False
57 | if not re.match(_business_id_pattern(), value):
58 | return False
59 | factors = [7, 9, 10, 5, 8, 4, 2]
60 | numbers = map(int, value[:7])
61 | checksum = int(value[8])
62 | modulo = sum(f * n for f, n in zip(factors, numbers)) % 11
63 | return (11 - modulo == checksum) or (modulo == checksum == 0)
64 |
65 |
66 | @validator
67 | def fi_ssn(value: str, /, *, allow_temporal_ssn: bool = True):
68 | """Validate a Finnish Social Security Number.
69 |
70 | This validator is based on [django-localflavor-fi][1].
71 |
72 | [1]: https://github.com/django/django-localflavor-fi/
73 |
74 | Examples:
75 | >>> fi_ssn('010101-0101')
76 | True
77 | >>> fi_ssn('101010-0102')
78 | ValidationError(func=fi_ssn, args={'value': '101010-0102'})
79 |
80 | Args:
81 | value:
82 | Social Security Number to be validated.
83 | allow_temporal_ssn:
84 | Whether to accept temporal SSN numbers. Temporal SSN numbers are the
85 | ones where the serial is in the range [900-999]. By default temporal
86 | SSN numbers are valid.
87 |
88 | Returns:
89 | (Literal[True]): If `value` is a valid finnish SSN.
90 | (ValidationError): If `value` is an invalid finnish SSN.
91 | """
92 | if not value:
93 | return False
94 | ssn_check_marks = "0123456789ABCDEFHJKLMNPRSTUVWXY"
95 | if not (result := re.match(_ssn_pattern(ssn_check_marks), value)):
96 | return False
97 | gd = result.groupdict()
98 | checksum = int(gd["date"] + gd["serial"])
99 | return (
100 | int(gd["serial"]) >= 2
101 | and (allow_temporal_ssn or int(gd["serial"]) <= 899)
102 | and ssn_check_marks[checksum % len(ssn_check_marks)] == gd["checksum"]
103 | )
104 |
--------------------------------------------------------------------------------
/src/validators/i18n/fr.py:
--------------------------------------------------------------------------------
1 | """France."""
2 |
3 | # standard
4 | from functools import lru_cache
5 | import re
6 | import typing
7 |
8 | # local
9 | from validators.utils import validator
10 |
11 |
12 | @lru_cache
13 | def _ssn_pattern():
14 | """SSN Pattern."""
15 | return re.compile(
16 | r"^([1,2])" # gender (1=M, 2=F)
17 | r"\s(\d{2})" # year of birth
18 | r"\s(0[1-9]|1[0-2])" # month of birth
19 | r"\s(\d{2,3}|2[A,B])" # department of birth
20 | r"\s(\d{2,3})" # town of birth
21 | r"\s(\d{3})" # registration number
22 | r"(?:\s(\d{2}))?$", # control key (may or may not be provided)
23 | re.VERBOSE,
24 | )
25 |
26 |
27 | @validator
28 | def fr_department(value: typing.Union[str, int]):
29 | """Validate a french department number.
30 |
31 | Examples:
32 | >>> fr_department(20) # can be an integer
33 | ValidationError(func=fr_department, args={'value': 20})
34 | >>> fr_department("20")
35 | ValidationError(func=fr_department, args={'value': '20'})
36 | >>> fr_department("971") # Guadeloupe
37 | True
38 | >>> fr_department("00")
39 | ValidationError(func=fr_department, args={'value': '00'})
40 | >>> fr_department('2A') # Corsica
41 | True
42 | >>> fr_department('2B')
43 | True
44 | >>> fr_department('2C')
45 | ValidationError(func=fr_department, args={'value': '2C'})
46 |
47 | Args:
48 | value:
49 | French department number to validate.
50 |
51 | Returns:
52 | (Literal[True]): If `value` is a valid french department number.
53 | (ValidationError): If `value` is an invalid french department number.
54 | """
55 | if not value:
56 | return False
57 | if isinstance(value, str):
58 | if value in ("2A", "2B"): # Corsica
59 | return True
60 | try:
61 | value = int(value)
62 | except ValueError:
63 | return False
64 | return 1 <= value <= 19 or 21 <= value <= 95 or 971 <= value <= 976 # Overseas departments
65 |
66 |
67 | @validator
68 | def fr_ssn(value: str):
69 | """Validate a french Social Security Number.
70 |
71 | Each french citizen has a distinct Social Security Number.
72 | For more information see [French Social Security Number][1] (sadly unavailable in english).
73 |
74 | [1]: https://fr.wikipedia.org/wiki/Num%C3%A9ro_de_s%C3%A9curit%C3%A9_sociale_en_France
75 |
76 | Examples:
77 | >>> fr_ssn('1 84 12 76 451 089 46')
78 | True
79 | >>> fr_ssn('1 84 12 76 451 089') # control key is optional
80 | True
81 | >>> fr_ssn('3 84 12 76 451 089 46') # wrong gender number
82 | ValidationError(func=fr_ssn, args={'value': '3 84 12 76 451 089 46'})
83 | >>> fr_ssn('1 84 12 76 451 089 47') # wrong control key
84 | ValidationError(func=fr_ssn, args={'value': '1 84 12 76 451 089 47'})
85 |
86 | Args:
87 | value:
88 | French Social Security Number string to validate.
89 |
90 | Returns:
91 | (Literal[True]): If `value` is a valid french Social Security Number.
92 | (ValidationError): If `value` is an invalid french Social Security Number.
93 | """
94 | if not value:
95 | return False
96 | matched = re.match(_ssn_pattern(), value)
97 | if not matched:
98 | return False
99 | groups = list(matched.groups())
100 | control_key = groups[-1]
101 | department = groups[3]
102 | if department != "99" and not fr_department(department):
103 | # 99 stands for foreign born people
104 | return False
105 | if control_key is None:
106 | # no control key provided, no additional check needed
107 | return True
108 | if len(department) == len(groups[4]):
109 | # if the department number is 3 digits long (overseas departments),
110 | # the town number must be 2 digits long
111 | # and vice versa
112 | return False
113 | if department in ("2A", "2B"):
114 | # Corsica's department numbers are not in the same range as the others
115 | # thus 2A and 2B are replaced by 19 and 18 respectively to compute the control key
116 | groups[3] = "19" if department == "2A" else "18"
117 | # the control key is valid if it is equal to 97 - (the first 13 digits modulo 97)
118 | digits = int("".join(groups[:-1]))
119 | return int(control_key) == (97 - (digits % 97))
120 |
--------------------------------------------------------------------------------
/src/validators/i18n/ind.py:
--------------------------------------------------------------------------------
1 | """India."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from validators.utils import validator
8 |
9 |
10 | @validator
11 | def ind_aadhar(value: str):
12 | """Validate an indian aadhar card number.
13 |
14 | Examples:
15 | >>> ind_aadhar('3675 9834 6015')
16 | True
17 | >>> ind_aadhar('3675 ABVC 2133')
18 | ValidationError(func=ind_aadhar, args={'value': '3675 ABVC 2133'})
19 |
20 | Args:
21 | value: Aadhar card number string to validate.
22 |
23 | Returns:
24 | (Literal[True]): If `value` is a valid aadhar card number.
25 | (ValidationError): If `value` is an invalid aadhar card number.
26 | """
27 | return re.match(r"^[2-9]{1}\d{3}\s\d{4}\s\d{4}$", value)
28 |
29 |
30 | @validator
31 | def ind_pan(value: str):
32 | """Validate a pan card number.
33 |
34 | Examples:
35 | >>> ind_pan('ABCDE9999K')
36 | True
37 | >>> ind_pan('ABC5d7896B')
38 | ValidationError(func=ind_pan, args={'value': 'ABC5d7896B'})
39 |
40 | Args:
41 | value: PAN card number string to validate.
42 |
43 | Returns:
44 | (Literal[True]): If `value` is a valid PAN card number.
45 | (ValidationError): If `value` is an invalid PAN card number.
46 | """
47 | return re.match(r"[A-Z]{5}\d{4}[A-Z]{1}", value)
48 |
--------------------------------------------------------------------------------
/src/validators/i18n/ru.py:
--------------------------------------------------------------------------------
1 | """Russia."""
2 |
3 | from validators.utils import validator
4 |
5 |
6 | @validator
7 | def ru_inn(value: str):
8 | """Validate a Russian INN (Taxpayer Identification Number).
9 |
10 | The INN can be either 10 digits (for companies) or 12 digits (for individuals).
11 | The function checks both the length and the control digits according to Russian tax rules.
12 |
13 | Examples:
14 | >>> ru_inn('500100732259') # Valid 12-digit INN
15 | True
16 | >>> ru_inn('7830002293') # Valid 10-digit INN
17 | True
18 | >>> ru_inn('1234567890') # Invalid INN
19 | ValidationError(func=ru_inn, args={'value': '1234567890'})
20 |
21 | Args:
22 | value: Russian INN string to validate. Can contain only digits.
23 |
24 | Returns:
25 | (Literal[True]): If `value` is a valid Russian INN.
26 | (ValidationError): If `value` is an invalid Russian INN.
27 |
28 | Note:
29 | The validation follows the official algorithm:
30 | - For 10-digit INN: checks 10th control digit
31 | - For 12-digit INN: checks both 11th and 12th control digits
32 | """
33 | if not value:
34 | return False
35 |
36 | try:
37 | digits = list(map(int, value))
38 | # company
39 | if len(digits) == 10:
40 | weight_coefs = [2, 4, 10, 3, 5, 9, 4, 6, 8, 0]
41 | control_number = sum([d * w for d, w in zip(digits, weight_coefs)]) % 11
42 | return (
43 | (control_number % 10) == digits[-1]
44 | if control_number > 9
45 | else control_number == digits[-1]
46 | )
47 | # person
48 | elif len(digits) == 12:
49 | weight_coefs1 = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8, 0, 0]
50 | control_number1 = sum([d * w for d, w in zip(digits, weight_coefs1)]) % 11
51 | weight_coefs2 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8, 0]
52 | control_number2 = sum([d * w for d, w in zip(digits, weight_coefs2)]) % 11
53 | return (
54 | (control_number1 % 10) == digits[-2]
55 | if control_number1 > 9
56 | else control_number1 == digits[-2] and (control_number2 % 10) == digits[-1]
57 | if control_number2 > 9
58 | else control_number2 == digits[-1]
59 | )
60 | else:
61 | return False
62 | except ValueError:
63 | return False
64 |
--------------------------------------------------------------------------------
/src/validators/iban.py:
--------------------------------------------------------------------------------
1 | """IBAN."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from .utils import validator
8 |
9 |
10 | def _char_value(char: str):
11 | """A=10, B=11, ..., Z=35."""
12 | return char if char.isdigit() else str(10 + ord(char) - ord("A"))
13 |
14 |
15 | def _mod_check(value: str):
16 | """Check if the value string passes the mod97-test."""
17 | # move country code and check numbers to end
18 | rearranged = value[4:] + value[:4]
19 | return int("".join(_char_value(char) for char in rearranged)) % 97 == 1
20 |
21 |
22 | @validator
23 | def iban(value: str, /):
24 | """Return whether or not given value is a valid IBAN code.
25 |
26 | Examples:
27 | >>> iban('DE29100500001061045672')
28 | True
29 | >>> iban('123456')
30 | ValidationError(func=iban, args={'value': '123456'})
31 |
32 | Args:
33 | value:
34 | IBAN string to validate.
35 |
36 | Returns:
37 | (Literal[True]): If `value` is a valid IBAN code.
38 | (ValidationError): If `value` is an invalid IBAN code.
39 | """
40 | return (
41 | (re.match(r"^[a-z]{2}[0-9]{2}[a-z0-9]{11,30}$", value, re.IGNORECASE) and _mod_check(value))
42 | if value
43 | else False
44 | )
45 |
--------------------------------------------------------------------------------
/src/validators/ip_address.py:
--------------------------------------------------------------------------------
1 | """IP Address."""
2 |
3 | # standard
4 | from ipaddress import (
5 | AddressValueError,
6 | IPv4Address,
7 | IPv4Network,
8 | IPv6Address,
9 | IPv6Network,
10 | NetmaskValueError,
11 | )
12 | import re
13 | from typing import Optional
14 |
15 | # local
16 | from .utils import validator
17 |
18 |
19 | def _check_private_ip(value: str, is_private: Optional[bool]):
20 | if is_private is None:
21 | return True
22 | if (
23 | any(
24 | value.startswith(l_bit)
25 | for l_bit in {
26 | "10.", # private
27 | "192.168.", # private
28 | "169.254.", # link-local
29 | "127.", # localhost
30 | "0.0.0.0", # loopback #nosec
31 | }
32 | )
33 | or re.match(r"^172\.(?:1[6-9]|2\d|3[0-1])\.", value) # private
34 | or re.match(r"^(?:22[4-9]|23[0-9]|24[0-9]|25[0-5])\.", value) # broadcast
35 | ):
36 | return is_private
37 |
38 | return not is_private
39 |
40 |
41 | @validator
42 | def ipv4(
43 | value: str,
44 | /,
45 | *,
46 | cidr: bool = True,
47 | strict: bool = False,
48 | private: Optional[bool] = None,
49 | host_bit: bool = True,
50 | ):
51 | """Returns whether a given value is a valid IPv4 address.
52 |
53 | From Python version 3.9.5 leading zeros are no longer tolerated
54 | and are treated as an error. The initial version of ipv4 validator
55 | was inspired from [WTForms IPAddress validator][1].
56 |
57 | [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py
58 |
59 | Examples:
60 | >>> ipv4('123.0.0.7')
61 | True
62 | >>> ipv4('1.1.1.1/8')
63 | True
64 | >>> ipv4('900.80.70.11')
65 | ValidationError(func=ipv4, args={'value': '900.80.70.11'})
66 |
67 | Args:
68 | value:
69 | IP address string to validate.
70 | cidr:
71 | IP address string may contain CIDR notation.
72 | strict:
73 | IP address string is strictly in CIDR notation.
74 | private:
75 | IP address is public if `False`, private/local/loopback/broadcast if `True`.
76 | host_bit:
77 | If `False` and host bits (along with network bits) _are_ set in the supplied
78 | address, this function raises a validation error. ref [IPv4Network][2].
79 | [2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network
80 |
81 | Returns:
82 | (Literal[True]): If `value` is a valid IPv4 address.
83 | (ValidationError): If `value` is an invalid IPv4 address.
84 | """
85 | if not value:
86 | return False
87 | try:
88 | if cidr:
89 | if strict and value.count("/") != 1:
90 | raise ValueError("IPv4 address was expected in CIDR notation")
91 | return IPv4Network(value, strict=not host_bit) and _check_private_ip(value, private)
92 | return IPv4Address(value) and _check_private_ip(value, private)
93 | except (ValueError, AddressValueError, NetmaskValueError):
94 | return False
95 |
96 |
97 | @validator
98 | def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bool = True):
99 | """Returns if a given value is a valid IPv6 address.
100 |
101 | Including IPv4-mapped IPv6 addresses. The initial version of ipv6 validator
102 | was inspired from [WTForms IPAddress validator][1].
103 |
104 | [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py
105 |
106 | Examples:
107 | >>> ipv6('::ffff:192.0.2.128')
108 | True
109 | >>> ipv6('::1/128')
110 | True
111 | >>> ipv6('abc.0.0.1')
112 | ValidationError(func=ipv6, args={'value': 'abc.0.0.1'})
113 |
114 | Args:
115 | value:
116 | IP address string to validate.
117 | cidr:
118 | IP address string may contain CIDR annotation.
119 | strict:
120 | IP address string is strictly in CIDR notation.
121 | host_bit:
122 | If `False` and host bits (along with network bits) _are_ set in the supplied
123 | address, this function raises a validation error. ref [IPv6Network][2].
124 | [2]: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Network
125 |
126 | Returns:
127 | (Literal[True]): If `value` is a valid IPv6 address.
128 | (ValidationError): If `value` is an invalid IPv6 address.
129 | """
130 | if not value:
131 | return False
132 | try:
133 | if cidr:
134 | if strict and value.count("/") != 1:
135 | raise ValueError("IPv6 address was expected in CIDR notation")
136 | return IPv6Network(value, strict=not host_bit)
137 | return IPv6Address(value)
138 | except (ValueError, AddressValueError, NetmaskValueError):
139 | return False
140 |
--------------------------------------------------------------------------------
/src/validators/length.py:
--------------------------------------------------------------------------------
1 | """Length."""
2 |
3 | # standard
4 | from typing import Union
5 |
6 | # local
7 | from .between import between
8 | from .utils import validator
9 |
10 |
11 | @validator
12 | def length(value: str, /, *, min_val: Union[int, None] = None, max_val: Union[int, None] = None):
13 | """Return whether or not the length of given string is within a specified range.
14 |
15 | Examples:
16 | >>> length('something', min_val=2)
17 | True
18 | >>> length('something', min_val=9, max_val=9)
19 | True
20 | >>> length('something', max_val=5)
21 | ValidationError(func=length, args={'value': 'something', 'max_val': 5})
22 |
23 | Args:
24 | value:
25 | The string to validate.
26 | min_val:
27 | The minimum required length of the string. If not provided,
28 | minimum length will not be checked.
29 | max_val:
30 | The maximum length of the string. If not provided,
31 | maximum length will not be checked.
32 |
33 | Returns:
34 | (Literal[True]): If `len(value)` is in between the given conditions.
35 | (ValidationError): If `len(value)` is not in between the given conditions.
36 |
37 | Raises:
38 | (ValueError): If either `min_val` or `max_val` is negative.
39 | """
40 | if min_val is not None and min_val < 0:
41 | raise ValueError("Length cannot be negative. `min_val` is less than zero.")
42 | if max_val is not None and max_val < 0:
43 | raise ValueError("Length cannot be negative. `max_val` is less than zero.")
44 |
45 | return bool(between(len(value), min_val=min_val, max_val=max_val))
46 |
--------------------------------------------------------------------------------
/src/validators/mac_address.py:
--------------------------------------------------------------------------------
1 | """MAC Address."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from .utils import validator
8 |
9 |
10 | @validator
11 | def mac_address(value: str, /):
12 | """Return whether or not given value is a valid MAC address.
13 |
14 | This validator is based on [WTForms MacAddress validator][1].
15 |
16 | [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py#L482
17 |
18 | Examples:
19 | >>> mac_address('01:23:45:67:ab:CD')
20 | True
21 | >>> mac_address('00:00:00:00:00')
22 | ValidationError(func=mac_address, args={'value': '00:00:00:00:00'})
23 |
24 | Args:
25 | value:
26 | MAC address string to validate.
27 |
28 | Returns:
29 | (Literal[True]): If `value` is a valid MAC address.
30 | (ValidationError): If `value` is an invalid MAC address.
31 | """
32 | return re.match(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", value) if value else False
33 |
--------------------------------------------------------------------------------
/src/validators/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/python-validators/validators/e6d50ba532201ee9327b52ca084dcff9f60503d1/src/validators/py.typed
--------------------------------------------------------------------------------
/src/validators/slug.py:
--------------------------------------------------------------------------------
1 | """Slug."""
2 |
3 | # standard
4 | import re
5 |
6 | # local
7 | from .utils import validator
8 |
9 |
10 | @validator
11 | def slug(value: str, /):
12 | """Validate whether or not given value is valid slug.
13 |
14 | Valid slug can contain only lowercase alphanumeric characters and hyphens.
15 | It starts and ends with these lowercase alphanumeric characters.
16 |
17 | Examples:
18 | >>> slug('my-slug-2134')
19 | True
20 | >>> slug('my.slug')
21 | ValidationError(func=slug, args={'value': 'my.slug'})
22 |
23 | Args:
24 | value: Slug string to validate.
25 |
26 | Returns:
27 | (Literal[True]): If `value` is a valid slug.
28 | (ValidationError): If `value` is an invalid slug.
29 | """
30 | return re.match(r"^[a-z0-9]+(?:-[a-z0-9]+)*$", value) if value else False
31 |
--------------------------------------------------------------------------------
/src/validators/uri.py:
--------------------------------------------------------------------------------
1 | """URI."""
2 |
3 | # Read: https://stackoverflow.com/questions/176264
4 | # https://www.rfc-editor.org/rfc/rfc3986#section-3
5 |
6 | # local
7 | from .email import email
8 | from .url import url
9 | from .utils import validator
10 |
11 |
12 | def _file_url(value: str):
13 | if not value.startswith("file:///"):
14 | return False
15 | return True
16 |
17 |
18 | def _ipfs_url(value: str):
19 | if not value.startswith("ipfs://"):
20 | return False
21 | return True
22 |
23 |
24 | @validator
25 | def uri(value: str, /):
26 | """Return whether or not given value is a valid URI.
27 |
28 | Examples:
29 | >>> uri('mailto:example@domain.com')
30 | True
31 | >>> uri('file:path.txt')
32 | ValidationError(func=uri, args={'value': 'file:path.txt'})
33 |
34 | Args:
35 | value:
36 | URI to validate.
37 |
38 | Returns:
39 | (Literal[True]): If `value` is a valid URI.
40 | (ValidationError): If `value` is an invalid URI.
41 | """
42 | if not value:
43 | return False
44 |
45 | # TODO: work on various validations
46 |
47 | # url
48 | if any(
49 | # fmt: off
50 | value.startswith(item)
51 | for item in {
52 | "ftp",
53 | "ftps",
54 | "git",
55 | "http",
56 | "https",
57 | "irc",
58 | "rtmp",
59 | "rtmps",
60 | "rtsp",
61 | "sftp",
62 | "ssh",
63 | "telnet",
64 | }
65 | # fmt: on
66 | ):
67 | return url(value)
68 |
69 | # email
70 | if value.startswith("mailto:"):
71 | return email(value[len("mailto:") :])
72 |
73 | # file
74 | if value.startswith("file:"):
75 | return _file_url(value)
76 |
77 | # ipfs
78 | if value.startswith("ipfs:"):
79 | return _ipfs_url(value)
80 |
81 | # magnet
82 | if value.startswith("magnet:?"):
83 | return True
84 |
85 | # telephone
86 | if value.startswith("tel:"):
87 | return True
88 |
89 | # data
90 | if value.startswith("data:"):
91 | return True
92 |
93 | # urn
94 | if value.startswith("urn:"):
95 | return True
96 |
97 | # urc
98 | if value.startswith("urc:"):
99 | return True
100 |
101 | return False
102 |
--------------------------------------------------------------------------------
/src/validators/utils.py:
--------------------------------------------------------------------------------
1 | """Utils."""
2 |
3 | # standard
4 | from functools import wraps
5 | from inspect import getfullargspec
6 | from itertools import chain
7 | from os import environ
8 | from typing import Any, Callable, Dict
9 |
10 |
11 | class ValidationError(Exception):
12 | """Exception class when validation failure occurs."""
13 |
14 | def __init__(self, function: Callable[..., Any], arg_dict: Dict[str, Any], message: str = ""):
15 | """Initialize Validation Failure."""
16 | if message:
17 | self.reason = message
18 | self.func = function
19 | self.__dict__.update(arg_dict)
20 |
21 | def __repr__(self):
22 | """Repr Validation Failure."""
23 | return (
24 | f"ValidationError(func={self.func.__name__}, "
25 | + f"args={ ({k: v for (k, v) in self.__dict__.items() if k != 'func'}) })"
26 | )
27 |
28 | def __str__(self):
29 | """Str Validation Failure."""
30 | return repr(self)
31 |
32 | def __bool__(self):
33 | """Bool Validation Failure."""
34 | return False
35 |
36 |
37 | def _func_args_as_dict(func: Callable[..., Any], *args: Any, **kwargs: Any):
38 | """Return function's positional and key value arguments as an ordered dictionary."""
39 | return dict(
40 | list(zip(dict.fromkeys(chain(getfullargspec(func)[0], kwargs.keys())), args))
41 | + list(kwargs.items())
42 | )
43 |
44 |
45 | def validator(func: Callable[..., Any]):
46 | """A decorator that makes given function validator.
47 |
48 | Whenever the given `func` returns `False` this
49 | decorator returns `ValidationError` object.
50 |
51 | Examples:
52 | >>> @validator
53 | ... def even(value):
54 | ... return not (value % 2)
55 | >>> even(4)
56 | True
57 | >>> even(5)
58 | ValidationError(func=even, args={'value': 5})
59 |
60 | Args:
61 | func:
62 | Function which is to be decorated.
63 |
64 | Returns:
65 | (Callable[..., ValidationError | Literal[True]]):
66 | A decorator which returns either `ValidationError`
67 | or `Literal[True]`.
68 |
69 | Raises:
70 | (ValidationError): If `r_ve` or `RAISE_VALIDATION_ERROR` is `True`
71 | """
72 |
73 | @wraps(func)
74 | def wrapper(*args: Any, **kwargs: Any):
75 | raise_validation_error = False
76 | if "r_ve" in kwargs:
77 | raise_validation_error = True
78 | del kwargs["r_ve"]
79 | if environ.get("RAISE_VALIDATION_ERROR", "False") == "True":
80 | raise_validation_error = True
81 |
82 | try:
83 | if raise_validation_error:
84 | if func(*args, **kwargs):
85 | return True
86 | else:
87 | raise ValidationError(func, _func_args_as_dict(func, *args, **kwargs))
88 | else:
89 | return (
90 | True
91 | if func(*args, **kwargs)
92 | else ValidationError(func, _func_args_as_dict(func, *args, **kwargs))
93 | )
94 | except (ValueError, TypeError, UnicodeError) as exp:
95 | if raise_validation_error:
96 | raise ValidationError(
97 | func, _func_args_as_dict(func, *args, **kwargs), str(exp)
98 | ) from exp
99 | else:
100 | return ValidationError(func, _func_args_as_dict(func, *args, **kwargs), str(exp))
101 |
102 | return wrapper
103 |
--------------------------------------------------------------------------------
/src/validators/uuid.py:
--------------------------------------------------------------------------------
1 | """UUID."""
2 |
3 | # standard
4 | import re
5 | from typing import Union
6 | from uuid import UUID
7 |
8 | # local
9 | from .utils import validator
10 |
11 |
12 | @validator
13 | def uuid(value: Union[str, UUID], /):
14 | """Return whether or not given value is a valid UUID-v4 string.
15 |
16 | This validator is based on [WTForms UUID validator][1].
17 |
18 | [1]: https://github.com/wtforms/wtforms/blob/master/src/wtforms/validators.py#L539
19 |
20 | Examples:
21 | >>> uuid('2bc1c94f-0deb-43e9-92a1-4775189ec9f8')
22 | True
23 | >>> uuid('2bc1c94f 0deb-43e9-92a1-4775189ec9f8')
24 | ValidationError(func=uuid, args={'value': '2bc1c94f 0deb-43e9-92a1-4775189ec9f8'})
25 |
26 | Args:
27 | value:
28 | UUID string or object to validate.
29 |
30 | Returns:
31 | (Literal[True]): If `value` is a valid UUID.
32 | (ValidationError): If `value` is an invalid UUID.
33 | """
34 | if not value:
35 | return False
36 | if isinstance(value, UUID):
37 | return True
38 | try:
39 | return UUID(value) or re.match(
40 | r"^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$", value
41 | )
42 | except ValueError:
43 | return False
44 |
--------------------------------------------------------------------------------
/tests/crypto_addresses/test_bsc_address.py:
--------------------------------------------------------------------------------
1 | """Test BSC address."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, bsc_address
8 |
9 |
10 | @pytest.mark.parametrize(
11 | "value",
12 | [
13 | "0x4e5acf9684652BEa56F2f01b7101a225Ee33d23f",
14 | "0x22B0f92af10FdC25659e4C3A590c2F0D0c809c27",
15 | "0xb61724F993E7942ef2d8e4A94fF7c9e1cc26995F",
16 | "0x9c3dF8a511Fec8076D4B8EFb4d5E733B9F953dD7",
17 | "0x4536337B91c0623a4FD098023E6065e4773117c5",
18 | "0xAC484e1CE274eD1d40A7C2AeAb0bEA863634286F",
19 | "0x1FDE521fBe3483Cbb5957E6275028225a74387e4",
20 | "0x1693c3D1bA787Ba2bf81ac8897614AAaee5cb800",
21 | "0xf4C3Fd476A40658aEd9e595DA49c37d8965D2fFE",
22 | "0xc053E3D4932640787D6Cf67FcA36021E7BE62653",
23 | "0xaFd563A5aED0bC363e802842aD93Af46c1168b8a",
24 | ],
25 | )
26 | def test_returns_true_on_valid_bsc_address(value: str):
27 | """Test returns true on valid bsc address."""
28 | assert bsc_address(value)
29 |
30 |
31 | @pytest.mark.parametrize(
32 | "value",
33 | [
34 | "1x32Be343B94f860124dC4fEe278FDCBD38C102D88",
35 | "0x32Be343B94f860124dC4fEe278FDCBD38C102D",
36 | "0x32Be343B94f860124dC4fEe278FDCBD38C102D88aabbcc",
37 | "0x4g5acf9684652BEa56F2f01b7101a225Eh33d23z",
38 | "0x",
39 | "Wrong@Address.com",
40 | "0x32Be343B94f860124dC4fEe278FDCBD38C102D__",
41 | "0x32Be343B94f860124dC4fEe278FDCBD38C102D88G",
42 | "0X32Be343B94f860124dC4fEe278FDCBD38C102D88",
43 | "0X32BE343B94F860124DCFEE278FDCBD38C102D88",
44 | "0x32Be 343B94f860124dC4fEe278FDCBD38C102D88",
45 | "0x32Be343B94f860124dC4fEe278FDCBD38C102D88!",
46 | "ox32Be343B94f860124dC4fEe278FDCBD38C102D88",
47 | "0x32Be343B94f860124dC4fEe278FDCBD38C102D88XYZ",
48 | ],
49 | )
50 | def test_returns_failed_validation_on_invalid_bsc_address(value: str):
51 | """Test returns failed validation on invalid bsc address."""
52 | assert isinstance(bsc_address(value), ValidationError)
53 |
--------------------------------------------------------------------------------
/tests/crypto_addresses/test_btc_address.py:
--------------------------------------------------------------------------------
1 | """Test BTC address."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, btc_address
8 |
9 |
10 | @pytest.mark.parametrize(
11 | "value",
12 | [
13 | # P2PKH (Pay-to-PubkeyHash) type
14 | "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2",
15 | # P2SH (Pay to script hash) type
16 | "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy",
17 | # Bech32/segwit type
18 | "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
19 | "bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9",
20 | ],
21 | )
22 | def test_returns_true_on_valid_btc_address(value: str):
23 | """Test returns true on valid btc address."""
24 | assert btc_address(value)
25 |
26 |
27 | @pytest.mark.parametrize(
28 | "value",
29 | [
30 | "ff3Cwgr2g7vsi1bXDUkpEnVoRLA9w4FZfC69",
31 | "b3Cgwgr2g7vsi1bXyjyDUkphEnVoRLA9w4FZfC69",
32 | # incorrect header
33 | "1BvBMsEYstWetqTFn5Au4m4GFg7xJaNVN2",
34 | # incorrect checksum
35 | "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLz",
36 | ],
37 | )
38 | def test_returns_failed_validation_on_invalid_btc_address(value: str):
39 | """Test returns failed validation on invalid btc address."""
40 | assert isinstance(btc_address(value), ValidationError)
41 |
--------------------------------------------------------------------------------
/tests/crypto_addresses/test_eth_address.py:
--------------------------------------------------------------------------------
1 | """Test ETH address."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, eth_address
8 |
9 |
10 | @pytest.mark.parametrize(
11 | "value",
12 | [
13 | "0x8ba1f109551bd432803012645ac136ddd64dba72",
14 | "0x9cc14ba4f9f68ca159ea4ebf2c292a808aaeb598",
15 | "0x5AEDA56215b167893e80B4fE645BA6d5Bab767DE",
16 | "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
17 | "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
18 | "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
19 | "0x1234567890123456789012345678901234567890",
20 | "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51",
21 | ],
22 | )
23 | def test_returns_true_on_valid_eth_address(value: str):
24 | """Test returns true on valid eth address."""
25 | assert eth_address(value)
26 |
27 |
28 | @pytest.mark.parametrize(
29 | "value",
30 | [
31 | "0x742d35Cc6634C0532925a3b844Bc454e4438f44g",
32 | "0x742d35Cc6634C0532925a3b844Bc454e4438f44",
33 | "0xAbcdefg1234567890Abcdefg1234567890Abcdefg",
34 | "0x7c8EE9977c6f96b6b9774b3e8e4Cc9B93B12b2c72",
35 | "0x80fBD7F8B3f81D0e1d6EACAb69AF104A6508AFB1",
36 | "0x7c8EE9977c6f96b6b9774b3e8e4Cc9B93B12b2c7g",
37 | "0x7c8EE9977c6f96b6b9774b3e8e4Cc9B93B12b2c",
38 | "0x7Fb21a171205f3B8d8E4d88A2d2f8A56E45DdB5c",
39 | "validators.eth",
40 | ],
41 | )
42 | def test_returns_failed_validation_on_invalid_eth_address(value: str):
43 | """Test returns failed validation on invalid eth address."""
44 | assert isinstance(eth_address(value), ValidationError)
45 |
--------------------------------------------------------------------------------
/tests/crypto_addresses/test_trx_address.py:
--------------------------------------------------------------------------------
1 | """Test TRX address."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, trx_address
8 |
9 |
10 | @pytest.mark.parametrize(
11 | "value",
12 | [
13 | "TLjfbTbpZYDQ4EoA4N5CLNgGjfbF8ZWz38",
14 | "TDQ6C92wuNqvMWE967sMptCFaXq77uj1PF",
15 | "TFuGbxCQGSL4oLnJzVsen844LDwFbrUY4e",
16 | "TFAPKADDRhkSe3v27CsR8TZSjN8eJ8ycDK",
17 | "TSJHywLNva2MNjCD5iYfn5QAKD9Rk5Ncit",
18 | "TEi1qhi5LuTicg1u9oAstyXCSf5uibSyqo",
19 | "TAGvx5An6VBeHTu91cQwdABNcAYMRPcP4n",
20 | "TXbE5tXTejqT3Q47sYKCDb9NJDm3xrFpab",
21 | "TMTxQWNuWHXvHcYXc5D1wQhFmZFJijAxcG",
22 | "TPHgw9E8QYM3esNWih5KVnUVpUHwLTPfpA",
23 | "TFFLtBTi9jdaGwV3hznjCmPYaJme5AeqwU",
24 | "TC74QG8tbtixG5Raa4fEifywgjrFs45fNz",
25 | ],
26 | )
27 | def test_returns_true_on_valid_trx_address(value: str):
28 | """Test returns true on valid trx address."""
29 | assert trx_address(value)
30 |
31 |
32 | @pytest.mark.parametrize(
33 | "value",
34 | [
35 | "T12345678901234567890123456789012345",
36 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678",
37 | "TR2G7Rm4vFqF8EpY4U5xdLdQ7XgJ2U8Vd",
38 | "TP6ah2v5mdsj8Z3hGz1yDMvDq7BzEbK8o",
39 | "TQmmhp6uz2Xre8yL3FsPYZyo4mhtw4vg4XX",
40 | "TQNy2C6VHJPk4P32bsEX3QSGx2Qqm4J2k9",
41 | "TP6ah2v5mdsj8Z3hGz1yDMvDq7BzEbK8oN",
42 | "TSTVdfU1x4L7K3Bc3v5C28Gp2J1rPyeL3f",
43 | "THPByuCzvU5QER9j2NC2mUQ2JPyRCam4e7",
44 | "TW5eZqUZgdW4rxFKAKsc2ryJbfFA94WXvD",
45 | "TR2G7Rm4vFqF8EpY4U5xdLdQ7XgJ2U8Vdd",
46 | "tQmmhp6uz2Xre8yL3FsPYZyo4mhtw4vg4X",
47 | "TR2G7Rm4vFqF8EpY4U5xdLdQ7Xg",
48 | "TQmmhp6uz2Xre8yL3FsPYZyo4mhtw4vg4x",
49 | "my-trox-address.trx",
50 | ],
51 | )
52 | def test_returns_failed_validation_on_invalid_trx_address(value: str):
53 | """Test returns failed validation on invalid trx address."""
54 | assert isinstance(trx_address(value), ValidationError)
55 |
--------------------------------------------------------------------------------
/tests/i18n/test_es.py:
--------------------------------------------------------------------------------
1 | """Test i18n/es."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, es_cif, es_doi, es_nie, es_nif
8 |
9 |
10 | @pytest.mark.parametrize(
11 | ("value",),
12 | [
13 | ("B25162520",),
14 | ("U4839822F",),
15 | ("B96817697",),
16 | ("P7067074J",),
17 | ("Q7899705C",),
18 | ("C75098681",),
19 | ("G76061860",),
20 | ("C71345375",),
21 | ("G20558169",),
22 | ("U5021960I",),
23 | ],
24 | )
25 | def test_returns_true_on_valid_cif(value: str):
26 | """Test returns true on valid cif."""
27 | assert es_cif(value)
28 |
29 |
30 | @pytest.mark.parametrize(
31 | ("value",),
32 | [
33 | ("12345",),
34 | ("ABCDEFGHI",),
35 | ("Z5021960I",),
36 | ],
37 | )
38 | def test_returns_false_on_invalid_cif(value: str):
39 | """Test returns false on invalid cif."""
40 | result = es_cif(value)
41 | assert isinstance(result, ValidationError)
42 |
43 |
44 | @pytest.mark.parametrize(
45 | ("value",),
46 | [
47 | ("X0095892M",),
48 | ("X8868108K",),
49 | ("X2911154K",),
50 | ("Y2584969J",),
51 | ("X7536157T",),
52 | ("Y5840388N",),
53 | ("Z2915723H",),
54 | ("Y4002236C",),
55 | ("X7750702R",),
56 | ("Y0408759V",),
57 | ],
58 | )
59 | def test_returns_true_on_valid_nie(value: str):
60 | """Test returns true on valid nie."""
61 | assert es_nie(value)
62 |
63 |
64 | @pytest.mark.parametrize(
65 | ("value",),
66 | [
67 | ("K0000023T",),
68 | ("L0000024R",),
69 | ("M0000025W",),
70 | ("00000026A",),
71 | ("00000027G",),
72 | ("00000028M",),
73 | ("00000029Y",),
74 | ("00000030F",),
75 | ("00000031P",),
76 | ("00000032D",),
77 | ("00000033X",),
78 | ("00000034B",),
79 | ("00000035N",),
80 | ("00000036J",),
81 | ("00000037Z",),
82 | ("00000038S",),
83 | ("00000039Q",),
84 | ("00000040V",),
85 | ("00000041H",),
86 | ("00000042L",),
87 | ("00000043C",),
88 | ("00000044K",),
89 | ("00000045E",),
90 | ],
91 | )
92 | def test_returns_true_on_valid_nif(value: str):
93 | """Test returns true on valid nif."""
94 | assert es_nif(value)
95 |
96 |
97 | def test_returns_false_on_invalid_nif():
98 | """Test returns false on invalid nif."""
99 | result = es_nif("12345")
100 | assert isinstance(result, ValidationError)
101 |
102 |
103 | @pytest.mark.parametrize(
104 | ("value",),
105 | [
106 | # CIFs
107 | ("B25162520",),
108 | ("U4839822F",),
109 | ("B96817697",),
110 | # NIEs
111 | ("X0000000T",),
112 | ("X0095892M",),
113 | ("X8868108K",),
114 | ("X2911154K",),
115 | # NIFs
116 | ("00000001R",),
117 | ("00000000T",),
118 | ("26643189N",),
119 | ("07060225F",),
120 | ("49166693F",),
121 | ],
122 | )
123 | def test_returns_true_on_valid_doi(value: str):
124 | """Test returns true on valid doi."""
125 | assert es_doi(value)
126 |
--------------------------------------------------------------------------------
/tests/i18n/test_fi.py:
--------------------------------------------------------------------------------
1 | """Test i18n/es."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError
8 | from validators.i18n.fi import fi_business_id, fi_ssn
9 |
10 |
11 | @pytest.mark.parametrize(
12 | ("value",),
13 | [
14 | ("2336509-6",), # Supercell
15 | ("0112038-9",), # Fast Monkeys
16 | ("2417581-7",), # Nokia
17 | ],
18 | )
19 | def test_returns_true_on_valid_business_id(value: str):
20 | """Test returns true on valid business id."""
21 | assert fi_business_id(value)
22 |
23 |
24 | @pytest.mark.parametrize(
25 | ("value",),
26 | [
27 | (None,),
28 | ("",),
29 | ("1233312312",),
30 | ("1333333-8",),
31 | ("1231233-9",),
32 | ],
33 | )
34 | def test_returns_failed_validation_on_invalid_business_id(value: str):
35 | """Test returns failed validation on invalid business id."""
36 | assert isinstance(fi_business_id(value), ValidationError)
37 |
38 |
39 | @pytest.mark.parametrize(
40 | ("value",),
41 | [
42 | ("010190-002R",),
43 | ("010101-0101",),
44 | ("010101+0101",),
45 | ("010101A0101",),
46 | ("010190-900P",),
47 | ("020516C903K",),
48 | ("010594Y9032",),
49 | ],
50 | )
51 | def test_returns_true_on_valid_ssn(value: str):
52 | """Test returns true on valid ssn."""
53 | assert fi_ssn(value)
54 |
55 |
56 | @pytest.mark.parametrize(
57 | ("value",),
58 | [
59 | (None,),
60 | ("",),
61 | ("010190-001P",), # Too low serial
62 | ("010190-000N",), # Too low serial
63 | ("000190-0023",), # Invalid day
64 | ("010090-002X",), # Invalid month
65 | ("010190-002r",), # Invalid checksum
66 | ("101010-0102",),
67 | ("10a010-0101",),
68 | ("101010-0\xe401",),
69 | ("101010b0101",),
70 | ("0205169C03K",),
71 | ("0105949Y032",),
72 | ],
73 | )
74 | def test_returns_failed_validation_on_invalid_ssn(value: str):
75 | """Test returns failed validation on invalid_ssn."""
76 | assert isinstance(fi_ssn(value), ValidationError)
77 |
78 |
79 | def test_returns_failed_validation_on_temporal_ssn_when_not_allowed():
80 | """Test returns failed validation on temporal-ssn when not allowed."""
81 | assert isinstance(fi_ssn("010190-900P", allow_temporal_ssn=False), ValidationError)
82 |
--------------------------------------------------------------------------------
/tests/i18n/test_fr.py:
--------------------------------------------------------------------------------
1 | """Test French validators."""
2 |
3 | # standard
4 | from typing import Union
5 |
6 | # external
7 | import pytest
8 |
9 | # local
10 | from validators import ValidationError
11 | from validators.i18n.fr import fr_department, fr_ssn
12 |
13 |
14 | @pytest.mark.parametrize(
15 | ("value",),
16 | [
17 | ("1 84 12 76 451 089 46",),
18 | ("1 84 12 76 451 089",), # control key is optional
19 | ("2 99 05 75 202 818 97",),
20 | ("2 99 05 75 202 817 01",),
21 | ("2 99 05 2A 202 817 58",),
22 | ("2 99 05 2B 202 817 85",),
23 | ("2 99 05 971 12 817 70",),
24 | ],
25 | )
26 | def test_returns_true_on_valid_ssn(value: str):
27 | """Test returns true on valid ssn."""
28 | assert fr_ssn(value)
29 |
30 |
31 | @pytest.mark.parametrize(
32 | ("value",),
33 | [
34 | (None,),
35 | ("",),
36 | ("3 84 12 76 451 089 46",), # wrong gender number
37 | ("1 84 12 76 451 089 47",), # wrong control key
38 | ("1 84 00 76 451 089",), # invalid month
39 | ("1 84 13 76 451 089",), # invalid month
40 | ("1 84 12 00 451 089",), # invalid department
41 | ("1 84 12 2C 451 089",),
42 | ("1 84 12 98 451 089",), # invalid department
43 | # ("1 84 12 971 451 089",), # ?
44 | ],
45 | )
46 | def test_returns_failed_validation_on_invalid_ssn(value: str):
47 | """Test returns failed validation on invalid_ssn."""
48 | assert isinstance(fr_ssn(value), ValidationError)
49 |
50 |
51 | @pytest.mark.parametrize(
52 | ("value",),
53 | [
54 | ("01",),
55 | ("2A",), # Corsica
56 | ("2B",),
57 | (14,),
58 | ("95",),
59 | ("971",),
60 | (971,),
61 | ],
62 | )
63 | def test_returns_true_on_valid_department(value: Union[str, int]):
64 | """Test returns true on valid department."""
65 | assert fr_department(value)
66 |
67 |
68 | @pytest.mark.parametrize(
69 | ("value",),
70 | [
71 | (None,),
72 | ("",),
73 | ("00",),
74 | (0,),
75 | ("2C",),
76 | ("97",),
77 | ("978",),
78 | ("98",),
79 | ("96",),
80 | ("20",),
81 | (20,),
82 | ],
83 | )
84 | def test_returns_failed_validation_on_invalid_department(value: Union[str, int]):
85 | """Test returns failed validation on invalid department."""
86 | assert isinstance(fr_department(value), ValidationError)
87 |
--------------------------------------------------------------------------------
/tests/i18n/test_ind.py:
--------------------------------------------------------------------------------
1 | """Test Indian validators."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError
8 | from validators.i18n import ind_aadhar, ind_pan
9 |
10 |
11 | @pytest.mark.parametrize("value", ["3675 9834 6012", "5046 3182 4299"])
12 | def test_returns_true_on_valid_ind_aadhar(value: str):
13 | """Test returns true on valid ind aadhar."""
14 | assert ind_aadhar(value)
15 |
16 |
17 | @pytest.mark.parametrize("value", ["3675 9834 6012 8", "417598346012", "3675 98AF 60#2"])
18 | def test_returns_failed_validation_on_invalid_ind_aadhar(value: str):
19 | """Test returns failed validation on invalid ind aadhar."""
20 | assert isinstance(ind_aadhar(value), ValidationError)
21 |
22 |
23 | @pytest.mark.parametrize("value", ["ABCDE9999K", "AAAPL1234C"])
24 | def test_returns_true_on_valid_ind_pan(value: str):
25 | """Test returns true on valid ind pan."""
26 | assert ind_pan(value)
27 |
28 |
29 | @pytest.mark.parametrize("value", ["ABC5d7896B", "417598346012", "AaaPL1234C"])
30 | def test_returns_failed_validation_on_invalid_ind_pan(value: str):
31 | """Test returns failed validation on invalid ind pan."""
32 | assert isinstance(ind_pan(value), ValidationError)
33 |
--------------------------------------------------------------------------------
/tests/i18n/test_ru.py:
--------------------------------------------------------------------------------
1 | """Test i18n/inn."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError
8 | from validators.i18n.ru import ru_inn
9 |
10 |
11 | @pytest.mark.parametrize(
12 | ("value",),
13 | [
14 | ("2222058686",),
15 | ("7709439560",),
16 | ("5003052454",),
17 | ("7730257499",),
18 | ("3664016814",),
19 | ("026504247480",),
20 | ("780103209220",),
21 | ("7707012148",),
22 | ("140700989885",),
23 | ("774334078053",),
24 | ],
25 | )
26 | def test_returns_true_on_valid_ru_inn(value: str):
27 | """Test returns true on valid russian individual tax number."""
28 | assert ru_inn(value)
29 |
30 |
31 | @pytest.mark.parametrize(
32 | ("value",),
33 | [
34 | ("2222058687",),
35 | ("7709439561",),
36 | ("5003052453",),
37 | ("7730257490",),
38 | ("3664016815",),
39 | ("026504247481",),
40 | ("780103209222",),
41 | ("7707012149",),
42 | ("140700989886",),
43 | ("774334078054",),
44 | ],
45 | )
46 | def test_returns_false_on_valid_ru_inn(value: str):
47 | """Test returns true on valid russian individual tax number."""
48 | assert isinstance(ru_inn(value), ValidationError)
49 |
--------------------------------------------------------------------------------
/tests/test__extremes.py:
--------------------------------------------------------------------------------
1 | """Test Extremes."""
2 |
3 | # standard
4 | from typing import Any
5 |
6 | # external
7 | import pytest
8 |
9 | # local
10 | from validators._extremes import AbsMax, AbsMin
11 |
12 | abs_max = AbsMax()
13 | abs_min = AbsMin()
14 |
15 |
16 | @pytest.mark.parametrize(
17 | ("value",),
18 | [(None,), ("",), (12,), (abs_min,)],
19 | )
20 | def test_abs_max_is_greater_than_every_other_value(value: Any):
21 | """Test if AbsMax is greater than every other value."""
22 | assert value < abs_max
23 | assert abs_max > value
24 |
25 |
26 | def test_abs_max_is_not_greater_than_itself():
27 | """Test if AbsMax is not greater than itself."""
28 | assert not (abs_max > abs_max)
29 |
30 |
31 | def test_other_comparison_methods_for_abs_max():
32 | """Test other comparison methods for AbsMax."""
33 | assert abs_max <= abs_max
34 | assert abs_max == abs_max
35 | assert abs_max == abs_max
36 |
37 |
38 | @pytest.mark.parametrize(
39 | ("value",),
40 | [(None,), ("",), (12,), (abs_max,)],
41 | )
42 | def test_abs_min_is_smaller_than_every_other_value(value: Any):
43 | """Test if AbsMin is less than every other value."""
44 | assert value > abs_min
45 |
46 |
47 | def test_abs_min_is_not_greater_than_itself():
48 | """Test if AbsMin is not less than itself."""
49 | assert not (abs_min < abs_min)
50 |
51 |
52 | def test_other_comparison_methods_for_abs_min():
53 | """Test other comparison methods for AbsMin."""
54 | assert abs_min <= abs_min
55 | assert abs_min == abs_min
56 | assert abs_min == abs_min
57 |
--------------------------------------------------------------------------------
/tests/test_between.py:
--------------------------------------------------------------------------------
1 | """Test Between."""
2 |
3 | # standard
4 | from datetime import datetime
5 | from typing import TypeVar
6 |
7 | # external
8 | import pytest
9 |
10 | # local
11 | from validators import ValidationError, between
12 |
13 | T = TypeVar("T", int, float, str, datetime)
14 |
15 |
16 | @pytest.mark.parametrize(
17 | ("value", "min_val", "max_val"),
18 | [(12, 11, 13), (12, None, 14), (12, 11, None), (12, 12, 12), (0, 0, 0), (0, -1, 3)],
19 | )
20 | def test_returns_true_on_valid_range(value: T, min_val: T, max_val: T):
21 | """Test returns true on valid range."""
22 | assert between(value, min_val=min_val, max_val=max_val)
23 |
24 |
25 | @pytest.mark.parametrize(
26 | ("value", "min_val", "max_val"),
27 | [
28 | (None, 13, 14),
29 | (12, 13, 14),
30 | (12, None, 11),
31 | (12, 13, None),
32 | (12, "13.5", datetime(1970, 1, 1)),
33 | ("12", 20.5, "None"),
34 | (datetime(1970, 1, 1), 20, "string"),
35 | (30, 40, "string"),
36 | ],
37 | )
38 | def test_returns_failed_validation_on_invalid_range(value: T, min_val: T, max_val: T):
39 | """Test returns failed validation on invalid range."""
40 | assert isinstance(between(value, min_val=min_val, max_val=max_val), ValidationError)
41 |
--------------------------------------------------------------------------------
/tests/test_card.py:
--------------------------------------------------------------------------------
1 | """Test Card."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import (
8 | ValidationError,
9 | amex,
10 | card_number,
11 | diners,
12 | discover,
13 | jcb,
14 | mastercard,
15 | mir,
16 | unionpay,
17 | visa,
18 | )
19 |
20 | visa_cards = ["4242424242424242", "4000002760003184"]
21 | mastercard_cards = ["5555555555554444", "2223003122003222"]
22 | amex_cards = ["378282246310005", "371449635398431"]
23 | unionpay_cards = ["6200000000000005"]
24 | diners_cards = ["3056930009020004", "36227206271667"]
25 | jcb_cards = ["3566002020360505"]
26 | discover_cards = ["6011111111111117", "6011000990139424"]
27 | mir_cards = ["2200123456789019", "2204987654321098"]
28 |
29 |
30 | @pytest.mark.parametrize(
31 | "value",
32 | visa_cards
33 | + mastercard_cards
34 | + amex_cards
35 | + unionpay_cards
36 | + diners_cards
37 | + jcb_cards
38 | + discover_cards
39 | + mir_cards,
40 | )
41 | def test_returns_true_on_valid_card_number(value: str):
42 | """Test returns true on valid card number."""
43 | assert card_number(value)
44 |
45 |
46 | @pytest.mark.parametrize(
47 | "value",
48 | [
49 | "4242424242424240",
50 | "4000002760003180",
51 | "400000276000318X",
52 | "220012345678901X",
53 | ],
54 | )
55 | def test_returns_failed_on_valid_card_number(value: str):
56 | """Test returns failed on valid card number."""
57 | assert isinstance(card_number(value), ValidationError)
58 |
59 |
60 | @pytest.mark.parametrize("value", visa_cards)
61 | def test_returns_true_on_valid_visa(value: str):
62 | """Test returns true on valid visa."""
63 | assert visa(value)
64 |
65 |
66 | @pytest.mark.parametrize(
67 | "value",
68 | mastercard_cards + amex_cards + unionpay_cards + diners_cards + jcb_cards + discover_cards,
69 | )
70 | def test_returns_failed_on_valid_visa(value: str):
71 | """Test returns failed on valid visa."""
72 | assert isinstance(visa(value), ValidationError)
73 |
74 |
75 | @pytest.mark.parametrize("value", mastercard_cards)
76 | def test_returns_true_on_valid_mastercard(value: str):
77 | """Test returns true on valid mastercard."""
78 | assert mastercard(value)
79 |
80 |
81 | @pytest.mark.parametrize(
82 | "value",
83 | visa_cards + amex_cards + unionpay_cards + diners_cards + jcb_cards + discover_cards,
84 | )
85 | def test_returns_failed_on_valid_mastercard(value: str):
86 | """Test returns failed on valid mastercard."""
87 | assert isinstance(mastercard(value), ValidationError)
88 |
89 |
90 | @pytest.mark.parametrize("value", amex_cards)
91 | def test_returns_true_on_valid_amex(value: str):
92 | """Test returns true on valid amex."""
93 | assert amex(value)
94 |
95 |
96 | @pytest.mark.parametrize(
97 | "value",
98 | visa_cards
99 | + mastercard_cards
100 | + unionpay_cards
101 | + diners_cards
102 | + jcb_cards
103 | + discover_cards
104 | + mir_cards,
105 | )
106 | def test_returns_failed_on_valid_amex(value: str):
107 | """Test returns failed on valid amex."""
108 | assert isinstance(amex(value), ValidationError)
109 |
110 |
111 | @pytest.mark.parametrize("value", unionpay_cards)
112 | def test_returns_true_on_valid_unionpay(value: str):
113 | """Test returns true on valid unionpay."""
114 | assert unionpay(value)
115 |
116 |
117 | @pytest.mark.parametrize(
118 | "value",
119 | visa_cards
120 | + mastercard_cards
121 | + amex_cards
122 | + diners_cards
123 | + jcb_cards
124 | + discover_cards
125 | + mir_cards,
126 | )
127 | def test_returns_failed_on_valid_unionpay(value: str):
128 | """Test returns failed on valid unionpay."""
129 | assert isinstance(unionpay(value), ValidationError)
130 |
131 |
132 | @pytest.mark.parametrize("value", diners_cards)
133 | def test_returns_true_on_valid_diners(value: str):
134 | """Test returns true on valid diners."""
135 | assert diners(value)
136 |
137 |
138 | @pytest.mark.parametrize(
139 | "value",
140 | visa_cards
141 | + mastercard_cards
142 | + amex_cards
143 | + unionpay_cards
144 | + jcb_cards
145 | + discover_cards
146 | + mir_cards,
147 | )
148 | def test_returns_failed_on_valid_diners(value: str):
149 | """Test returns failed on valid diners."""
150 | assert isinstance(diners(value), ValidationError)
151 |
152 |
153 | @pytest.mark.parametrize("value", jcb_cards)
154 | def test_returns_true_on_valid_jcb(value: str):
155 | """Test returns true on valid jcb."""
156 | assert jcb(value)
157 |
158 |
159 | @pytest.mark.parametrize(
160 | "value",
161 | visa_cards
162 | + mastercard_cards
163 | + amex_cards
164 | + unionpay_cards
165 | + diners_cards
166 | + discover_cards
167 | + mir_cards,
168 | )
169 | def test_returns_failed_on_valid_jcb(value: str):
170 | """Test returns failed on valid jcb."""
171 | assert isinstance(jcb(value), ValidationError)
172 |
173 |
174 | @pytest.mark.parametrize("value", discover_cards)
175 | def test_returns_true_on_valid_discover(value: str):
176 | """Test returns true on valid discover."""
177 | assert discover(value)
178 |
179 |
180 | @pytest.mark.parametrize(
181 | "value",
182 | visa_cards
183 | + mastercard_cards
184 | + amex_cards
185 | + unionpay_cards
186 | + diners_cards
187 | + jcb_cards
188 | + mir_cards,
189 | )
190 | def test_returns_failed_on_valid_discover(value: str):
191 | """Test returns failed on valid discover."""
192 | assert isinstance(discover(value), ValidationError)
193 |
194 |
195 | @pytest.mark.parametrize("value", mir_cards)
196 | def test_returns_true_on_valid_mir(value: str):
197 | """Test returns true on valid Mir card."""
198 | assert mir(value)
199 |
200 |
201 | @pytest.mark.parametrize(
202 | "value",
203 | visa_cards
204 | + mastercard_cards
205 | + amex_cards
206 | + unionpay_cards
207 | + diners_cards
208 | + jcb_cards
209 | + discover_cards,
210 | )
211 | def test_returns_failed_on_valid_mir(value: str):
212 | """Test returns failed on invalid Mir card (other payment systems)."""
213 | assert isinstance(mir(value), ValidationError)
214 |
--------------------------------------------------------------------------------
/tests/test_country.py:
--------------------------------------------------------------------------------
1 | """Test Country."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, calling_code, country_code, currency
8 |
9 |
10 | @pytest.mark.parametrize(("value"), ["+1", "+371"])
11 | def test_returns_true_on_valid_calling_code(value: str):
12 | """Test returns true on valid calling code."""
13 | assert calling_code(value)
14 |
15 |
16 | @pytest.mark.parametrize(("value"), ["+19", "+37", "-9"])
17 | def test_returns_failed_validation_invalid_calling_code(value: str):
18 | """Test returns failed validation invalid calling code."""
19 | assert isinstance(calling_code(value), ValidationError)
20 |
21 |
22 | @pytest.mark.parametrize(
23 | ("value", "iso_format"),
24 | [
25 | ("ISR", "auto"),
26 | ("US", "alpha2"),
27 | ("USA", "alpha3"),
28 | ("840", "numeric"),
29 | ],
30 | )
31 | def test_returns_true_on_valid_country_code(value: str, iso_format: str):
32 | """Test returns true on valid country code."""
33 | assert country_code(value, iso_format=iso_format)
34 |
35 |
36 | @pytest.mark.parametrize(
37 | ("value", "iso_format"),
38 | [
39 | (None, "auto"),
40 | ("", "auto"),
41 | ("123456", "auto"),
42 | ("XY", "alpha2"),
43 | ("PPP", "alpha3"),
44 | ("123", "numeric"),
45 | ("us", "auto"),
46 | ("uSa", "auto"),
47 | ("US ", "auto"),
48 | ("U.S", "auto"),
49 | ("1ND", "unknown"),
50 | ("ISR", None),
51 | ],
52 | )
53 | def test_returns_failed_validation_on_invalid_country_code(value: str, iso_format: str):
54 | """Test returns failed validation on invalid country code."""
55 | assert isinstance(country_code(value, iso_format=iso_format), ValidationError)
56 |
57 |
58 | @pytest.mark.parametrize(
59 | ("value", "skip_symbols", "ignore_case"), [("$", False, False), ("uSd", True, True)]
60 | )
61 | def test_returns_true_on_valid_currency(value: str, skip_symbols: bool, ignore_case: bool):
62 | """Test returns true on valid currency."""
63 | assert currency(value, skip_symbols=skip_symbols, ignore_case=ignore_case)
64 |
65 |
66 | @pytest.mark.parametrize(
67 | ("value", "skip_symbols", "ignore_case"),
68 | [("$", True, False), ("uSd", True, False), ("Bucks", True, True)],
69 | )
70 | def test_returns_failed_validation_invalid_currency(
71 | value: str, skip_symbols: bool, ignore_case: bool
72 | ):
73 | """Test returns failed validation invalid currency."""
74 | assert isinstance(
75 | currency(value, skip_symbols=skip_symbols, ignore_case=ignore_case), ValidationError
76 | )
77 |
--------------------------------------------------------------------------------
/tests/test_cron.py:
--------------------------------------------------------------------------------
1 | """Test Cron."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, cron
8 |
9 |
10 | @pytest.mark.parametrize(
11 | "value",
12 | [
13 | "* * * * *",
14 | "*/5 * * * *",
15 | "0 0 * * *",
16 | "30 3 * * 1-5",
17 | "15 5 * * 1,3,5",
18 | "0 12 1 */2 *",
19 | "0 */3 * * *",
20 | "0 0 1 1 *",
21 | "0 12 * 1-6 1-5",
22 | "0 3-6 * * *",
23 | "*/15 0,6,12,18 * * *",
24 | "0 12 * * 0",
25 | "*/61 * * * *",
26 | # "5-10/2 * * * *", # this is valid, but not supported yet
27 | ],
28 | )
29 | def test_returns_true_on_valid_cron(value: str):
30 | """Test returns true on valid cron string."""
31 | assert cron(value)
32 |
33 |
34 | @pytest.mark.parametrize(
35 | "value",
36 | [
37 | "* * * * * *",
38 | "* * * *",
39 | "*/5 25 * * *",
40 | "*/5 * *-1 * *",
41 | "32-30 * * * *",
42 | "0 12 32 * *",
43 | "0 12 * * 8",
44 | "0 */0 * * *",
45 | "30-20 * * * *",
46 | "10-* * * * *",
47 | "*/15 0,6,12,24 * * *",
48 | "& * * & * *",
49 | "* - * * - *",
50 | ],
51 | )
52 | def test_returns_failed_validation_on_invalid_cron(value: str):
53 | """Test returns failed validation on invalid cron string."""
54 | assert isinstance(cron(value), ValidationError)
55 |
--------------------------------------------------------------------------------
/tests/test_domain.py:
--------------------------------------------------------------------------------
1 | """Test Domain."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, domain
8 |
9 |
10 | @pytest.mark.parametrize(
11 | ("value", "rfc_1034", "rfc_2782"),
12 | [
13 | ("example.com", False, False),
14 | ("exa_mple.com", False, True),
15 | ("xn----gtbspbbmkef.xn--p1ai", False, False),
16 | ("underscore_subdomain.example.com", False, True),
17 | ("something.versicherung", False, False),
18 | ("someThing.versicherung.", True, False),
19 | ("11.com", False, False),
20 | ("3.cn.", True, False),
21 | ("_example.com", False, True),
22 | ("example_.com", False, True),
23 | ("_exa_mple_.com", False, True),
24 | ("a.cn", False, False),
25 | ("sub1.sub2.sample.co.uk", False, False),
26 | ("somerandomexample.xn--fiqs8s", False, False),
27 | ("kräuter.com.", True, False),
28 | ("über.com", False, False),
29 | ],
30 | )
31 | def test_returns_true_on_valid_domain(value: str, rfc_1034: bool, rfc_2782: bool):
32 | """Test returns true on valid domain."""
33 | assert domain(value, rfc_1034=rfc_1034, rfc_2782=rfc_2782)
34 |
35 |
36 | @pytest.mark.parametrize(
37 | ("value", "consider_tld", "rfc_1034", "rfc_2782"),
38 | [
39 | ("example.com", True, False, False),
40 | ("exa_mple.com", True, False, True),
41 | ("xn----gtbspbbmkef.xn--p1ai", True, False, False),
42 | ("underscore_subdomain.example.com", True, False, True),
43 | ("someThing.versicherung.", True, True, False),
44 | ("11.com", True, False, False),
45 | ("3.cn.", True, True, False),
46 | ("_example.com", True, False, True),
47 | ("example_.com", True, False, True),
48 | ("somerandomexample.xn--fiqs8s", True, False, False),
49 | ("somerandomexample.onion", True, False, False),
50 | ],
51 | )
52 | def test_returns_true_on_valid_top_level_domain(
53 | value: str, consider_tld: bool, rfc_1034: bool, rfc_2782: bool
54 | ):
55 | """Test returns true on valid top level domain."""
56 | assert domain(value, consider_tld=consider_tld, rfc_1034=rfc_1034, rfc_2782=rfc_2782)
57 |
58 |
59 | @pytest.mark.parametrize(
60 | ("value", "rfc_1034", "rfc_2782"),
61 | [
62 | ("example.com/.", True, False),
63 | ("example.com:4444", False, False),
64 | ("example.-com", False, False),
65 | ("example.", False, False),
66 | ("-example.com", False, False),
67 | ("example-.com.", True, False),
68 | ("_example.com", False, False),
69 | ("_example._com", False, False),
70 | ("example_.com", False, False),
71 | ("example", False, False),
72 | ("example.com!", True, False),
73 | ("example?.com", True, False),
74 | ("__exa__mple__.com", False, True),
75 | ("a......b.com", False, False),
76 | ("a.123", False, False),
77 | ("123.123", False, False),
78 | ("123.123.123.", True, False),
79 | ("123.123.123.123", False, False),
80 | ],
81 | )
82 | def test_returns_failed_validation_on_invalid_domain(value: str, rfc_1034: bool, rfc_2782: bool):
83 | """Test returns failed validation on invalid domain."""
84 | assert isinstance(domain(value, rfc_1034=rfc_1034, rfc_2782=rfc_2782), ValidationError)
85 |
86 |
87 | @pytest.mark.parametrize(
88 | ("value", "consider_tld", "rfc_1034", "rfc_2782"),
89 | [
90 | ("example.266", True, False, False),
91 | ("exa_mple.org_", True, False, True),
92 | ("xn----gtbspbbmkef.xn-p1ai", True, False, False),
93 | ("underscore_subdomain.example.flat", True, False, True),
94 | ("someThing.versicherung.reddit.", True, True, False),
95 | ("11.twitter", True, False, False),
96 | ("3.cnx.", True, True, False),
97 | ("_example.#13", True, False, True),
98 | ("example_.fo-ul", True, False, True),
99 | ("somerandomexample.xn-n-fiqs8s", True, False, False),
100 | ],
101 | )
102 | def test_returns_failed_validation_invalid_top_level_domain(
103 | value: str, consider_tld: bool, rfc_1034: bool, rfc_2782: bool
104 | ):
105 | """Test returns failed validation invalid top level domain."""
106 | assert isinstance(
107 | domain(value, consider_tld=consider_tld, rfc_1034=rfc_1034, rfc_2782=rfc_2782),
108 | ValidationError,
109 | )
110 |
--------------------------------------------------------------------------------
/tests/test_email.py:
--------------------------------------------------------------------------------
1 | """Test eMail."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, email
8 |
9 |
10 | @pytest.mark.parametrize(
11 | ("value",),
12 | [
13 | ("email@here.com",),
14 | ("weirder-email@here.and.there.com",),
15 | ("email@127.local.home.arpa",),
16 | ("example@valid-----hyphens.com",),
17 | ("example@valid-with-hyphens.com",),
18 | ("test@domain.with.idn.tld.उदाहरण.परीक्षा",),
19 | ("email@localhost.in",),
20 | ("Łókaść@email.com",),
21 | ("łemłail@here.com",),
22 | ("email@localdomain.org",),
23 | ('"\\\011"@here.com',),
24 | ],
25 | )
26 | def test_returns_true_on_valid_email(value: str):
27 | """Test returns true on valid email."""
28 | assert email(value)
29 |
30 |
31 | @pytest.mark.parametrize(
32 | ("value",),
33 | [
34 | (None,),
35 | ("",),
36 | ("abc",),
37 | ("abc@",),
38 | ("abc@bar",),
39 | ("a @x.cz",),
40 | ("abc@.com",),
41 | ("something@@somewhere.com",),
42 | ("email@127.0.0.1",),
43 | ("example@invalid-.com",),
44 | ("example@-invalid.com",),
45 | ("example@inv-.alid-.com",),
46 | ("example@inv-.-alid.com",),
47 | ("john56789.john56789.john56789.john56789.john56789.john56789.john5@example.com",),
48 | ('"test@test"@example.com',),
49 | # Quoted-string format (CR not allowed)
50 | ('"\\\012"@here.com',),
51 | # Non-quoted space/semicolon not allowed
52 | ("stephen smith@example.com",),
53 | ("stephen;smith@example.com",),
54 | ],
55 | )
56 | def test_returns_failed_validation_on_invalid_email(value: str):
57 | """Test returns failed validation on invalid email."""
58 | assert isinstance(email(value), ValidationError)
59 |
--------------------------------------------------------------------------------
/tests/test_encoding.py:
--------------------------------------------------------------------------------
1 | """Test Encodings."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, base16, base32, base58, base64
8 |
9 | # ==> base16 <== #
10 |
11 |
12 | @pytest.mark.parametrize(
13 | "value",
14 | [
15 | "a3f4b2",
16 | "01ef",
17 | "abcdef0123456789",
18 | "1234567890abcdef",
19 | "1a2b3c",
20 | "abcdef",
21 | "000102030405060708090A0B0C0D0E0F",
22 | ],
23 | )
24 | def test_returns_true_on_valid_base16(value: str):
25 | """Test returns true on valid base16."""
26 | assert base16(value)
27 |
28 |
29 | @pytest.mark.parametrize(
30 | "value",
31 | ["12345g", "hello world", "1234567890abcdeg", "GHIJKL", "12345G", "!@#$%^", "1a2h3c", "a3f4Z1"],
32 | )
33 | def test_returns_failed_validation_on_invalid_base16(value: str):
34 | """Test returns failed validation on invalid base16."""
35 | assert isinstance(base16(value), ValidationError)
36 |
37 |
38 | # ==> base32 <== #
39 |
40 |
41 | @pytest.mark.parametrize(
42 | "value",
43 | [
44 | "JBSWY3DPEHPK3PXP",
45 | "MFRGGZDFMZTWQ2LK",
46 | "MZXW6YTBOI======",
47 | "MFZWIZLTOQ======",
48 | "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ",
49 | "MFRGGZDFMZTWQ2LKNNWG23Q=",
50 | ],
51 | )
52 | def test_returns_true_on_valid_base32(value: str):
53 | """Test returns true on valid base32."""
54 | assert base32(value)
55 |
56 |
57 | @pytest.mark.parametrize(
58 | "value",
59 | [
60 | "ThisIsNotBase32!",
61 | "12345!",
62 | "Any==invalid=base32=",
63 | "MzXW6yTBOI======",
64 | "JBSWY8DPEHPK9PXP",
65 | "MfZW3zLT9Q======",
66 | ],
67 | )
68 | def test_returns_failed_validation_on_invalid_base32(value: str):
69 | """Test returns failed validation on invalid base32."""
70 | assert isinstance(base32(value), ValidationError)
71 |
72 |
73 | # ==> base58 <== #
74 |
75 |
76 | @pytest.mark.parametrize(
77 | "value",
78 | [
79 | "cUSECaVvAiV3srWbFRvVPzm5YzcXJwPSwZfE7veYPHoXmR9h6YMQ",
80 | "18KToMF5ckjXBYt2HAj77qsG3GPeej3PZn",
81 | "n4FFXRNNEW1aA2WPscSuzHTCjzjs4TVE2Z",
82 | "38XzQ9dPGb1uqbZsjPtUajp7omy8aefjqj",
83 | ],
84 | )
85 | def test_returns_true_on_valid_base58(value: str):
86 | """Test returns true on valid base58."""
87 | assert base58(value)
88 |
89 |
90 | @pytest.mark.parametrize(
91 | "value",
92 | ["ThisIsAReallyLongStringThatIsDefinitelyNotBase58Encoded", "abcABC!@#", "InvalidBase58!"],
93 | )
94 | def test_returns_failed_validation_on_invalid_base58(value: str):
95 | """Test returns failed validation on invalid base58."""
96 | assert isinstance(base58(value), ValidationError)
97 |
98 |
99 | # ==> base64 <== #
100 |
101 |
102 | @pytest.mark.parametrize(
103 | "value",
104 | ["SGVsbG8gV29ybGQ=", "U29tZSBkYXRhIHN0cmluZw==", "YW55IGNhcm5hbCBwbGVhcw=="],
105 | )
106 | def test_returns_true_on_valid_base64(value: str):
107 | """Test returns true on valid base64."""
108 | assert base64(value)
109 |
110 |
111 | @pytest.mark.parametrize(
112 | "value",
113 | ["SGVsbG8gV29ybGQ", "U29tZSBkYXRhIHN0cmluZw", "YW55IGNhcm5hbCBwbGVhc"],
114 | )
115 | def test_returns_failed_validation_on_invalid_base64(value: str):
116 | """Test returns failed validation on invalid base64."""
117 | assert isinstance(base64(value), ValidationError)
118 |
--------------------------------------------------------------------------------
/tests/test_finance.py:
--------------------------------------------------------------------------------
1 | """Test Finance."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, cusip, isin, sedol
8 |
9 | # ==> CUSIP <== #
10 |
11 |
12 | @pytest.mark.parametrize("value", ["912796X38", "912796X20", "912796x20"])
13 | def test_returns_true_on_valid_cusip(value: str):
14 | """Test returns true on valid cusip."""
15 | assert cusip(value)
16 |
17 |
18 | @pytest.mark.parametrize("value", ["912796T67", "912796T68", "XCVF", "00^^^1234"])
19 | def test_returns_failed_validation_on_invalid_cusip(value: str):
20 | """Test returns failed validation on invalid cusip."""
21 | assert isinstance(cusip(value), ValidationError)
22 |
23 |
24 | # ==> ISIN <== #
25 |
26 |
27 | @pytest.mark.parametrize("value", ["US0004026250", "JP000K0VF054", "US0378331005"])
28 | def test_returns_true_on_valid_isin(value: str):
29 | """Test returns true on valid isin."""
30 | assert isin(value)
31 |
32 |
33 | @pytest.mark.parametrize("value", ["010378331005", "XCVF", "00^^^1234", "A000009"])
34 | def test_returns_failed_validation_on_invalid_isin(value: str):
35 | """Test returns failed validation on invalid isin."""
36 | assert isinstance(isin(value), ValidationError)
37 |
38 |
39 | # ==> SEDOL <== #
40 |
41 |
42 | @pytest.mark.parametrize("value", ["0263494", "0540528", "B000009"])
43 | def test_returns_true_on_valid_sedol(value: str):
44 | """Test returns true on valid sedol."""
45 | assert sedol(value)
46 |
47 |
48 | @pytest.mark.parametrize("value", ["0540526", "XCVF", "00^^^1234", "A000009"])
49 | def test_returns_failed_validation_on_invalid_sedol(value: str):
50 | """Test returns failed validation on invalid sedol."""
51 | assert isinstance(sedol(value), ValidationError)
52 |
--------------------------------------------------------------------------------
/tests/test_hashes.py:
--------------------------------------------------------------------------------
1 | """Test Hashes."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, base58, base64, md5, sha1, sha224, sha256, sha384, sha512
8 |
9 | # ==> base58 <== #
10 |
11 |
12 | @pytest.mark.parametrize(
13 | "value",
14 | [
15 | "cUSECaVvAiV3srWbFRvVPzm5YzcXJwPSwZfE7veYPHoXmR9h6YMQ",
16 | "18KToMF5ckjXBYt2HAj77qsG3GPeej3PZn",
17 | "n4FFXRNNEW1aA2WPscSuzHTCjzjs4TVE2Z",
18 | "38XzQ9dPGb1uqbZsjPtUajp7omy8aefjqj",
19 | ],
20 | )
21 | def test_returns_true_on_valid_base58(value: str):
22 | """Test returns true on valid base58."""
23 | assert base58(value)
24 |
25 |
26 | @pytest.mark.parametrize(
27 | "value",
28 | ["ThisIsAReallyLongStringThatIsDefinitelyNotBase58Encoded", "abcABC!@#", "InvalidBase58!"],
29 | )
30 | def test_returns_failed_validation_on_invalid_base58(value: str):
31 | """Test returns failed validation on invalid base58."""
32 | assert isinstance(base58(value), ValidationError)
33 |
34 |
35 | # ==> base64 <== #
36 |
37 |
38 | @pytest.mark.parametrize(
39 | "value",
40 | ["SGVsbG8gV29ybGQ=", "U29tZSBkYXRhIHN0cmluZw==", "YW55IGNhcm5hbCBwbGVhcw=="],
41 | )
42 | def test_returns_true_on_valid_base64(value: str):
43 | """Test returns true on valid base64."""
44 | assert base64(value)
45 |
46 |
47 | @pytest.mark.parametrize(
48 | "value",
49 | ["SGVsbG8gV29ybGQ", "U29tZSBkYXRhIHN0cmluZw", "YW55IGNhcm5hbCBwbGVhc"],
50 | )
51 | def test_returns_failed_validation_on_invalid_base64(value: str):
52 | """Test returns failed validation on invalid base64."""
53 | assert isinstance(base64(value), ValidationError)
54 |
55 |
56 | # ==> md5 <== #
57 |
58 |
59 | @pytest.mark.parametrize(
60 | "value", ["d41d8cd98f00b204e9800998ecf8427e", "D41D8CD98F00B204E9800998ECF8427E"]
61 | )
62 | def test_returns_true_on_valid_md5(value: str):
63 | """Test returns true on valid md5."""
64 | assert md5(value)
65 |
66 |
67 | @pytest.mark.parametrize(
68 | "value",
69 | [
70 | "z41d8cd98f00b204e9800998ecf8427e",
71 | "z8cd98f00b204e9800998ecf8427e",
72 | "z4aaaa1d8cd98f00b204e9800998ecf8427e",
73 | ],
74 | )
75 | def test_returns_failed_validation_on_invalid_md5(value: str):
76 | """Test returns failed validation on invalid md5."""
77 | assert isinstance(md5(value), ValidationError)
78 |
79 |
80 | # ==> sha1 <== #
81 |
82 |
83 | @pytest.mark.parametrize(
84 | "value",
85 | ["da39a3ee5e6b4b0d3255bfef95601890afd80709", "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"],
86 | )
87 | def test_returns_true_on_valid_sha1(value: str):
88 | """Test returns true on valid sha1."""
89 | assert sha1(value)
90 |
91 |
92 | @pytest.mark.parametrize(
93 | "value",
94 | [
95 | "za39a3ee5e6b4b0d3255bfef95601890afd80709",
96 | "da39e5e6b4b0d3255bfef95601890afd80709",
97 | "daaaa39a3ee5e6b4b0d3255bfef95601890afd80709",
98 | ],
99 | )
100 | def test_returns_failed_validation_on_invalid_sha1(value: str):
101 | """Test returns failed validation on invalid sha1."""
102 | assert isinstance(sha1(value), ValidationError)
103 |
104 |
105 | # ==> sha224 <== #
106 |
107 |
108 | @pytest.mark.parametrize(
109 | "value",
110 | [
111 | "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f",
112 | "D14A028C2A3A2BC9476102BB288234C415A2B01F828EA62AC5B3E42F",
113 | ],
114 | )
115 | def test_returns_true_on_valid_sha224(value: str):
116 | """Test returns true on valid sha224."""
117 | assert sha224(value)
118 |
119 |
120 | @pytest.mark.parametrize(
121 | "value",
122 | [
123 | "z14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f",
124 | "d028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f",
125 | "daaa14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f",
126 | ],
127 | )
128 | def test_returns_failed_validation_on_invalid_sha224(value: str):
129 | """Test returns failed validation on invalid sha224."""
130 | assert isinstance(sha224(value), ValidationError)
131 |
132 |
133 | # ==> sha256 <== #
134 |
135 |
136 | @pytest.mark.parametrize(
137 | "value",
138 | [
139 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
140 | "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
141 | ],
142 | )
143 | def test_returns_true_on_valid_sha256(value: str):
144 | """Test returns true on valid sha256."""
145 | assert sha256(value)
146 |
147 |
148 | @pytest.mark.parametrize(
149 | "value",
150 | [
151 | "z3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
152 | "ec44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
153 | "eaaaa3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
154 | ],
155 | )
156 | def test_returns_failed_validation_on_invalid_sha256(value: str):
157 | """Test returns failed validation on invalid sha256."""
158 | assert isinstance(sha256(value), ValidationError)
159 |
160 |
161 | # ==> sha384 <== #
162 |
163 |
164 | @pytest.mark.parametrize(
165 | "value",
166 | [
167 | "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7",
168 | "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1631A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7",
169 | "bfd76c0ebbd006fee583410547c1887b0292be76d582d96c242d2a792723e3fd6fd061f9d5cfd13b8f961358e6adba4a",
170 | "F21EF1F8DBF806106813C8504AF864D8D9BFDFA8D67FA9B7DFF1C5B61C2584394A05897C4F157CEEE0E8FBC29205BB8B",
171 | ],
172 | )
173 | def test_returns_true_on_valid_sha384(value: str):
174 | """Test returns true on valid sha384."""
175 | assert sha384(value)
176 |
177 |
178 | @pytest.mark.parametrize(
179 | "value",
180 | [
181 | "zb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7",
182 | "c753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7",
183 | "cb00aaaa753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7",
184 | ],
185 | )
186 | def test_returns_failed_validation_on_invalid_sha384(value: str):
187 | """Test returns failed validation on invalid sha384."""
188 | assert isinstance(sha384(value), ValidationError)
189 |
190 |
191 | # ==> sha512 <== #
192 |
193 |
194 | @pytest.mark.parametrize(
195 | "value",
196 | [
197 | (
198 | "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d"
199 | "13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
200 | ),
201 | (
202 | "CF83E1357EEFB8BDF1542850D66D8007D620E4050B5715DC83F4A921D36CE9CE47D0D"
203 | "13C5D85F2B0FF8318D2877EEC2F63B931BD47417A81A538327AF927DA3E"
204 | ),
205 | ],
206 | )
207 | def test_returns_true_on_valid_sha512(value: str):
208 | """Test returns true on valid sha512."""
209 | assert sha512(value)
210 |
211 |
212 | @pytest.mark.parametrize(
213 | "value",
214 | [
215 | (
216 | "zf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d"
217 | "13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
218 | ),
219 | (
220 | "cf8357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c"
221 | "5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
222 | ),
223 | (
224 | "cf8aaaa3e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce4"
225 | "7d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
226 | ),
227 | ],
228 | )
229 | def test_returns_failed_validation_on_invalid_sha512(value: str):
230 | """Test returns failed validation on invalid sha512."""
231 | assert isinstance(sha512(value), ValidationError)
232 |
--------------------------------------------------------------------------------
/tests/test_hostname.py:
--------------------------------------------------------------------------------
1 | """Test Hostname."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, hostname
8 |
9 |
10 | @pytest.mark.parametrize(
11 | ("value", "rfc_1034", "rfc_2782"),
12 | [
13 | # simple hostname w/ optional ports
14 | ("ubuntu-pc:443", False, False),
15 | ("this-pc", False, False),
16 | ("lab-01a-notebook:404", False, False),
17 | ("4-oh-4", False, False),
18 | # hostname w/ optional ports
19 | ("example.com:4444", False, False),
20 | ("kräuter.com.", True, False),
21 | ("xn----gtbspbbmkef.xn--p1ai:65535", False, False),
22 | ("_example.com", False, True),
23 | # ipv4 addr w/ optional ports
24 | ("123.123.123.123:9090", False, False),
25 | ("127.0.0.1:43512", False, False),
26 | ("123.5.77.88:31000", False, False),
27 | ("12.12.12.12:5353", False, False),
28 | # ipv6 addr w/ optional ports
29 | ("[::1]:22", False, False),
30 | ("[dead:beef:0:0:0:0000:42:1]:5731", False, False),
31 | ("[0:0:0:0:0:ffff:1.2.3.4]:80", False, False),
32 | ("[0:a:b:c:d:e:f::]:53", False, False),
33 | ],
34 | )
35 | def test_returns_true_on_valid_hostname(value: str, rfc_1034: bool, rfc_2782: bool):
36 | """Test returns true on valid hostname."""
37 | assert hostname(value, rfc_1034=rfc_1034, rfc_2782=rfc_2782)
38 |
39 |
40 | @pytest.mark.parametrize(
41 | ("value", "rfc_1034", "rfc_2782"),
42 | [
43 | # bad (simple hostname w/ optional ports)
44 | ("ubuntu-pc:443080", False, False),
45 | ("this-pc-is-sh*t", False, False),
46 | ("lab-01a-note._com_.com:404", False, False),
47 | ("4-oh-4:@.com", False, False),
48 | # bad (hostname w/ optional ports)
49 | ("example.com:-4444", False, False),
50 | ("xn----gtbspbbmkef.xn--p1ai:65538", False, False),
51 | ("_example.com:0", False, True),
52 | ("kräuter.com.:81_00", True, False),
53 | # bad (ipv4 addr w/ optional ports)
54 | ("123.123.123.123:99999", False, False),
55 | ("127.0.0.1:", False, False),
56 | ("123.5.-12.88:8080", False, False),
57 | ("12.12.12.12:$#", False, False),
58 | # bad (ipv6 addr w/ optional ports)
59 | ("[::1]:[22]", False, False),
60 | ("[dead:beef:0:-:0:-:42:1]:5731", False, False),
61 | ("[0:0:0:0:0:ffff:1.2.3.4]:-65538", False, False),
62 | ("[0:&:b:c:@:e:f:::9999", False, False),
63 | ],
64 | )
65 | def test_returns_failed_validation_on_invalid_hostname(value: str, rfc_1034: bool, rfc_2782: bool):
66 | """Test returns failed validation on invalid hostname."""
67 | assert isinstance(hostname(value, rfc_1034=rfc_1034, rfc_2782=rfc_2782), ValidationError)
68 |
--------------------------------------------------------------------------------
/tests/test_iban.py:
--------------------------------------------------------------------------------
1 | """Test IBAN."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, iban
8 |
9 |
10 | @pytest.mark.parametrize("value", ["GB82WEST12345698765432", "NO9386011117947"])
11 | def test_returns_true_on_valid_iban(value: str):
12 | """Test returns true on valid iban."""
13 | assert iban(value)
14 |
15 |
16 | @pytest.mark.parametrize("value", ["GB81WEST12345698765432", "NO9186011117947"])
17 | def test_returns_failed_validation_on_invalid_iban(value: str):
18 | """Test returns failed validation on invalid iban."""
19 | assert isinstance(iban(value), ValidationError)
20 |
--------------------------------------------------------------------------------
/tests/test_ip_address.py:
--------------------------------------------------------------------------------
1 | """Test IP Address."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, ipv4, ipv6
8 |
9 |
10 | @pytest.mark.parametrize(
11 | ("address",),
12 | [
13 | ("127.0.0.1",),
14 | ("123.5.77.88",),
15 | ("12.12.12.12",),
16 | ],
17 | )
18 | def test_returns_true_on_valid_ipv4_address(address: str):
19 | """Test returns true on valid ipv4 address."""
20 | assert ipv4(address)
21 | assert not ipv6(address)
22 |
23 |
24 | @pytest.mark.parametrize(
25 | ("address", "cidr", "strict", "host_bit"),
26 | [
27 | ("127.0.0.1/0", True, True, True),
28 | ("123.5.77.88", True, False, True),
29 | ("12.12.12.0/24", True, True, False),
30 | ],
31 | )
32 | def test_returns_true_on_valid_ipv4_cidr_address(
33 | address: str, cidr: bool, strict: bool, host_bit: bool
34 | ):
35 | """Test returns true on valid ipv4 CIDR address."""
36 | assert ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit)
37 | assert not ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit)
38 |
39 |
40 | @pytest.mark.parametrize(
41 | ("address",),
42 | [
43 | # leading zeroes error-out from Python 3.9.5
44 | # ("100.100.033.033",),
45 | ("900.200.100.75",),
46 | ("0127.0.0.1",),
47 | ("abc.0.0.1",),
48 | ],
49 | )
50 | def test_returns_failed_validation_on_invalid_ipv4_address(address: str):
51 | """Test returns failed validation on invalid ipv4 address."""
52 | assert isinstance(ipv4(address), ValidationError)
53 |
54 |
55 | @pytest.mark.parametrize(
56 | ("address", "cidr", "strict", "host_bit"),
57 | [
58 | ("1.1.1.1/1", False, True, True),
59 | ("1.1.1.1/33", True, False, True),
60 | ("1.1.1.1/24", True, True, False),
61 | ("1.1.1.1/-1", True, True, True),
62 | ],
63 | )
64 | def test_returns_failed_validation_on_invalid_ipv4_cidr_address(
65 | address: str, cidr: bool, strict: bool, host_bit: bool
66 | ):
67 | """Test returns failed validation on invalid ipv4 CIDR address."""
68 | assert isinstance(ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit), ValidationError)
69 |
70 |
71 | @pytest.mark.parametrize(
72 | ("address",),
73 | [
74 | ("::",),
75 | ("::1",),
76 | ("1::",),
77 | ("dead:beef:0:0:0:0000:42:1",),
78 | ("abcd:ef::42:1",),
79 | ("0:0:0:0:0:ffff:1.2.3.4",),
80 | ("::192.168.30.2",),
81 | ("0000:0000:0000:0000:0000::",),
82 | ("0:a:b:c:d:e:f::",),
83 | ],
84 | )
85 | def test_returns_true_on_valid_ipv6_address(address: str):
86 | """Test returns true on valid ipv6 address."""
87 | assert ipv6(address)
88 | assert not ipv4(address)
89 |
90 |
91 | @pytest.mark.parametrize(
92 | ("address", "cidr", "strict", "host_bit"),
93 | [
94 | ("::1/128", True, True, True),
95 | ("::1/0", True, True, True),
96 | ("dead:beef:0:0:0:0:42:1/8", True, True, True),
97 | ("abcd:ef::42:1/32", True, True, True),
98 | ("0:0:0:0:0:ffff:1.2.3.4/16", True, True, True),
99 | ("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64", True, True, True),
100 | ("::192.168.30.2/128", True, True, True),
101 | ],
102 | )
103 | def test_returns_true_on_valid_ipv6_cidr_address(
104 | address: str, cidr: bool, strict: bool, host_bit: bool
105 | ):
106 | """Test returns true on valid ipv6 CIDR address."""
107 | assert ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit)
108 | assert not ipv4(address, cidr=cidr, strict=strict, host_bit=host_bit)
109 |
110 |
111 | @pytest.mark.parametrize(
112 | ("address",),
113 | [
114 | ("abc.0.0.1",),
115 | ("abcd:1234::123::1",),
116 | ("1:2:3:4:5:6:7:8:9",),
117 | ("1:2:3:4:5:6:7:8::",),
118 | ("1:2:3:4:5:6:7::8:9",),
119 | ("abcd::1ffff",),
120 | ("1111:",),
121 | (":8888",),
122 | (":1.2.3.4",),
123 | ("18:05",),
124 | (":",),
125 | (":1:2:",),
126 | (":1:2::",),
127 | ("::1:2::",),
128 | ("8::1:2::9",),
129 | ("02001:0000:1234:0000:0000:C1C0:ABCD:0876",),
130 | ],
131 | )
132 | def test_returns_failed_validation_on_invalid_ipv6_address(address: str):
133 | """Test returns failed validation on invalid ipv6 address."""
134 | assert isinstance(ipv6(address), ValidationError)
135 |
136 |
137 | @pytest.mark.parametrize(
138 | ("address", "cidr", "strict", "host_bit"),
139 | [
140 | ("::1/128", False, True, True),
141 | ("::1/129", True, False, True),
142 | ("dead:beef:0:0:0:0:42:1/8", True, True, False),
143 | ("::1/-130", True, True, True),
144 | ],
145 | )
146 | def test_returns_failed_validation_on_invalid_ipv6_cidr_address(
147 | address: str, cidr: bool, strict: bool, host_bit: bool
148 | ):
149 | """Test returns failed validation on invalid ipv6 CIDR address."""
150 | assert isinstance(ipv6(address, cidr=cidr, strict=strict, host_bit=host_bit), ValidationError)
151 |
152 |
153 | @pytest.mark.parametrize(
154 | ("address", "private"),
155 | [
156 | ("10.1.1.1", True),
157 | ("192.168.1.1", True),
158 | ("169.254.1.1", True),
159 | ("127.0.0.1", True),
160 | ("0.0.0.0", True),
161 | ],
162 | )
163 | def test_returns_true_on_valid_private_ipv4_address(address: str, private: bool):
164 | """Test returns true on private ipv4 address."""
165 | assert ipv4(address, private=private)
166 |
167 |
168 | @pytest.mark.parametrize(
169 | ("address", "private"),
170 | [
171 | ("1.1.1.1", True),
172 | ("192.169.1.1", True),
173 | ("7.53.12.1", True),
174 | ],
175 | )
176 | def test_returns_failed_validation_on_invalid_private_ipv4_address(address: str, private: bool):
177 | """Test returns failed validation on invalid private ipv4 address."""
178 | assert isinstance(ipv4(address, private=private), ValidationError)
179 |
180 |
181 | @pytest.mark.parametrize(
182 | ("address", "private"),
183 | [
184 | ("1.1.1.1", False),
185 | ("192.169.1.1", False),
186 | ("7.53.12.1", False),
187 | ],
188 | )
189 | def test_returns_true_on_valid_public_ipv4_address(address: str, private: bool):
190 | """Test returns true on valid public ipv4 address."""
191 | assert ipv4(address, private=private)
192 |
193 |
194 | @pytest.mark.parametrize(
195 | ("address", "private"),
196 | [
197 | ("10.1.1.1", False),
198 | ("192.168.1.1", False),
199 | ("169.254.1.1", False),
200 | ("127.0.0.1", False),
201 | ("0.0.0.0", False),
202 | ],
203 | )
204 | def test_returns_failed_validation_on_invalid_public_ipv4_address(address: str, private: bool):
205 | """Test returns failed validation on private ipv4 address."""
206 | assert isinstance(ipv4(address, private=private), ValidationError)
207 |
--------------------------------------------------------------------------------
/tests/test_length.py:
--------------------------------------------------------------------------------
1 | """Test Length."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, length
8 |
9 |
10 | @pytest.mark.parametrize(
11 | ("value", "min_val", "max_val"),
12 | [("password", 2, None), ("password", None, None), ("password", 0, 10), ("password", 8, 8)],
13 | )
14 | def test_returns_true_on_valid_length(value: str, min_val: int, max_val: int):
15 | """Test returns true on valid length."""
16 | assert length(value, min_val=min_val, max_val=max_val)
17 |
18 |
19 | @pytest.mark.parametrize(
20 | ("value", "min_val", "max_val"),
21 | [("something", 14, 12), ("something", -10, -20), ("something", 0, -2), ("something", 13, 14)],
22 | )
23 | def test_returns_failed_validation_on_invalid_range(value: str, min_val: int, max_val: int):
24 | """Test returns failed validation on invalid range."""
25 | assert isinstance(length(value, min_val=min_val, max_val=max_val), ValidationError)
26 |
--------------------------------------------------------------------------------
/tests/test_mac_address.py:
--------------------------------------------------------------------------------
1 | """MAC Address."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, mac_address
8 |
9 |
10 | @pytest.mark.parametrize(
11 | ("address",),
12 | [
13 | ("01:23:45:67:ab:CD",),
14 | ("01-23-45-67-ab-CD",),
15 | ("01:2F:45:37:ab:CD",),
16 | ("A1-2F-4E-68-ab-CD",),
17 | ],
18 | )
19 | def test_returns_true_on_valid_mac_address(address: str):
20 | """Test returns true on valid mac address."""
21 | assert mac_address(address)
22 |
23 |
24 | @pytest.mark.parametrize(
25 | ("address",),
26 | [
27 | ("00-00:-00-00-00",),
28 | ("01:23:45:67:89:",),
29 | ("01:23-45:67-89:gh",),
30 | ("123:23:45:67:89:00",),
31 | ],
32 | )
33 | def test_returns_failed_validation_on_invalid_mac_address(address: str):
34 | """Test returns failed validation on invalid mac address."""
35 | assert isinstance(mac_address(address), ValidationError)
36 |
--------------------------------------------------------------------------------
/tests/test_slug.py:
--------------------------------------------------------------------------------
1 | """Test Slug."""
2 |
3 | # external
4 | import pytest
5 |
6 | # local
7 | from validators import ValidationError, slug
8 |
9 |
10 | @pytest.mark.parametrize(
11 | "value",
12 | [
13 | "123-asd-7sda",
14 | "123-k-123",
15 | "dac-12sa-459",
16 | "dac-12sa7-ad31as",
17 | ],
18 | )
19 | def test_returns_true_on_valid_slug(value: str):
20 | """Test returns true on valid slug."""
21 | assert slug(value)
22 |
23 |
24 | @pytest.mark.parametrize(
25 | "value",
26 | [
27 | "some.slug&",
28 | "1231321%",
29 | " 21312",
30 | "-47q-p--123",
31 | ],
32 | )
33 | def test_returns_failed_validation_on_invalid_slug(value: str):
34 | """Test returns failed validation on invalid slug."""
35 | assert isinstance(slug(value), ValidationError)
36 |
--------------------------------------------------------------------------------
/tests/test_uuid.py:
--------------------------------------------------------------------------------
1 | """Test UUIDs."""
2 |
3 | # standard
4 | from typing import Union
5 | from uuid import UUID, uuid4
6 |
7 | # external
8 | import pytest
9 |
10 | # local
11 | from validators import ValidationError, uuid
12 |
13 |
14 | @pytest.mark.parametrize(
15 | ("value",),
16 | [
17 | (uuid4(),),
18 | ("2bc1c94f-0deb-43e9-92a1-4775189ec9f8",),
19 | (uuid4(),),
20 | ("888256d7c49341f19fa33f29d3f820d7",),
21 | ],
22 | )
23 | def test_returns_true_on_valid_uuid(value: Union[str, UUID]):
24 | """Test returns true on valid uuid."""
25 | assert uuid(value)
26 |
27 |
28 | @pytest.mark.parametrize(
29 | ("value",),
30 | [
31 | ("2bc1c94f-deb-43e9-92a1-4775189ec9f8",),
32 | ("2bc1c94f-0deb-43e9-92a1-4775189ec9f",),
33 | ("gbc1c94f-0deb-43e9-92a1-4775189ec9f8",),
34 | ("2bc1c94f 0deb-43e9-92a1-4775189ec9f8",),
35 | ],
36 | )
37 | def test_returns_failed_validation_on_invalid_uuid(value: Union[str, UUID]):
38 | """Test returns failed validation on invalid uuid."""
39 | assert isinstance(uuid(value), ValidationError)
40 |
--------------------------------------------------------------------------------
/tests/test_validation_failure.py:
--------------------------------------------------------------------------------
1 | """Test validation Failure."""
2 |
3 | # local
4 | from validators import between
5 |
6 | failed_obj_repr = "ValidationError(func=between"
7 |
8 |
9 | class TestValidationError:
10 | """Test validation Failure."""
11 |
12 | def setup_method(self):
13 | """Setup Method."""
14 | self.is_in_between = between(3, min_val=4, max_val=5)
15 |
16 | def test_boolean_coerce(self):
17 | """Test Boolean."""
18 | assert not bool(self.is_in_between)
19 | assert not self.is_in_between
20 |
21 | def test_repr(self):
22 | """Test Repr."""
23 | assert failed_obj_repr in repr(self.is_in_between)
24 |
25 | def test_string(self):
26 | """Test Repr."""
27 | assert failed_obj_repr in str(self.is_in_between)
28 |
29 | def test_arguments_as_properties(self):
30 | """Test argument properties."""
31 | assert self.is_in_between.__dict__["value"] == 3
32 | assert self.is_in_between.__dict__["min_val"] == 4
33 | assert self.is_in_between.__dict__["max_val"] == 5
34 |
--------------------------------------------------------------------------------