linkbay-core 0.1.0
Shared utilities and constants for the LinkBay CMS ecosystem
License: MIT
Requires Python: >=3.8
Keywords: linkbay, cms, utilities, validators, formatters

# LinkBay Core

**Version**: 0.1.0  
**License**: MIT  
**Python**: 3.8+

Shared utilities and constants for the LinkBay CMS ecosystem.

---

## Table of Contents

- [What is LinkBay Core?](#what-is-linkbay-core)
- [Who Should Use This Library](#who-should-use-this-library)
- [Who Should NOT Use This Library](#who-should-not-use-this-library)
- [Installation](#installation)
- [Core Features](#core-features)
- [API Reference](#api-reference)
  - [Exceptions](#exceptions)
  - [Validators](#validators)
  - [Formatters](#formatters)
  - [Constants](#constants)
  - [Decorators](#decorators)
  - [Utilities](#utilities)
- [Usage Examples](#usage-examples)
- [Dependencies](#dependencies)
- [Testing](#testing)
- [License](#license)
- [Support](#support)

---

## What is LinkBay Core?

`linkbay-core` is a foundational Python library that provides shared utilities, validators, formatters, and constants for the LinkBay CMS ecosystem. It serves as the common layer across all LinkBay packages, ensuring consistency in error handling, data validation, formatting, and business logic.

This library is designed to be lightweight, dependency-minimal, and framework-agnostic, making it suitable for use in various contexts within the LinkBay platform.

**Key capabilities:**
- Standardized exception hierarchy for consistent error handling across all LinkBay services
- Comprehensive validation functions for emails, phone numbers, VAT numbers, slugs, and currencies
- Locale-aware formatting utilities for currencies, dates, and phone numbers
- Shared enumerations for order status, payment status, and tenant tiers
- Function decorators for rate limiting, caching, and tenant context enforcement
- Common utility functions for string manipulation, UUID generation, and date parsing

---

## Who Should Use This Library

This library is designed for:

- **LinkBay ecosystem developers**: If you're building or extending LinkBay CMS packages (Auth, Catalog, Orders, etc.), this library provides the foundational utilities you need
- **Python backend developers**: Working on e-commerce, CMS, or multi-tenant SaaS applications who need robust validation and formatting utilities
- **API developers**: Building RESTful or GraphQL APIs that require consistent error handling and data validation
- **Teams requiring standardization**: Organizations that need a shared set of utilities across multiple microservices or modules
- **Developers needing locale-aware formatting**: Applications serving Italian and international markets with proper currency and date formatting

---

## Who Should NOT Use This Library

This library may not be suitable if:

- **You're building standalone projects**: If you're not part of the LinkBay ecosystem and don't need the specific validators, formatters, and constants provided here
- **You need advanced validation**: For more complex validation scenarios beyond email, phone, VAT, slug, and currency, consider using libraries like `pydantic`, `cerberus`, or `marshmallow`
- **You require a full-featured framework**: This is a utility library, not a web framework. Use FastAPI, Django, or Flask for full application development
- **You need production-grade rate limiting**: The rate limiting decorator is in-memory and single-process. For distributed systems, use Redis-based solutions like `flask-limiter` or `slowapi`
- **You need persistent caching**: The caching decorator uses in-memory storage. For production, use Redis, Memcached, or other dedicated cache systems
- **You work exclusively with non-Italian locales**: While the library supports international formats, it has specific optimizations for Italian locale (currency, VAT, phone numbers)

---

## Installation

Install via pip (when published):

```bash
pip install linkbay-core
```

Or install directly from the repository:

```bash
pip install git+https://github.com/AlessioQuagliara/LinkBay-Core.git
```

For development installation:

```bash
git clone https://github.com/AlessioQuagliara/LinkBay-Core.git
cd LinkBay-Core
pip install -e ".[dev]"
```

---

## Core Features

- **Exception Hierarchy**: Seven exception classes (LinkBayException, ValidationError, NotFoundError, UnauthorizedError, ForbiddenError, ConflictError, RateLimitError) with HTTP status codes
- **Validators**: Five validation functions (email, phone, VAT, slug, currency) with comprehensive error messages
- **Formatters**: Three formatting utilities (currency, date, phone) with locale support
- **Constants**: Three enums (OrderStatus, PaymentStatus, TenantTier) and 13 configuration constants
- **Decorators**: Three function decorators (rate_limit, cache_result, require_tenant) with thread-safe implementations
- **Utilities**: Six helper functions (slugify, generate_uuid, parse_iso_date, truncate_string, normalize_whitespace, is_valid_uuid)

---

## API Reference

### Exceptions

All exceptions inherit from `LinkBayException` and include a `message` and `status_code` attribute.

#### `LinkBayException`

Base exception class for all LinkBay errors.

**Attributes:**
- `message` (str): Error description
- `status_code` (int): HTTP status code (default: 500)

**Usage:**
```python
from linkbay_core import LinkBayException

raise LinkBayException("Something went wrong", status_code=500)
```

#### `ValidationError`

Raised when input validation fails.

**Status Code:** 400  
**Usage:**
```python
from linkbay_core import ValidationError

raise ValidationError("Invalid email format")
```

#### `NotFoundError`

Raised when a requested resource does not exist.

**Status Code:** 404  
**Usage:**
```python
from linkbay_core import NotFoundError

raise NotFoundError("Product not found")
```

#### `UnauthorizedError`

Raised when authentication is required but not provided or invalid.

**Status Code:** 401  
**Usage:**
```python
from linkbay_core import UnauthorizedError

raise UnauthorizedError("Invalid token")
```

#### `ForbiddenError`

Raised when user lacks permission for an operation.

**Status Code:** 403  
**Usage:**
```python
from linkbay_core import ForbiddenError

raise ForbiddenError("Insufficient permissions")
```

#### `ConflictError`

Raised when there's a conflict with existing resources (e.g., duplicate entries).

**Status Code:** 409  
**Usage:**
```python
from linkbay_core import ConflictError

raise ConflictError("Email already exists")
```

#### `RateLimitError`

Raised when rate limit is exceeded.

**Status Code:** 429  
**Usage:**
```python
from linkbay_core import RateLimitError

raise RateLimitError("Too many requests. Try again in 30 seconds.")
```

---

### Validators

#### `validate_email(email: str) -> str`

Validates email address format and returns normalized (lowercase) email.

**Parameters:**
- `email` (str): Email address to validate

**Returns:**
- `str`: Normalized email address (lowercase)

**Raises:**
- `ValidationError`: If email format is invalid

**Example:**
```python
from linkbay_core import validate_email

email = validate_email("User@Example.COM")
# Returns: "user@example.com"

# Invalid email
try:
    validate_email("invalid-email")
except ValidationError as e:
    print(e.message)  # "Invalid email format: invalid-email"
```

#### `validate_phone(phone: str, region: str = "IT") -> str`

Validates phone number and returns E164 format.

**Parameters:**
- `phone` (str): Phone number to validate
- `region` (str, optional): ISO country code (default: "IT")

**Returns:**
- `str`: Phone number in E164 format (e.g., "+393401234567")

**Raises:**
- `ValidationError`: If phone number is invalid

**Example:**
```python
from linkbay_core import validate_phone

# Italian phone
phone = validate_phone("+39 340 123 4567")
# Returns: "+393401234567"

# US phone
phone = validate_phone("+1 (555) 123-4567", region="US")
# Returns: "+15551234567"

# Invalid phone
try:
    validate_phone("123")
except ValidationError as e:
    print(e.message)  # "Invalid phone number: 123"
```

#### `validate_vat(vat: str, country_code: str = "IT") -> str`

Validates VAT number and returns normalized format.

**Parameters:**
- `vat` (str): VAT number to validate
- `country_code` (str, optional): ISO country code (default: "IT")

**Returns:**
- `str`: Normalized VAT number (uppercase, no spaces)

**Raises:**
- `ValidationError`: If VAT number is invalid

**Example:**
```python
from linkbay_core import validate_vat

# Italian VAT (Partita IVA)
vat = validate_vat("12345678901")
# Returns: "IT12345678901"

vat = validate_vat("IT 12345678901")
# Returns: "IT12345678901"

# Other countries (basic validation)
vat = validate_vat("GB123456789", country_code="GB")
# Returns: "GB123456789"

# Invalid VAT
try:
    validate_vat("123")
except ValidationError as e:
    print(e.message)  # "Invalid Italian VAT number: 123"
```

#### `validate_slug(slug: str) -> str`

Validates URL slug format.

**Parameters:**
- `slug` (str): Slug to validate

**Returns:**
- `str`: Validated slug (lowercase)

**Raises:**
- `ValidationError`: If slug format is invalid

**Slug Requirements:**
- Only lowercase letters, numbers, and hyphens
- Must start and end with alphanumeric character
- Length between 2 and 100 characters

**Example:**
```python
from linkbay_core import validate_slug

slug = validate_slug("my-product-name")
# Returns: "my-product-name"

slug = validate_slug("Product-123")
# Returns: "product-123"

# Invalid slugs
try:
    validate_slug("invalid_slug")  # Underscores not allowed
except ValidationError as e:
    print(e.message)

try:
    validate_slug("-invalid")  # Cannot start with hyphen
except ValidationError as e:
    print(e.message)
```

#### `validate_currency(currency: str) -> str`

Validates ISO 4217 currency code.

**Parameters:**
- `currency` (str): Currency code to validate (e.g., "EUR", "USD")

**Returns:**
- `str`: Validated currency code (uppercase)

**Raises:**
- `ValidationError`: If currency code is invalid or unsupported

**Supported Currencies:**
EUR, USD, GBP, JPY, CHF, CAD, AUD, NZD, SEK, NOK, DKK, PLN, CZK, HUF, RON, BGN, HRK, RUB, TRY, BRL, CNY, INR, KRW, MXN, ZAR, SGD, HKD, THB, MYR, IDR, PHP, AED, SAR, ILS, EGP, ARS, CLP, COP, PEN, VND

**Example:**
```python
from linkbay_core import validate_currency

currency = validate_currency("eur")
# Returns: "EUR"

currency = validate_currency("USD")
# Returns: "USD"

# Invalid currency
try:
    validate_currency("XYZ")
except ValidationError as e:
    print(e.message)  # "Invalid or unsupported currency code: XYZ"
```

---

### Formatters

#### `format_currency(amount: Decimal, currency: str = "EUR") -> str`

Formats amount as currency string with proper symbols and separators.

**Parameters:**
- `amount` (Decimal | int | float): Amount to format
- `currency` (str, optional): Currency code (default: "EUR")

**Returns:**
- `str`: Formatted currency string

**Raises:**
- `ValidationError`: If amount is invalid

**Formatting Rules:**
- EUR, GBP: European format (1.234,56)
- USD, others: US format (1,234.56)

**Example:**
```python
from linkbay_core import format_currency
from decimal import Decimal

# European format (EUR)
amount = format_currency(Decimal("1234.56"), "EUR")
# Returns: "€1.234,56"

# US format (USD)
amount = format_currency(Decimal("1234.56"), "USD")
# Returns: "$1,234.56"

# British Pound
amount = format_currency(Decimal("999.99"), "GBP")
# Returns: "£999,99"

# Japanese Yen (no decimals typically)
amount = format_currency(Decimal("1234.00"), "JPY")
# Returns: "¥1,234.00"
```

#### `format_date(date: datetime, locale: str = "it_IT") -> str`

Formats datetime as localized string.

**Parameters:**
- `date` (datetime): Datetime object to format
- `locale` (str, optional): Locale code (default: "it_IT")

**Returns:**
- `str`: Formatted date string

**Raises:**
- `ValidationError`: If date is invalid

**Example:**
```python
from linkbay_core import format_date
from datetime import datetime

# Italian format
date = format_date(datetime(2025, 11, 24), "it_IT")
# Returns: "24 novembre 2025"

date = format_date(datetime(2025, 1, 15), "it_IT")
# Returns: "15 gennaio 2025"

# English format
date = format_date(datetime(2025, 11, 24), "en_US")
# Returns: "November 24, 2025"
```

#### `format_phone(phone: str, format: str = "international") -> str`

Formats phone number according to specified format.

**Parameters:**
- `phone` (str): Phone number to format (E164 or any parseable format)
- `format` (str, optional): Output format - "international", "national", or "e164" (default: "international")

**Returns:**
- `str`: Formatted phone number

**Raises:**
- `ValidationError`: If phone number is invalid or format is not supported

**Example:**
```python
from linkbay_core import format_phone

# International format
phone = format_phone("+393401234567", "international")
# Returns: "+39 340 123 4567"

# National format (Italian)
phone = format_phone("+393401234567", "national")
# Returns: "340 123 4567"

# E164 format
phone = format_phone("+39 340 123 4567", "e164")
# Returns: "+393401234567"

# US phone
phone = format_phone("+15551234567", "international")
# Returns: "+1 555 123 4567"
```

---

### Constants

#### Enums

**`OrderStatus`**

Order status enumeration for e-commerce workflows.

Values:
- `PENDING`: "pending" - Order created but not confirmed
- `CONFIRMED`: "confirmed" - Order confirmed by customer
- `PROCESSING`: "processing" - Order being prepared
- `SHIPPED`: "shipped" - Order shipped to customer
- `DELIVERED`: "delivered" - Order delivered successfully
- `CANCELLED`: "cancelled" - Order cancelled
- `REFUNDED`: "refunded" - Order refunded

**Example:**
```python
from linkbay_core import OrderStatus

status = OrderStatus.PENDING
print(status)  # Output: "pending"
print(status.value)  # Output: "pending"

# Comparison
if status == OrderStatus.PENDING:
    print("Order is pending")
```

**`PaymentStatus`**

Payment status enumeration for payment workflows.

Values:
- `UNPAID`: "unpaid" - Payment not received
- `AUTHORIZED`: "authorized" - Payment authorized but not captured
- `PAID`: "paid" - Payment completed successfully
- `REFUNDED`: "refunded" - Payment refunded
- `FAILED`: "failed" - Payment failed

**Example:**
```python
from linkbay_core import PaymentStatus

status = PaymentStatus.PAID
print(status)  # Output: "paid"
```

**`TenantTier`**

Tenant subscription tier enumeration for multi-tenant SaaS.

Values:
- `STARTER`: "starter" - Basic tier
- `PROFESSIONAL`: "professional" - Professional tier
- `ENTERPRISE`: "enterprise" - Enterprise tier

**Example:**
```python
from linkbay_core import TenantTier

tier = TenantTier.PROFESSIONAL
print(tier)  # Output: "professional"
```

#### Configuration Constants

```python
from linkbay_core import (
    DEFAULT_CACHE_TTL,          # 600 (10 minutes)
    DEFAULT_PAGE_SIZE,          # 20
    MAX_PAGE_SIZE,              # 100
    DEFAULT_RATE_LIMIT_REQUESTS,  # 100
    DEFAULT_RATE_LIMIT_WINDOW,    # 60 (seconds)
    ISO_DATE_FORMAT,            # "%Y-%m-%d"
    ISO_DATETIME_FORMAT,        # "%Y-%m-%dT%H:%M:%S"
    DEFAULT_LOCALE,             # "it_IT"
    DEFAULT_CURRENCY,           # "EUR"
    MIN_SLUG_LENGTH,            # 2
    MAX_SLUG_LENGTH,            # 100
    MAX_EMAIL_LENGTH,           # 254
    DEFAULT_PHONE_REGION,       # "IT"
)
```

---

### Decorators

#### `@rate_limit(requests: int = 100, window: int = 60)`

Rate limiting decorator to limit function calls within a time window.

**Parameters:**
- `requests` (int): Maximum number of requests allowed (default: 100)
- `window` (int): Time window in seconds (default: 60)

**Raises:**
- `RateLimitError`: When rate limit is exceeded

**Note:** This is an in-memory, single-process implementation. For distributed systems, use Redis-based solutions.

**Example:**
```python
from linkbay_core import rate_limit, RateLimitError

@rate_limit(requests=10, window=60)
def send_email(to: str, subject: str):
    # Limited to 10 calls per minute
    print(f"Sending email to {to}")

# Usage
try:
    for i in range(15):
        send_email("user@example.com", "Test")
except RateLimitError as e:
    print(e.message)  # "Rate limit exceeded. Try again in X seconds."
```

#### `@cache_result(ttl: int = 600)`

Caches function result for a specified time.

**Parameters:**
- `ttl` (int): Time to live in seconds (default: 600)

**Note:** This is an in-memory cache. Results are lost on process restart. For production, use Redis or Memcached.

**Example:**
```python
from linkbay_core import cache_result
import time

@cache_result(ttl=300)
def expensive_query(user_id: int):
    print("Executing expensive query...")
    time.sleep(2)  # Simulate slow operation
    return {"user_id": user_id, "data": "..."}

# First call - executes query
result1 = expensive_query(123)  # Takes 2 seconds

# Second call within 5 minutes - returns cached result
result2 = expensive_query(123)  # Returns immediately

# After TTL expires - executes query again
time.sleep(301)
result3 = expensive_query(123)  # Takes 2 seconds again
```

**Clear cache:**
```python
from linkbay_core import clear_cache

clear_cache()  # Clears all cached results
```

#### `@require_tenant`

Ensures tenant context exists before function execution.

**Raises:**
- `UnauthorizedError`: If tenant context is missing

**Example:**
```python
from linkbay_core import require_tenant, UnauthorizedError

@require_tenant
def get_tenant_data(tenant_id: str):
    return f"Data for tenant: {tenant_id}"

# Valid call
data = get_tenant_data(tenant_id="tenant-123")

# Invalid call - raises UnauthorizedError
try:
    get_tenant_data()  # Missing tenant_id
except UnauthorizedError as e:
    print(e.message)  # "Tenant context is required. Please provide tenant_id."
```

---

### Utilities

#### `slugify(text: str) -> str`

Converts text to a URL-friendly slug.

**Parameters:**
- `text` (str): Text to convert

**Returns:**
- `str`: URL-friendly slug (lowercase, hyphens only)

**Raises:**
- `ValidationError`: If text is empty or invalid

**Features:**
- Converts to lowercase
- Replaces accented characters (à → a, é → e, etc.)
- Removes non-alphanumeric characters
- Replaces spaces with hyphens
- Collapses multiple hyphens

**Example:**
```python
from linkbay_core import slugify

slug = slugify("Hello World!")
# Returns: "hello-world"

slug = slugify("Caffè Latte")
# Returns: "caffe-latte"

slug = slugify("Product #123 - Special Offer!!!")
# Returns: "product-123-special-offer"

slug = slugify("Città di Roma")
# Returns: "citta-di-roma"
```

#### `generate_uuid() -> str`

Generates a new UUID v4.

**Returns:**
- `str`: UUID string (lowercase, with hyphens)

**Example:**
```python
from linkbay_core import generate_uuid

uuid = generate_uuid()
# Returns: "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"

# Generate multiple UUIDs
ids = [generate_uuid() for _ in range(3)]
```

#### `parse_iso_date(date_str: str) -> datetime`

Parses ISO format date string to datetime object.

**Parameters:**
- `date_str` (str): ISO format date string

**Returns:**
- `datetime`: Parsed datetime object

**Raises:**
- `ValidationError`: If date string is invalid

**Supported Formats:**
- `2025-11-24`
- `2025-11-24T10:30:00`
- `2025-11-24T10:30:00Z`
- `2025-11-24T10:30:00+01:00`

**Example:**
```python
from linkbay_core import parse_iso_date

# Date only
date = parse_iso_date("2025-11-24")
# Returns: datetime(2025, 11, 24, 0, 0)

# DateTime
date = parse_iso_date("2025-11-24T10:30:00")
# Returns: datetime(2025, 11, 24, 10, 30)

# DateTime with timezone
date = parse_iso_date("2025-11-24T10:30:00+01:00")
# Returns: datetime with timezone info

# Invalid date
try:
    parse_iso_date("not-a-date")
except ValidationError as e:
    print(e.message)
```

#### `truncate_string(text: str, max_length: int = 100, suffix: str = "...") -> str`

Truncates string to maximum length with suffix.

**Parameters:**
- `text` (str): Text to truncate
- `max_length` (int): Maximum length (default: 100)
- `suffix` (str): Suffix to add when truncated (default: "...")

**Returns:**
- `str`: Truncated string

**Example:**
```python
from linkbay_core import truncate_string

text = truncate_string("Hello World", 5)
# Returns: "He..."

text = truncate_string("Short", 10)
# Returns: "Short"

text = truncate_string("Long description here", 15, suffix="…")
# Returns: "Long descrip…"
```

#### `normalize_whitespace(text: str) -> str`

Normalizes whitespace in text (collapses multiple spaces, trims).

**Parameters:**
- `text` (str): Text to normalize

**Returns:**
- `str`: Text with normalized whitespace

**Example:**
```python
from linkbay_core import normalize_whitespace

text = normalize_whitespace("  Hello   World  ")
# Returns: "Hello World"

text = normalize_whitespace("Multiple\n\nlines\twith\ttabs")
# Returns: "Multiple lines with tabs"
```

#### `is_valid_uuid(value: str) -> bool`

Checks if string is a valid UUID.

**Parameters:**
- `value` (str): String to check

**Returns:**
- `bool`: True if valid UUID, False otherwise

**Example:**
```python
from linkbay_core import is_valid_uuid

valid = is_valid_uuid("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d")
# Returns: True

valid = is_valid_uuid("not-a-uuid")
# Returns: False

valid = is_valid_uuid("12345678-1234-1234-1234-123456789abc")
# Returns: True
```

---

## Usage Examples

### Complete Validation Pipeline

```python
from linkbay_core import (
    validate_email,
    validate_phone,
    validate_currency,
    ValidationError
)

def process_order(email: str, phone: str, amount: float, currency: str):
    try:
        # Validate all inputs
        validated_email = validate_email(email)
        validated_phone = validate_phone(phone)
        validated_currency = validate_currency(currency)
        
        print(f"Order processed:")
        print(f"  Email: {validated_email}")
        print(f"  Phone: {validated_phone}")
        print(f"  Amount: {amount} {validated_currency}")
        
        return True
        
    except ValidationError as e:
        print(f"Validation failed: {e.message}")
        return False

# Usage
process_order(
    email="customer@example.com",
    phone="+39 340 123 4567",
    amount=99.99,
    currency="EUR"
)
```

### API Rate Limiting

```python
from linkbay_core import rate_limit, RateLimitError
from datetime import datetime

@rate_limit(requests=5, window=60)
def api_endpoint(user_id: str):
    print(f"[{datetime.now()}] Processing request for user {user_id}")
    return {"status": "success"}

# Simulate API calls
for i in range(10):
    try:
        result = api_endpoint("user-123")
        print(f"Call {i+1}: {result}")
    except RateLimitError as e:
        print(f"Call {i+1}: {e.message}")
```

### Caching Expensive Operations

```python
from linkbay_core import cache_result
import time

@cache_result(ttl=300)
def fetch_user_data(user_id: int):
    print(f"Fetching data for user {user_id} from database...")
    time.sleep(2)  # Simulate database query
    return {
        "id": user_id,
        "name": "John Doe",
        "email": "john@example.com"
    }

# First call - hits database
start = time.time()
data1 = fetch_user_data(123)
print(f"First call took {time.time() - start:.2f}s")

# Second call - returns cached result
start = time.time()
data2 = fetch_user_data(123)
print(f"Second call took {time.time() - start:.2f}s")
```

### Multi-tenant Application

```python
from linkbay_core import require_tenant, UnauthorizedError

@require_tenant
def get_tenant_products(tenant_id: str):
    # Fetch products for specific tenant
    return [
        {"id": 1, "name": "Product A", "tenant_id": tenant_id},
        {"id": 2, "name": "Product B", "tenant_id": tenant_id}
    ]

# Valid usage
products = get_tenant_products(tenant_id="company-abc")

# Invalid usage
try:
    products = get_tenant_products()  # Missing tenant_id
except UnauthorizedError as e:
    print(f"Error: {e.message}")
```

### Complete Error Handling

```python
from linkbay_core import (
    ValidationError,
    NotFoundError,
    UnauthorizedError,
    ForbiddenError,
    ConflictError,
    RateLimitError,
    LinkBayException
)

def handle_request(action: str):
    try:
        if action == "validate":
            raise ValidationError("Invalid input data")
        elif action == "not_found":
            raise NotFoundError("Resource not found")
        elif action == "unauthorized":
            raise UnauthorizedError("Authentication required")
        elif action == "forbidden":
            raise ForbiddenError("Access denied")
        elif action == "conflict":
            raise ConflictError("Resource already exists")
        elif action == "rate_limit":
            raise RateLimitError("Too many requests")
        else:
            raise LinkBayException("Unknown error")
            
    except LinkBayException as e:
        return {
            "error": e.message,
            "status_code": e.status_code
        }

# Test all exceptions
for action in ["validate", "not_found", "unauthorized", "forbidden", "conflict", "rate_limit"]:
    result = handle_request(action)
    print(f"{action}: {result}")
```

---

## Dependencies

This library has minimal external dependencies:

- `pydantic>=2.0.0` - Data validation and settings management
- `python-dateutil>=2.8.0` - Date parsing utilities
- `phonenumbers>=8.13.0` - Phone number validation and formatting

Development dependencies (optional):

```bash
pip install -e ".[dev]"
```

Includes:
- `pytest` - Testing framework
- `pytest-cov` - Coverage reporting
- `black` - Code formatting
- `mypy` - Type checking

---

## Testing

```bash
# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=linkbay_core --cov-report=html

# Run type checking
mypy linkbay_core

# Format code
black linkbay_core
```

---

## License

MIT License - Copyright (c) 2025 Alessio Quagliara (quagliara.alessio@gmail.com)

See [LICENSE](LICENSE) for full details.

---

## Support

- Issues: https://github.com/AlessioQuagliara/linkbay-core/issues
- Email: quagliara.alessio@gmail.com

Contribuisci, apri una issue o raccontaci come stai usando la libreria 🧡