or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-data.mdconfiguration.mdcore-parsing.mdcustom-parsers.mdfile-secrets.mdframework-integration.mdindex.mdspecialized-types.mdvalidation.md

validation.mddocs/

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

```