valid8r.core.validators

Core validators for validating values against specific criteria.

This module provides a collection of validator functions for common validation scenarios. All validators follow the same pattern - they take a value and return a Maybe object that either contains the validated value or an error message.

Attributes

T

U

N

Classes

SupportsComparison

Base class for protocol classes.

Validator

A wrapper class for validator functions that supports operator overloading.

Functions

minimum(min_value[, error_message])

Create a validator that ensures a value is at least the minimum.

maximum(max_value[, error_message])

Create a validator that ensures a value is at most the maximum.

between(min_value, max_value[, error_message])

Create a validator that ensures a value is between minimum and maximum (inclusive).

predicate(pred, error_message)

Create a validator using a custom predicate function.

length(min_length, max_length[, error_message])

Create a validator that ensures a string's length is within bounds.

matches_regex(pattern[, error_message])

Create a validator that ensures a string matches a regular expression pattern.

in_set(allowed_values[, error_message])

Create a validator that ensures a value is in a set of allowed values.

non_empty_string([error_message])

Create a validator that ensures a string is not empty.

unique_items([error_message])

Create a validator that ensures all items in a list are unique.

subset_of(allowed_set[, error_message])

Create a validator that ensures a set is a subset of allowed values.

superset_of(required_set[, error_message])

Create a validator that ensures a set is a superset of required values.

is_sorted(*[, reverse, error_message])

Create a validator that ensures a list is sorted.

exists()

Create a validator that ensures a path exists on the filesystem.

is_file()

Create a validator that ensures a path is a regular file.

is_dir()

Create a validator that ensures a path is a directory.

is_readable()

Create a validator that ensures a path has read permissions.

is_writable()

Create a validator that ensures a path has write permissions.

is_executable()

Create a validator that ensures a path has execute permissions.

max_size(max_bytes)

Create a validator that ensures a file does not exceed a maximum size.

min_size(min_bytes)

Create a validator that ensures a file meets a minimum size requirement.

has_extension(*extensions)

Create a validator that ensures a file has one of the allowed extensions.

Module Contents

class valid8r.core.validators.SupportsComparison[source]

Bases: Protocol

Base class for protocol classes.

Protocol classes are defined as:

class Proto(Protocol):
    def meth(self) -> int:
        ...

Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).

For example:

class C:
    def meth(self) -> int:
        return 0

def func(x: Proto) -> int:
    return x.meth()

func(C())  # Passes static type check

See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as:

class GenProto[T](Protocol):
    def meth(self) -> T:
        ...
__le__(other, /)[source]
Parameters:

other (object)

Return type:

bool

__lt__(other, /)[source]
Parameters:

other (object)

Return type:

bool

__ge__(other, /)[source]
Parameters:

other (object)

Return type:

bool

__gt__(other, /)[source]
Parameters:

other (object)

Return type:

bool

__eq__(other, /)[source]
Parameters:

other (object)

Return type:

bool

__ne__(other, /)[source]
Parameters:

other (object)

Return type:

bool

__hash__(/)[source]
Return type:

int

valid8r.core.validators.T[source]
valid8r.core.validators.U[source]
valid8r.core.validators.N[source]
class valid8r.core.validators.Validator(func)[source]

Bases: Generic[T]

A wrapper class for validator functions that supports operator overloading.

Parameters:

func (collections.abc.Callable[[T], valid8r.core.maybe.Maybe[T]])

func[source]
__call__(value)[source]

Apply the validator to a value.

Parameters:

value (T) – The value to validate

Returns:

A Maybe containing either the validated value or an error

Return type:

valid8r.core.maybe.Maybe[T]

__and__(other)[source]

Combine with another validator using logical AND.

Parameters:

other (Validator[T]) – Another validator to combine with

Returns:

A new validator that passes only if both validators pass

Return type:

Validator[T]

__or__(other)[source]

Combine with another validator using logical OR.

Parameters:

other (Validator[T]) – Another validator to combine with

Returns:

A new validator that passes if either validator passes

Return type:

Validator[T]

__invert__()[source]

Negate this validator.

Returns:

A new validator that passes if this validator fails

Return type:

Validator[T]

valid8r.core.validators.minimum(min_value, error_message=None)[source]

Create a validator that ensures a value is at least the minimum.

Parameters:
  • min_value (N) – The minimum allowed value (inclusive)

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that accepts values >= min_value

Return type:

Validator[N]

Examples

>>> from valid8r.core.validators import minimum
>>> validator = minimum(0)
>>> validator(5)
Success(5)
>>> validator(0)
Success(0)
>>> validator(-1).is_failure()
True
>>> # With custom error message
>>> validator = minimum(18, error_message="Must be an adult")
>>> validator(17).error_or("")
'Must be an adult'
valid8r.core.validators.maximum(max_value, error_message=None)[source]

Create a validator that ensures a value is at most the maximum.

Parameters:
  • max_value (N) – The maximum allowed value (inclusive)

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that accepts values <= max_value

Return type:

Validator[N]

Examples

>>> from valid8r.core.validators import maximum
>>> validator = maximum(100)
>>> validator(50)
Success(50)
>>> validator(100)
Success(100)
>>> validator(101).is_failure()
True
>>> # With custom error message
>>> validator = maximum(120, error_message="Age too high")
>>> validator(150).error_or("")
'Age too high'
valid8r.core.validators.between(min_value, max_value, error_message=None)[source]

Create a validator that ensures a value is between minimum and maximum (inclusive).

Parameters:
  • min_value (N) – The minimum allowed value (inclusive)

  • max_value (N) – The maximum allowed value (inclusive)

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that accepts values where min_value <= value <= max_value

Return type:

Validator[N]

Examples

>>> from valid8r.core.validators import between
>>> validator = between(0, 100)
>>> validator(50)
Success(50)
>>> validator(0)
Success(0)
>>> validator(100)
Success(100)
>>> validator(-1).is_failure()
True
>>> validator(101).is_failure()
True
>>> # With custom error message
>>> validator = between(1, 10, error_message="Rating must be 1-10")
>>> validator(11).error_or("")
'Rating must be 1-10'
valid8r.core.validators.predicate(pred, error_message)[source]

Create a validator using a custom predicate function.

Allows creating custom validators for any validation logic by providing a predicate function that returns True for valid values.

Parameters:
  • pred (collections.abc.Callable[[T], bool]) – A function that takes a value and returns True if valid, False otherwise

  • error_message (str) – Error message to return when validation fails

Returns:

A validator function that applies the predicate

Return type:

Validator[T]

Examples

>>> from valid8r.core.validators import predicate
>>> # Validate even numbers
>>> is_even = predicate(lambda x: x % 2 == 0, "Must be even")
>>> is_even(4)
Success(4)
>>> is_even(3).is_failure()
True
>>> # Validate string patterns
>>> starts_with_a = predicate(lambda s: s.startswith('a'), "Must start with 'a'")
>>> starts_with_a("apple")
Success('apple')
>>> starts_with_a("banana").error_or("")
"Must start with 'a'"
valid8r.core.validators.length(min_length, max_length, error_message=None)[source]

Create a validator that ensures a string’s length is within bounds.

Parameters:
  • min_length (int) – Minimum length of the string (inclusive)

  • max_length (int) – Maximum length of the string (inclusive)

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that checks string length

Return type:

Validator[str]

Examples

>>> from valid8r.core.validators import length
>>> validator = length(3, 10)
>>> validator("hello")
Success('hello')
>>> validator("abc")
Success('abc')
>>> validator("abcdefghij")
Success('abcdefghij')
>>> validator("ab").is_failure()
True
>>> validator("abcdefghijk").is_failure()
True
>>> # With custom error message
>>> validator = length(8, 20, error_message="Password must be 8-20 characters")
>>> validator("short").error_or("")
'Password must be 8-20 characters'
valid8r.core.validators.matches_regex(pattern, error_message=None)[source]

Create a validator that ensures a string matches a regular expression pattern.

Parameters:
  • pattern (str | re.Pattern[str]) – Regular expression pattern (string or compiled Pattern object)

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that checks pattern matching

Return type:

Validator[str]

Examples

>>> from valid8r.core.validators import matches_regex
>>> import re
>>> # String pattern
>>> validator = matches_regex(r'^\d{3}-\d{2}-\d{4}$')
>>> validator('123-45-6789')
Success('123-45-6789')
>>> validator('invalid').is_failure()
True
>>> # Compiled regex pattern
>>> pattern = re.compile(r'^[A-Z][a-z]+$')
>>> validator = matches_regex(pattern)
>>> validator('Hello')
Success('Hello')
>>> validator('hello').is_failure()
True
>>> # With custom error message
>>> validator = matches_regex(r'^\d{5}$', error_message='Must be a 5-digit ZIP code')
>>> validator('1234').error_or('')
'Must be a 5-digit ZIP code'
valid8r.core.validators.in_set(allowed_values, error_message=None)[source]

Create a validator that ensures a value is in a set of allowed values.

Parameters:
  • allowed_values (set[T]) – Set of allowed values

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that checks membership

Return type:

Validator[T]

Examples

>>> from valid8r.core.validators import in_set
>>> # String values
>>> validator = in_set({'red', 'green', 'blue'})
>>> validator('red')
Success('red')
>>> validator('yellow').is_failure()
True
>>> # Numeric values
>>> validator = in_set({1, 2, 3, 4, 5})
>>> validator(3)
Success(3)
>>> validator(10).is_failure()
True
>>> # With custom error message
>>> validator = in_set({'small', 'medium', 'large'}, error_message='Size must be S, M, or L')
>>> validator('extra-large').error_or('')
'Size must be S, M, or L'
valid8r.core.validators.non_empty_string(error_message=None)[source]

Create a validator that ensures a string is not empty.

Validates that a string contains at least one non-whitespace character. Both empty strings and whitespace-only strings are rejected.

Parameters:

error_message (str | None) – Optional custom error message

Returns:

A validator function that checks for non-empty strings

Return type:

Validator[str]

Examples

>>> from valid8r.core.validators import non_empty_string
>>> validator = non_empty_string()
>>> validator('hello')
Success('hello')
>>> validator('  hello  ')
Success('  hello  ')
>>> validator('').is_failure()
True
>>> validator('   ').is_failure()
True
>>> # With custom error message
>>> validator = non_empty_string(error_message='Name is required')
>>> validator('').error_or('')
'Name is required'
valid8r.core.validators.unique_items(error_message=None)[source]

Create a validator that ensures all items in a list are unique.

Validates that a list contains no duplicate elements by comparing the list length to the set length.

Parameters:

error_message (str | None) – Optional custom error message

Returns:

A validator function that checks for unique items

Return type:

Validator[list[T]]

Examples

>>> from valid8r.core.validators import unique_items
>>> validator = unique_items()
>>> validator([1, 2, 3, 4, 5])
Success([1, 2, 3, 4, 5])
>>> validator([1, 2, 2, 3]).is_failure()
True
>>> # Works with strings
>>> validator(['a', 'b', 'c'])
Success(['a', 'b', 'c'])
>>> validator(['a', 'b', 'a']).is_failure()
True
>>> # With custom error message
>>> validator = unique_items(error_message='Duplicate items found')
>>> validator([1, 1, 2]).error_or('')
'Duplicate items found'
valid8r.core.validators.subset_of(allowed_set, error_message=None)[source]

Create a validator that ensures a set is a subset of allowed values.

Validates that all elements in the input set are contained within the allowed set. An empty set is always a valid subset.

Parameters:
  • allowed_set (set[T]) – The set of allowed values

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that checks subset relationship

Return type:

Validator[set[T]]

Examples

>>> from valid8r.core.validators import subset_of
>>> validator = subset_of({1, 2, 3, 4, 5})
>>> validator({1, 2, 3})
Success({1, 2, 3})
>>> validator({1, 2, 3, 4, 5, 6}).is_failure()
True
>>> # Empty set is valid subset
>>> validator(set())
Success(set())
>>> # With custom error message
>>> validator = subset_of({'a', 'b', 'c'}, error_message='Invalid characters')
>>> validator({'a', 'd'}).error_or('')
'Invalid characters'
valid8r.core.validators.superset_of(required_set, error_message=None)[source]

Create a validator that ensures a set is a superset of required values.

Validates that the input set contains all elements from the required set. The input set may contain additional elements beyond those required.

Parameters:
  • required_set (set[T]) – The set of required values

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that checks superset relationship

Return type:

Validator[set[T]]

Examples

>>> from valid8r.core.validators import superset_of
>>> validator = superset_of({1, 2, 3})
>>> validator({1, 2, 3, 4, 5})
Success({1, 2, 3, 4, 5})
>>> validator({1, 2}).is_failure()
True
>>> # Exact match is valid
>>> validator({1, 2, 3})
Success({1, 2, 3})
>>> # With custom error message
>>> validator = superset_of({'read', 'write'}, error_message='Missing required permissions')
>>> validator({'read'}).error_or('')
'Missing required permissions'
valid8r.core.validators.is_sorted(*, reverse=False, error_message=None)[source]

Create a validator that ensures a list is sorted.

Validates that a list is sorted in either ascending or descending order. Uses keyword-only parameters to avoid boolean trap anti-pattern.

Parameters:
  • reverse (bool) – If True, checks for descending order; otherwise ascending (default)

  • error_message (str | None) – Optional custom error message

Returns:

A validator function that checks if list is sorted

Return type:

Validator[list[N]]

Examples

>>> from valid8r.core.validators import is_sorted
>>> # Ascending order (default)
>>> validator = is_sorted()
>>> validator([1, 2, 3, 4, 5])
Success([1, 2, 3, 4, 5])
>>> validator([3, 1, 4, 2]).is_failure()
True
>>> # Descending order
>>> validator = is_sorted(reverse=True)
>>> validator([5, 4, 3, 2, 1])
Success([5, 4, 3, 2, 1])
>>> validator([1, 2, 3]).is_failure()
True
>>> # Works with strings
>>> validator = is_sorted()
>>> validator(['a', 'b', 'c'])
Success(['a', 'b', 'c'])
>>> # With custom error message
>>> validator = is_sorted(error_message='List must be in order')
>>> validator([3, 1, 2]).error_or('')
'List must be in order'
valid8r.core.validators.exists()[source]

Create a validator that ensures a path exists on the filesystem.

Validates that a Path object points to an existing file or directory. Follows symbolic links by default (uses Path.exists()).

Returns:

A validator function that checks path existence

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import exists
>>> # Existing path (doctest creates temp file)
>>> import tempfile
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     exists()(path).is_success()
True
>>> # Non-existent path
>>> path = Path('/nonexistent/file.txt')
>>> exists()(path).is_failure()
True
>>> exists()(path).error_or('')
'Path does not exist: /nonexistent/file.txt'
valid8r.core.validators.is_file()[source]

Create a validator that ensures a path is a regular file.

Validates that a Path object points to an existing regular file (not a directory, symlink, socket, etc.). Note that this also checks that the path exists. For better error messages, chain with exists() first.

Returns:

A validator function that checks path is a file

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import is_file
>>> # Regular file
>>> import tempfile
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     is_file()(path).is_success()
True
>>> # Directory
>>> import os
>>> path = Path(os.getcwd())
>>> result = is_file()(path)
>>> result.is_failure()
True
>>> 'not a file' in result.error_or('').lower()
True
valid8r.core.validators.is_dir()[source]

Create a validator that ensures a path is a directory.

Validates that a Path object points to an existing directory. Note that this also checks that the path exists. For better error messages, chain with exists() first.

Returns:

A validator function that checks path is a directory

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import is_dir
>>> # Directory
>>> import os
>>> path = Path(os.getcwd())
>>> is_dir()(path).is_success()
True
>>> # Regular file
>>> import tempfile
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     result = is_dir()(path)
...     result.is_failure()
True
>>> # Non-existent path
>>> path = Path('/nonexistent/dir')
>>> result = is_dir()(path)
>>> result.is_failure()
True
>>> 'not a directory' in result.error_or('').lower()
True
valid8r.core.validators.is_readable()[source]

Create a validator that ensures a path has read permissions.

Validates that a Path object has read permissions using os.access(). Works with files, directories, and symbolic links. For symlinks, checks the target’s permissions (follows the link).

Returns:

A validator function that checks read permissions

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import is_readable
>>> # Readable file
>>> import tempfile
>>> import os
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     os.chmod(tmp.name, 0o444)  # r--r--r--
...     is_readable()(path).is_success()
True
>>> # Non-existent path
>>> path = Path('/nonexistent/file.txt')
>>> is_readable()(path).is_failure()
True
>>> 'not readable' in is_readable()(path).error_or('').lower()
True
valid8r.core.validators.is_writable()[source]

Create a validator that ensures a path has write permissions.

Validates that a Path object has write permissions using os.access(). Works with files, directories, and symbolic links. For symlinks, checks the target’s permissions (follows the link).

Returns:

A validator function that checks write permissions

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import is_writable
>>> # Writable file
>>> import tempfile
>>> import os
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     os.chmod(tmp.name, 0o644)  # rw-r--r--
...     is_writable()(path).is_success()
True
>>> # Non-existent path
>>> path = Path('/nonexistent/file.txt')
>>> is_writable()(path).is_failure()
True
>>> 'not writable' in is_writable()(path).error_or('').lower()
True
valid8r.core.validators.is_executable()[source]

Create a validator that ensures a path has execute permissions.

Validates that a Path object has execute permissions using os.access(). Works with files, directories, and symbolic links. For symlinks, checks the target’s permissions (follows the link).

Returns:

A validator function that checks execute permissions

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import is_executable
>>> # Executable file
>>> import tempfile
>>> import os
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     os.chmod(tmp.name, 0o755)  # rwxr-xr-x
...     is_executable()(path).is_success()
True
>>> # Non-existent path
>>> path = Path('/nonexistent/file.sh')
>>> is_executable()(path).is_failure()
True
>>> 'not executable' in is_executable()(path).error_or('').lower()
True
valid8r.core.validators.max_size(max_bytes)[source]

Create a validator that ensures a file does not exceed a maximum size.

Validates that a file’s size in bytes is at most the specified maximum. This validator checks that the path is a regular file before checking size.

Parameters:

max_bytes (int) – Maximum allowed file size in bytes (inclusive)

Returns:

A validator function that checks file size

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import max_size
>>> # File under size limit
>>> import tempfile
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     path.write_bytes(b'x' * 1024)
...     max_size(2048)(path).is_success()
1024
True
>>> # File over size limit
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     path.write_bytes(b'x' * 5120)
...     result = max_size(1024)(path)
...     result.is_failure()
5120
True
>>> # Error includes actual size
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     path.write_bytes(b'x' * 5120)
...     result = max_size(1024)(path)
...     '5120' in result.error_or('')
5120
True
valid8r.core.validators.min_size(min_bytes)[source]

Create a validator that ensures a file meets a minimum size requirement.

Validates that a file’s size in bytes is at least the specified minimum. This validator checks that the path is a regular file before checking size.

Parameters:

min_bytes (int) – Minimum required file size in bytes (inclusive)

Returns:

A validator function that checks file size

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import min_size
>>> # File above size limit
>>> import tempfile
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     path.write_bytes(b'x' * 2048)
...     min_size(1024)(path).is_success()
2048
True
>>> # File below size limit
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     path.write_bytes(b'x' * 512)
...     result = min_size(1024)(path)
...     result.is_failure()
512
True
>>> # Error includes minimum size
>>> with tempfile.NamedTemporaryFile() as tmp:
...     path = Path(tmp.name)
...     path.write_bytes(b'x' * 512)
...     result = min_size(1024)(path)
...     '1024' in result.error_or('')
512
True
valid8r.core.validators.has_extension(*extensions)[source]

Create a validator that ensures a file has one of the allowed extensions.

Validates that a file’s extension matches one of the specified extensions. Extension matching is case-insensitive. Extensions should include the dot (e.g., ‘.txt’).

Parameters:

*extensions (str | list[str] | tuple[str, Ellipsis]) – Variable number of allowed file extensions (e.g., ‘.pdf’, ‘.txt’) or a single list/tuple of extensions (e.g., [‘.pdf’, ‘.txt’])

Returns:

A validator function that checks file extension

Return type:

Validator[Path]

Examples

>>> from pathlib import Path
>>> from valid8r.core.validators import has_extension
>>> # Single extension
>>> import tempfile
>>> import os
>>> with tempfile.TemporaryDirectory() as tmpdir:
...     path = Path(tmpdir) / 'document.pdf'
...     path.write_text('content')
...     has_extension('.pdf')(path).is_success()
7
True
>>> # Multiple extensions (variadic)
>>> with tempfile.TemporaryDirectory() as tmpdir:
...     path = Path(tmpdir) / 'document.docx'
...     path.write_text('content')
...     has_extension('.pdf', '.doc', '.docx')(path).is_success()
7
True
>>> # Multiple extensions (list)
>>> with tempfile.TemporaryDirectory() as tmpdir:
...     path = Path(tmpdir) / 'document.docx'
...     path.write_text('content')
...     has_extension(['.pdf', '.doc', '.docx'])(path).is_success()
7
True
>>> # Case-insensitive
>>> with tempfile.TemporaryDirectory() as tmpdir:
...     path = Path(tmpdir) / 'DOCUMENT.PDF'
...     path.write_text('content')
...     has_extension('.pdf')(path).is_success()
7
True
>>> # Wrong extension
>>> with tempfile.TemporaryDirectory() as tmpdir:
...     path = Path(tmpdir) / 'image.png'
...     path.write_text('content')
...     result = has_extension('.pdf', '.docx')(path)
...     result.is_failure()
7
True