0
# Validation
1
2
Comprehensive validation support using marshmallow validators to ensure environment variables meet specific criteria before being used in your application.
3
4
## Core Imports
5
6
```python
7
from environs import env, validate, ValidationError, EnvValidationError
8
```
9
10
## Capabilities
11
12
### Built-in Validators
13
14
Environs re-exports marshmallow's validation functions for common validation scenarios.
15
16
```python { .api }
17
validate.OneOf(choices, error=None): ... # Value must be one of the choices
18
validate.Range(min=None, max=None): ... # Numeric range validation
19
validate.Length(min=None, max=None): ... # String/list length validation
20
validate.Email(): ... # Email format validation
21
validate.URL(require_tld=True): ... # URL format validation
22
validate.Regexp(regex, flags=0): ... # Regular expression validation
23
validate.Equal(comparable): ... # Equality validation
24
validate.NoneOf(iterable): ... # Value must not be in iterable
25
validate.ContainsOnly(choices): ... # All items must be in choices
26
validate.Predicate(method, error=None): ... # Custom predicate function
27
```
28
29
### Single Validator Usage
30
31
Apply validation to any environment variable parsing method using the `validate` parameter.
32
33
```python
34
import os
35
from environs import env, validate, ValidationError
36
37
# Choice validation
38
os.environ["NODE_ENV"] = "development"
39
node_env = env.str(
40
"NODE_ENV",
41
validate=validate.OneOf(["development", "staging", "production"])
42
) # => "development"
43
44
# Range validation
45
os.environ["MAX_CONNECTIONS"] = "50"
46
max_connections = env.int(
47
"MAX_CONNECTIONS",
48
validate=validate.Range(min=1, max=100)
49
) # => 50
50
51
# String length validation
52
os.environ["API_KEY"] = "abc123def456"
53
api_key = env.str(
54
"API_KEY",
55
validate=validate.Length(min=8, max=64)
56
) # => "abc123def456"
57
58
# Email validation
59
os.environ["ADMIN_EMAIL"] = "admin@example.com"
60
admin_email = env.str(
61
"ADMIN_EMAIL",
62
validate=validate.Email()
63
) # => "admin@example.com"
64
65
# URL validation
66
os.environ["WEBHOOK_URL"] = "https://api.example.com/webhook"
67
webhook_url = env.str(
68
"WEBHOOK_URL",
69
validate=validate.URL()
70
) # => "https://api.example.com/webhook"
71
```
72
73
### Multiple Validators
74
75
Combine multiple validators to create comprehensive validation rules.
76
77
```python
78
import os
79
from environs import env, validate
80
81
# Multiple string validators
82
os.environ["USERNAME"] = "john_doe"
83
username = env.str(
84
"USERNAME",
85
validate=[
86
validate.Length(min=3, max=20),
87
validate.Regexp(r'^[a-zA-Z0-9_]+$', error="Username must contain only letters, numbers, and underscores")
88
]
89
) # => "john_doe"
90
91
# Multiple numeric validators
92
os.environ["PORT"] = "8080"
93
port = env.int(
94
"PORT",
95
validate=[
96
validate.Range(min=1024, max=65535, error="Port must be between 1024 and 65535"),
97
validate.NoneOf([3000, 5000], error="Port cannot be 3000 or 5000")
98
]
99
) # => 8080
100
101
# List validation with item validation
102
os.environ["ALLOWED_HOSTS"] = "localhost,api.example.com,admin.example.com"
103
allowed_hosts = env.list(
104
"ALLOWED_HOSTS",
105
validate=validate.Length(min=1, max=10) # Validate list length
106
)
107
# Individual items validated separately if needed
108
109
# Complex validation example
110
os.environ["DATABASE_NAME"] = "myapp_production"
111
db_name = env.str(
112
"DATABASE_NAME",
113
validate=[
114
validate.Length(min=5, max=63),
115
validate.Regexp(r'^[a-zA-Z][a-zA-Z0-9_]*$', error="Database name must start with letter"),
116
validate.NoneOf(["test", "temp", "debug"], error="Reserved database names not allowed")
117
]
118
) # => "myapp_production"
119
```
120
121
### Custom Validators
122
123
Create custom validation functions for specialized requirements.
124
125
```python
126
import os
127
from environs import env, ValidationError
128
129
def validate_positive_even(value):
130
"""Custom validator for positive even numbers."""
131
if value <= 0:
132
raise ValidationError("Value must be positive")
133
if value % 2 != 0:
134
raise ValidationError("Value must be even")
135
return value
136
137
def validate_version_format(value):
138
"""Custom validator for semantic version format."""
139
import re
140
if not re.match(r'^\d+\.\d+\.\d+$', value):
141
raise ValidationError("Version must be in format X.Y.Z")
142
return value
143
144
def validate_file_extension(extensions):
145
"""Factory function for file extension validation."""
146
def validator(value):
147
if not any(value.endswith(ext) for ext in extensions):
148
raise ValidationError(f"File must have one of these extensions: {', '.join(extensions)}")
149
return value
150
return validator
151
152
# Usage examples
153
os.environ["WORKER_COUNT"] = "4"
154
worker_count = env.int(
155
"WORKER_COUNT",
156
validate=validate_positive_even
157
) # => 4
158
159
os.environ["APP_VERSION"] = "1.2.3"
160
app_version = env.str(
161
"APP_VERSION",
162
validate=validate_version_format
163
) # => "1.2.3"
164
165
os.environ["CONFIG_FILE"] = "settings.json"
166
config_file = env.str(
167
"CONFIG_FILE",
168
validate=validate_file_extension(['.json', '.yaml', '.yml'])
169
) # => "settings.json"
170
171
# Combining custom and built-in validators
172
os.environ["BACKUP_COUNT"] = "6"
173
backup_count = env.int(
174
"BACKUP_COUNT",
175
validate=[
176
validate.Range(min=1, max=10),
177
validate_positive_even
178
]
179
) # => 6
180
```
181
182
### Deferred Validation
183
184
Use deferred validation to collect all validation errors before raising them.
185
186
```python
187
import os
188
from environs import Env, EnvValidationError, validate
189
190
# Set up invalid environment variables
191
os.environ["INVALID_PORT"] = "999" # Too low
192
os.environ["INVALID_EMAIL"] = "not-email" # Invalid format
193
os.environ["INVALID_ENV"] = "invalid" # Not in allowed choices
194
195
# Create env with deferred validation
196
env = Env(eager=False)
197
198
# Parse variables (errors collected, not raised)
199
port = env.int(
200
"INVALID_PORT",
201
validate=validate.Range(min=1024, max=65535)
202
)
203
204
email = env.str(
205
"INVALID_EMAIL",
206
validate=validate.Email()
207
)
208
209
environment = env.str(
210
"INVALID_ENV",
211
validate=validate.OneOf(["development", "staging", "production"])
212
)
213
214
# Validate all at once
215
try:
216
env.seal()
217
except EnvValidationError as e:
218
print("Validation errors found:")
219
for var_name, errors in e.error_messages.items():
220
print(f" {var_name}: {', '.join(errors)}")
221
# Handle errors appropriately
222
```
223
224
### Validation Error Handling
225
226
Handle validation errors gracefully with detailed error information.
227
228
```python
229
import os
230
from environs import env, validate, EnvValidationError
231
232
# Set invalid value
233
os.environ["INVALID_COUNT"] = "-5"
234
235
try:
236
count = env.int(
237
"INVALID_COUNT",
238
validate=[
239
validate.Range(min=0, max=100, error="Count must be between 0 and 100"),
240
lambda x: x if x % 2 == 0 else ValidationError("Count must be even")
241
]
242
)
243
except EnvValidationError as e:
244
print(f"Validation failed for INVALID_COUNT: {e}")
245
print(f"Error messages: {e.error_messages}")
246
247
# Provide fallback value
248
count = 10
249
print(f"Using fallback value: {count}")
250
251
# Graceful degradation
252
def get_validated_config():
253
"""Get configuration with validation and fallbacks."""
254
config = {}
255
256
# Required setting with validation
257
try:
258
config['port'] = env.int(
259
"PORT",
260
validate=validate.Range(min=1024, max=65535)
261
)
262
except EnvValidationError:
263
raise RuntimeError("Valid PORT is required")
264
265
# Optional setting with validation and fallback
266
try:
267
config['max_workers'] = env.int(
268
"MAX_WORKERS",
269
validate=validate.Range(min=1, max=32)
270
)
271
except EnvValidationError as e:
272
print(f"Invalid MAX_WORKERS: {e}, using default")
273
config['max_workers'] = 4
274
275
return config
276
277
# Usage
278
os.environ["PORT"] = "8080"
279
os.environ["MAX_WORKERS"] = "invalid"
280
281
try:
282
config = get_validated_config()
283
print(f"Configuration: {config}")
284
except RuntimeError as e:
285
print(f"Configuration error: {e}")
286
```
287
288
### Advanced Validation Patterns
289
290
Complex validation scenarios for real-world applications.
291
292
```python
293
import os
294
from environs import env, validate, ValidationError
295
296
def validate_database_url(url):
297
"""Validate database URL format and supported schemes."""
298
from urllib.parse import urlparse
299
300
parsed = urlparse(url)
301
supported_schemes = ['postgresql', 'mysql', 'sqlite', 'oracle']
302
303
if parsed.scheme not in supported_schemes:
304
raise ValidationError(f"Unsupported database scheme. Use: {', '.join(supported_schemes)}")
305
306
if parsed.scheme != 'sqlite' and not parsed.hostname:
307
raise ValidationError("Database URL must include hostname")
308
309
return url
310
311
def validate_json_config(value):
312
"""Validate that string contains valid JSON configuration."""
313
import json
314
try:
315
config = json.loads(value)
316
if not isinstance(config, dict):
317
raise ValidationError("JSON must be an object")
318
319
# Validate required keys
320
required_keys = ['name', 'version']
321
for key in required_keys:
322
if key not in config:
323
raise ValidationError(f"JSON must contain '{key}' field")
324
325
return value
326
except json.JSONDecodeError:
327
raise ValidationError("Value must be valid JSON")
328
329
# Environment-specific validation
330
def create_environment_validator():
331
"""Create validator based on current environment."""
332
current_env = os.environ.get('NODE_ENV', 'development')
333
334
if current_env == 'production':
335
# Strict validation for production
336
return [
337
validate.Length(min=32, max=128),
338
validate.Regexp(r'^[A-Za-z0-9+/=]+$', error="Must be base64 encoded")
339
]
340
else:
341
# Relaxed validation for development
342
return validate.Length(min=8)
343
344
# Usage examples
345
os.environ["DATABASE_URL"] = "postgresql://user:pass@localhost:5432/mydb"
346
db_url = env.str(
347
"DATABASE_URL",
348
validate=validate_database_url
349
)
350
351
os.environ["APP_CONFIG"] = '{"name": "MyApp", "version": "1.0.0", "debug": true}'
352
app_config = env.str(
353
"APP_CONFIG",
354
validate=validate_json_config
355
)
356
357
os.environ["NODE_ENV"] = "production"
358
os.environ["SECRET_KEY"] = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY3ODkw"
359
secret_key = env.str(
360
"SECRET_KEY",
361
validate=create_environment_validator()
362
)
363
```
364
365
## Types
366
367
```python { .api }
368
from typing import Any, Callable, List, Union
369
from marshmallow import ValidationError
370
371
ValidatorFunction = Callable[[Any], Any]
372
ValidatorList = List[ValidatorFunction]
373
Validator = Union[ValidatorFunction, ValidatorList]
374
```
375
376
## Error Types
377
378
```python { .api }
379
class ValidationError(Exception):
380
"""Raised by validators when validation fails."""
381
def __init__(self, message: str): ...
382
383
class EnvValidationError(Exception):
384
"""Raised when environment variable validation fails."""
385
def __init__(self, message: str, error_messages): ...
386
error_messages: dict | list # Detailed error information
387
```