An easy-to-user dictionary validation tool, good for use-cases like POST API data validation.
Project description
required-dict
An easy-to-use Python library for validating dictionary data with chainable validation rules. Build for API data validation, but good for any form processing or configuration validation.
Installation
pip install required_dict
Quick Start
from required_dict import REQUIRED
validation = {
'user_name': REQUIRED(),
'user_email': REQUIRED().as_email().none_ok(),
'user_age': REQUIRED().greater_than(18),
'user_type': REQUIRED().constrain_to('buyer', 'seller').default('buyer')
}
user_data = {
'user_name': 'John Doe',
'user_email': 'john@example.com',
'user_age': 25
}
validated = REQUIRED.validate_data(validation, user_data)
print(validated['user_type']) # 'buyer' (from default)
Core Features
Basic Requirements
REQUIRED() # Field must exist and not be None
REQUIRED().none_ok() # Allow None values
REQUIRED().default('value') # Set default if missing
Type Validation
REQUIRED().as_type(str) # Exact type matching
REQUIRED().as_type(int, isinstance=True) # isinstance() checking
Format Validation
REQUIRED().as_email() # Email format
REQUIRED().as_uuid() # Any UUID version
REQUIRED().as_uuid(4) # Specific UUID version
REQUIRED().as_isotime() # ISO-8601 timestamp
REQUIRED().as_json() # Valid JSON
Numeric Validation
REQUIRED().as_posnum() # Positive numbers
REQUIRED().as_negnum() # Negative numbers
REQUIRED().greater_than(18) # > 18
REQUIRED().greater_than(18, or_equal_to=True) # >= 18
REQUIRED().less_than(65) # < 65
REQUIRED().less_than(65, or_equal_to=True) # <= 65
# Chain for ranges
REQUIRED().greater_than(18, True).less_than(65, True) # 18 <= value <= 65
Constraints & Whitelists
REQUIRED().constrain_to('buyer', 'seller') # Must be one of these
REQUIRED().whitelist('SYSTEM', 'ADMIN') # These values bypass validation
Field Aliases
REQUIRED().alias('id', 'user_id', 'party_id') # Accept multiple field names
Examples
User Registration
validation = {
'user_name': REQUIRED(),
'user_email': REQUIRED().as_type(str).as_email(),
'user_age': REQUIRED().greater_than(13),
'user_balance': REQUIRED().as_posnum().default(0),
'user_type': REQUIRED().constrain_to('buyer', 'seller'),
'user_detail': REQUIRED().as_json(),
'joined_time': REQUIRED().as_isotime(),
'notes': 'Default notes' # Non-REQUIRED field
}
user_data = {
'user_name': 'John Doe',
'user_email': 'john@example.com',
'user_age': 25,
'user_type': 'buyer',
'user_detail': '{"preferences": {"theme": "dark"}}',
'joined_time': '2025-01-01T12:00:00+00:00'
}
validated = REQUIRED.validate_data(validation, user_data)
Handling Missing Fields
# This fails - missing required field
try:
REQUIRED.validate_data(
validation={'name': REQUIRED()},
user_data={}
)
except ValueError as e:
print(e) # name = REQUIRED - Missing
# This fails - None not allowed by default
try:
REQUIRED.validate_data(
validation={'name': REQUIRED()},
user_data={'name': None}
)
except ValueError as e:
print(e) # name = REQUIRED - Present, but Empty
# This works - None explicitly allowed
validated = REQUIRED.validate_data(
validation={'name': REQUIRED().none_ok()},
user_data={'name': None}
)
Alias Handling
# Basic usage - all aliases populated
validated = REQUIRED.validate_data(
validation={'id': REQUIRED().alias('user_id', 'party_id')},
user_data={'user_id': 'abc123'}
)
# Result: id, user_id, party_id all equal 'abc123'
# Conflicting aliases generate warnings
import warnings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
validated = REQUIRED.validate_data(
validation={'id': REQUIRED().alias('user_id', 'party_id')},
user_data={'user_id': 'abc', 'party_id': 'xyz'} # Different values!
)
if w:
print(w[0].message) # Warning about conflicting values
# Convert warnings to errors
try:
REQUIRED.validate_data(
validation={'id': REQUIRED().alias('user_id', 'party_id')},
user_data={'user_id': 'abc', 'party_id': 'xyz'},
error_on_conflicting_aliases=True
)
except ValueError as e:
print(e) # Error about conflicting aliases
Numeric Ranges
validation = {
'child_age': REQUIRED().greater_than(0).less_than(13),
'teen_age': REQUIRED().greater_than(12).less_than(20), # 13-19
'adult_age': REQUIRED().greater_than(17), # 18+
}
# Valid data
REQUIRED.validate_data(validation, {'child_age': 8}) # Valid
REQUIRED.validate_data(validation, {'teen_age': 16}) # Valid
REQUIRED.validate_data(validation, {'adult_age': 25}) # Valid
Email Validation
validation = {
'email1': REQUIRED().as_email(),
'email2': REQUIRED().as_email(),
'email3': REQUIRED().as_email(),
}
# Valid emails
valid_emails = {
'email1': 'user@example.com',
'email2': 'test.email@company.co.uk',
'email3': 'user_name@sub-domain.com',
}
validated = REQUIRED.validate_data(validation, valid_emails)
# Invalid emails will raise ValueError
invalid_emails = {
'email1': 'user@domain', # No TLD
'email2': 'user.domain.com', # No @
'email3': '@domain.com', # No local part
}
UUID Validation
validation = {
'any_uuid': REQUIRED().as_uuid(), # Any version
'uuid_v4': REQUIRED().as_uuid(4), # UUIDv4 only
'uuid_v7': REQUIRED().as_uuid(7), # UUIDv7 only
'special_id': REQUIRED().as_uuid().whitelist('SYSTEM') # UUID or special value
}
valid_data = {
'any_uuid': '550e8400-e29b-41d4-a716-446655440000',
'uuid_v4': '550e8400-e29b-41d4-a716-446655440000',
'uuid_v7': '01234567-1234-7654-8765-123456789012',
'special_id': 'SYSTEM' # Bypasses UUID validation
}
Complex Validation
# API payload validation
validation = {
'request_id': REQUIRED().as_uuid(4),
'timestamp': REQUIRED().as_isotime(),
'payload': REQUIRED().as_json(),
'user_id': REQUIRED().alias('id', 'userid'),
'action': REQUIRED().constrain_to('create', 'update', 'delete'),
'priority': REQUIRED().greater_than(0).less_than(11).default(5),
'metadata': REQUIRED().none_ok().default(None)
}
api_data = {
'request_id': '550e8400-e29b-41d4-a716-446655440000',
'timestamp': '2025-01-01T12:00:00+00:00',
'payload': '{"action": "user_update"}',
'id': 'user_12345', # Populates all aliases
'action': 'update'
}
validated = REQUIRED.validate_data(validation, api_data)
Configuration Options
Key Normalization
# Default: keys lowercased automatically
data = {'USER_NAME': 'John', 'Id': '123'}
validation = {'user_name': REQUIRED(), 'id': REQUIRED()}
validated = REQUIRED.validate_data(validation, data) # Works
# Disable key lowercasing
validated = REQUIRED.validate_data(
validation={'USER_NAME': REQUIRED()},
user_data={'USER_NAME': 'John'},
keys_lowercased=False
)
Value Trimming
# Default: string values trimmed
user_data = {'name': ' John '}
validated = REQUIRED.validate_data({'name': REQUIRED()}, user_data)
print(validated['name']) # 'John'
# Disable trimming
validated = REQUIRED.validate_data(
validation={'name': REQUIRED()},
user_data={'name': ' John '},
values_trimmed=False
)
print(validated['name']) # ' John '
Error Handling
# Disable errors for missing fields - this effectively removes validation
validated = REQUIRED.validate_data(
validation={'required_field': REQUIRED()},
user_data={},
error_on_missing_required=False
)
# Control alias conflicts
REQUIRED.validate_data(
validation={'id': REQUIRED().alias('user_id')},
user_data={'user_id': 'abc', 'id': 'xyz'},
error_on_conflicting_aliases=True # Raises error instead of warning
)
Error Messages
try:
REQUIRED.validate_data(
validation={
'user_name': REQUIRED(),
'user_email': REQUIRED().as_email(),
'user_age': REQUIRED().greater_than(18)
},
user_data={
'user_email': 'susy@example,com',
'user_age': 16
}
)
except ValueError as e:
print(e)
# Output:
# One or more required fields were missing, left empty, or of the wrong type:
# user_name = REQUIRED - Missing
# user_email = REQUIRED - Present, but Not Valid Email Format (susy@example,com)
# user_age = REQUIRED - Present, but Not Greater Than 18 (16)
#
# Full signature (JSON keys):
# Required: user_name, user_email, user_age
# Optional:
Advanced Features
Method Chaining
# All methods return self for chaining
REQUIRED().as_type(str).as_email().none_ok().default('admin@system.com')
REQUIRED().as_type(int).greater_than(0).less_than(100).default(50)
Debugging Rules
rule = REQUIRED().as_email().as_type(str).none_ok()
print(rule)
# Output:
# REQUIRED: KEY MUST EXIST
# REQUIRED: type : [<class 'str'>] (isinstance: False)
# REQUIRED: noneok : True
# REQUIRED: email : True
Class vs Instance Usage
# Both work
REQUIRED.validate_data(validation, data) # Class method
REQUIRED().validate_data(validation, data) # Instance method
# Rules must be instances
validation = {
'field': REQUIRED() # ✓ Correct
# 'field': REQUIRED # ✗ Wrong
}
Example Use-Cases
API Validation
def validate_user_update(request_data):
validation = {
'user_id': REQUIRED().as_uuid().alias('id'),
'name': REQUIRED().as_type(str),
'email': REQUIRED().as_email(),
'age': REQUIRED().greater_than(12).less_than(120),
'role': REQUIRED().constrain_to('user', 'admin').default('user')
}
return REQUIRED.validate_data(validation, request_data)
Configuration Validation
def validate_config(config_dict):
validation = {
'database_url': REQUIRED().as_type(str),
'port': REQUIRED().greater_than(1024).less_than(65536).default(8080),
'debug': REQUIRED().as_type(bool).default(False),
'log_level': REQUIRED().constrain_to('DEBUG', 'INFO', 'WARNING', 'ERROR').default('INFO')
}
return REQUIRED.validate_data(validation, config_dict)
License
MIT License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
required_dict-0.21.tar.gz
(10.1 kB
view details)
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file required_dict-0.21.tar.gz.
File metadata
- Download URL: required_dict-0.21.tar.gz
- Upload date:
- Size: 10.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a852e319b79050e36492fc9d3982ae9a2342ee0cd60f0affc7772ed9eaa72b21
|
|
| MD5 |
fa5a2b5c81e0dd31c4645c0bc3e62b2a
|
|
| BLAKE2b-256 |
6fc5cd4ca7b3f3444358ed45b6bc3852ebaa99fbb8b4ce0006e132c11cc4f600
|
File details
Details for the file required_dict-0.21-py3-none-any.whl.
File metadata
- Download URL: required_dict-0.21-py3-none-any.whl
- Upload date:
- Size: 5.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f95bef4b1d8e7d51b32074cb972891931f10c70a7f7f429049165e45e9df1f12
|
|
| MD5 |
d918975de8605793f7ccd46acbe1b198
|
|
| BLAKE2b-256 |
a37f24460d6068575d8cba5af8e13e2ea9e3cf51d13695d7cf4fad55eb5e9940
|