From 675229599025cce3823b7c7e24483939cc22e792 Mon Sep 17 00:00:00 2001 From: Sean N Date: Fri, 17 Apr 2026 14:25:23 +0200 Subject: [PATCH] Cleaning up and adding examples --- README.md | 21 +++-- examples/advanced_validation.py | 2 +- examples/basic_usage.py | 2 +- examples/date_validation.py | 118 ++++++++++++++++++++++++++++ examples/formats.py | 132 ++++++++++++++++++++++++++++++++ nitro_validator/__init__.py | 2 +- pyproject.toml | 13 ++-- 7 files changed, 273 insertions(+), 17 deletions(-) create mode 100644 examples/date_validation.py create mode 100644 examples/formats.py diff --git a/README.md b/README.md index 20d6530..d78bde4 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,16 @@ Python `3.7` or higher is required. pip install nitro-validator ``` -## AI Assistant Integration +### AI Assistant Integration -Add Nitro CLI knowledge to your AI coding assistant: +Add Nitro Validator knowledge to your AI coding assistant: ```bash npx skills add nitrosh/nitro-validate ``` +This enables AI assistants like Claude Code to understand Nitro Validator and generate correct validation code. + ## Features - **Simple API** - Easy to learn with minimal boilerplate @@ -349,6 +351,8 @@ The `examples/` directory contains working examples: python examples/basic_usage.py python examples/custom_rules.py python examples/advanced_validation.py +python examples/date_validation.py +python examples/formats.py ``` ## Development @@ -356,8 +360,8 @@ python examples/advanced_validation.py ### Setup ```bash -git clone https://github.com/nitro/nitro-validator.git -cd nitro-validator +git clone https://github.com/nitrosh/nitro-validate.git +cd nitro-validate pip install -e ".[dev]" ``` @@ -395,10 +399,11 @@ Nitro Validator is inspired by [GUMP](https://github.com/Wixel/GUMP) (a PHP vali ## Ecosystem -- **[nitro-ui](https://github.com/nitrosh/nitro-ui)** - Programmatic HTML generation -- **[nitro-datastore](https://github.com/nitrosh/nitro-datastore)** - Data loading with dot notation access -- **[nitro-dispatch](https://github.com/nitrosh/nitro-dispatch)** - Plugin system -- **[nitro-validate](https://github.com/nitrosh/nitro-validate)** - Data validation +- **[nitro-cli](https://github.com/nitrosh/nitro-cli)** - Static site generator that builds sites with Python code +- **[nitro-ui](https://github.com/nitrosh/nitro-ui)** - Build HTML with Python, not strings +- **[nitro-datastore](https://github.com/nitrosh/nitro-datastore)** - Schema-free JSON data store with dot notation access +- **[nitro-dispatch](https://github.com/nitrosh/nitro-dispatch)** - Framework-agnostic plugin system +- **[nitro-image](https://github.com/nitrosh/nitro-image)** - Fast, friendly image processing for the web ## License diff --git a/examples/advanced_validation.py b/examples/advanced_validation.py index 54c64b7..f4d8b32 100644 --- a/examples/advanced_validation.py +++ b/examples/advanced_validation.py @@ -98,7 +98,7 @@ def example_optional_fields(): validator = Validator() data = { - "name": "John Doe", + "name": "John", # middle_name is not provided (optional) # phone is not provided (optional) } diff --git a/examples/basic_usage.py b/examples/basic_usage.py index 560c4d1..83bd1ee 100644 --- a/examples/basic_usage.py +++ b/examples/basic_usage.py @@ -14,7 +14,7 @@ def example_basic_validation(): data = {"username": "john_doe", "email": "john@example.com", "age": 25} rules = { - "username": "required|alphanumeric", + "username": "required|alpha_dash", "email": "required|email", "age": "required|numeric|min:18", } diff --git a/examples/date_validation.py b/examples/date_validation.py new file mode 100644 index 0000000..2d72f45 --- /dev/null +++ b/examples/date_validation.py @@ -0,0 +1,118 @@ +""" +Date validation examples. + +Covers the five date rules: date, before, after, date_equals, date_format. + +Note: date, before, after, and date_equals accept only unambiguous ISO 8601 +formats (YYYY-MM-DD, YYYY/MM/DD, and datetime variants). For regional +formats like DD-MM-YYYY, use date_format with an explicit format string. +""" + +from nitro_validator import Validator, ValidationError + + +def example_basic_date(): + """Valid ISO 8601 dates pass the date rule.""" + print("=== Basic Date Validation ===\n") + + validator = Validator() + + data = {"birthdate": "1990-06-15", "joined_at": "2024-01-10T09:30:00"} + rules = {"birthdate": "required|date", "joined_at": "required|date"} + + try: + validated = validator.validate(data, rules) + print("✓ ISO 8601 dates accepted!") + print(f"Validated data: {validated}\n") + except ValidationError as e: + print(f"✗ Validation failed: {e.errors}\n") + + +def example_ambiguous_format_rejected(): + """Regional formats are rejected by the date rule.""" + print("=== Ambiguous Format Rejection ===\n") + + validator = Validator() + + # DD-MM-YYYY and MM-DD-YYYY are indistinguishable — the date rule rejects both + data = {"event_date": "15-06-1990"} + rules = {"event_date": "required|date"} + + try: + validator.validate(data, rules) + print("✓ Passed (unexpected!)") + except ValidationError as e: + print("✗ Rejected as expected (use date_format for regional formats):") + print(f" Errors: {e.errors}\n") + + +def example_before_after(): + """Compare a date against a reference date.""" + print("=== Before / After ===\n") + + validator = Validator() + + data = { + "start_date": "2024-03-01", + "end_date": "2024-12-31", + } + rules = { + "start_date": "required|date|after:2024-01-01", + "end_date": "required|date|before:2025-01-01", + } + + try: + validated = validator.validate(data, rules) + print("✓ Date range valid!") + print(f"Validated data: {validated}\n") + except ValidationError as e: + print(f"✗ Validation failed: {e.errors}\n") + + +def example_date_equals(): + """Require an exact date match.""" + print("=== Date Equals ===\n") + + validator = Validator() + + data = {"launch_date": "2025-07-04"} + rules = {"launch_date": "required|date_equals:2025-07-04"} + + try: + validator.validate(data, rules) + print("✓ Date matches expected value!\n") + except ValidationError as e: + print(f"✗ Validation failed: {e.errors}\n") + + +def example_date_format(): + """Use date_format for regional or non-ISO formats.""" + print("=== Date Format (Regional) ===\n") + + validator = Validator() + + data = { + "uk_date": "15/06/1990", # DD/MM/YYYY + "us_date": "06-15-1990", # MM-DD-YYYY + "timestamp": "15 Jun 1990 09:30", # DD Mon YYYY HH:MM + } + rules = { + "uk_date": "required|date_format:%d/%m/%Y", + "us_date": "required|date_format:%m-%d-%Y", + "timestamp": "required|date_format:%d %b %Y %H:%M", + } + + try: + validated = validator.validate(data, rules) + print("✓ All regional formats parsed!") + print(f"Validated data: {validated}\n") + except ValidationError as e: + print(f"✗ Validation failed: {e.errors}\n") + + +if __name__ == "__main__": + example_basic_date() + example_ambiguous_format_rejected() + example_before_after() + example_date_equals() + example_date_format() diff --git a/examples/formats.py b/examples/formats.py new file mode 100644 index 0000000..8d409d7 --- /dev/null +++ b/examples/formats.py @@ -0,0 +1,132 @@ +""" +Format validation examples. + +Covers identifier and format-string rules: uuid, url, ipv4, mac_address, +credit_card, hex_color, and regex. + +Note: the string form splits on `|` (rules) and `,` (args). For regex +patterns containing those characters, pass a RegexRule object instead. +""" + +from nitro_validator import Validator, ValidationError, RegexRule, RequiredRule + + +def example_identifiers(): + """UUID, URL, IPv4, and MAC address rules.""" + print("=== Identifiers (UUID, URL, IPv4, MAC) ===\n") + + validator = Validator() + + data = { + "request_id": "550e8400-e29b-41d4-a716-446655440000", + "website": "https://example.com/about", + "server_ip": "192.168.1.42", + "nic": "00:1B:44:11:3A:B7", + } + rules = { + "request_id": "required|uuid", + "website": "required|url", + "server_ip": "required|ipv4", + "nic": "required|mac_address", + } + + try: + validated = validator.validate(data, rules) + print("✓ All identifiers valid!") + print(f"Validated data: {validated}\n") + except ValidationError as e: + print(f"✗ Validation failed: {e.errors}\n") + + +def example_credit_card_and_color(): + """Credit card (Luhn-checked) and hex color codes.""" + print("=== Credit Card & Hex Color ===\n") + + validator = Validator() + + # 4532015112830366 is a Luhn-valid test number + data = { + "card": "4532015112830366", + "brand_color": "#3366FF", + "accent": "#f0a", + } + rules = { + "card": "required|credit_card", + "brand_color": "required|hex_color", + "accent": "required|hex_color", + } + + try: + validated = validator.validate(data, rules) + print("✓ Card number (Luhn) and colors valid!") + print(f"Validated data: {validated}\n") + except ValidationError as e: + print(f"✗ Validation failed: {e.errors}\n") + + +def example_invalid_credit_card(): + """A number that fails the Luhn check is rejected.""" + print("=== Invalid Credit Card (Luhn Fail) ===\n") + + validator = Validator() + + data = {"card": "4532015112830367"} # last digit altered — Luhn fails + rules = {"card": "required|credit_card"} + + try: + validator.validate(data, rules) + print("✓ Passed (unexpected!)") + except ValidationError as e: + print("✗ Rejected as expected:") + print(f" Errors: {e.errors}\n") + + +def example_regex_string_form(): + """Simple regex via the pipe-string syntax (no `|` or `,` in pattern).""" + print("=== Regex (String Form) ===\n") + + validator = Validator() + + # US zip code: 5 digits, optional -4 extension + data = {"zip": "94103", "zip_plus_four": "94103-1234"} + rules = { + "zip": "required|regex:^\\d{5}$", + "zip_plus_four": "required|regex:^\\d{5}-\\d{4}$", + } + + try: + validated = validator.validate(data, rules) + print("✓ Both zip formats matched!") + print(f"Validated data: {validated}\n") + except ValidationError as e: + print(f"✗ Validation failed: {e.errors}\n") + + +def example_regex_rule_object(): + """Use the RegexRule object when the pattern contains `|` or `,`.""" + print("=== Regex (Rule Object for Complex Patterns) ===\n") + + validator = Validator() + + # Pattern uses alternation (`|`) and a quantifier with a comma — + # both would be mis-parsed by the string form. + data = {"country_code": "GB", "postcode": "SW1A 1AA"} + rules = { + "country_code": [RequiredRule(), RegexRule(r"^(US|GB|CA|AU)$")], + "postcode": [RequiredRule(), RegexRule(r"^[A-Z]{1,2}\d{1,2}[A-Z]? \d[A-Z]{2}$")], + } + + try: + validated = validator.validate(data, rules) + print("✓ Complex patterns matched!") + print(f"Validated data: {validated}\n") + except ValidationError as e: + print(f"✗ Validation failed: {e.errors}\n") + + +if __name__ == "__main__": + example_identifiers() + example_credit_card_and_color() + example_invalid_credit_card() + example_regex_string_form() + example_regex_rule_object() diff --git a/nitro_validator/__init__.py b/nitro_validator/__init__.py index 0b16de9..3f45856 100644 --- a/nitro_validator/__init__.py +++ b/nitro_validator/__init__.py @@ -11,7 +11,7 @@ validated = validator.validate(data, rules) """ -__version__ = "1.0.0" +__version__ = "1.0.2" from .core import ( NitroValidator, diff --git a/pyproject.toml b/pyproject.toml index f0e4da7..e28b428 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,18 +4,19 @@ build-backend = "setuptools.build_meta" [project] name = "nitro-validator" -version = "1.0.1" +version = "1.0.2" description = "A powerful, standalone, dependency-free data validation library for Python with extensible rules and a clean, intuitive API." readme = "README.md" authors = [ { name = "Sean Nieuwoudt", email = "sean@nitro.sh" } ] -license = { text = "BSD-3-Clause" } +license-files = ["LICEN[CS]E*"] requires-python = ">=3.7" classifiers = [ "Operating System :: OS Independent", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", @@ -42,10 +43,10 @@ dev = [ ] [project.urls] -Homepage = "https://github.com/nitro/nitro-validator" -Documentation = "https://github.com/nitro/nitro-validator/blob/main/README.md" -Repository = "https://github.com/nitro/nitro-validator" -Issues = "https://github.com/nitro/nitro-validator/issues" +Homepage = "https://github.com/nitrosh/nitro-validate" +Documentation = "https://github.com/nitrosh/nitro-validate/blob/main/README.md" +Repository = "https://github.com/nitrosh/nitro-validate" +Issues = "https://github.com/nitrosh/nitro-validate/issues" [tool.setuptools] packages = ["nitro_validator", "nitro_validator.core", "nitro_validator.utils"]