Skip to content

Tutorial 5 — Input Validation

This tutorial demonstrates PyGo’s native validator package — struct-based validation with built-in and custom rules.

  • Define validation rules using struct tags
  • Use built-in validators (required, email, uuid, min, max)
  • Create custom validation rules
  • Integrate validation into HTMX forms
from validator import ValidateStruct
model UserSignup:
email: Email # Built-in email validation
name: String # Built-in string type
password: String # Validated via rules below
age: Integer? # Optional, validated if present
country: String # Required
# Add validation rules inline
def validate(self) -> List[String]:
errors = []
# Email is already Email type (built-in validation)
if not self.email:
errors.append("Email is required")
# Password rules
if len(self.password) < 8:
errors.append("Password must be at least 8 characters")
if len(self.password) > 128:
errors.append("Password too long")
# Age validation (optional field)
if self.age is not None:
if self.age < 13:
errors.append("Must be at least 13 years old")
if self.age > 120:
errors.append("Invalid age")
# Country must be 2-letter code
if len(self.country) != 2:
errors.append("Country must be 2-letter country code")
return errors
handler signup:
signup(user_data: Dict) -> String:
# Parse into model
user = UserSignup(
email=user_data["email"],
name=user_data["name"],
password=user_data["password"],
age=int(user_data.get("age", 0)) or None,
country=user_data["country"],
)
# Validate
errors = user.validate()
if errors:
# HTMX swaps this partial into the form
return """
<div class="alert alert-error">
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
"""
# Success — create user
user.save()
return redirect("/welcome")
<form
hx-post="/signup"
hx-swap="outerHTML"
hx-on::afterRequest="if(htmx.channeled) htmx.trigger(this, 'reset')"
>
<div>
<label>Email</label>
<input type="email" name="email" required />
</div>
<div>
<label>Name</label>
<input type="text" name="name" required />
</div>
<div>
<label>Password</label>
<input type="password" name="password" required minlength="8" />
</div>
<div>
<label>Age (optional)</label>
<input type="number" name="age" min="13" max="120" />
</div>
<div>
<label>Country (2-letter code)</label>
<input type="text" name="country" maxlength="2" required />
</div>
<button type="submit">Sign Up</button>
</form>
<!-- Errors are injected here by HTMX response -->
<div id="form-errors"></div>
Type Validation Description
Email format RFC 5322 email validation
UUID format UUID v4 format
String length min/max bounds
Integer range min/max bounds
Boolean type true/false
DateTime? nullable Optional datetime
from validator import AddRule
# Register a custom rule
AddRule("strong_password", lambda p: len(p) >= 8 and any(c.isupper() for c in p) and any(c.isdigit() for c in p))
model UserSignup:
password: String
# validator checks password against "strong_password" rule automatically
# Test validation in Python context
def test_user_validation():
user = UserSignup(
email="invalid-email",
password="weak",
country="USA",
)
errors = user.validate()
assert len(errors) == 3 # invalid email, weak password, invalid country