devgear 1.0.0
🛠️ Essential gear for Python developers: decorators, validators, file utils, logging — all in one lightweight, zero-dependency package
License: MIT
Requires Python: >=3.8
Keywords: utilities, utils, helpers, tools, toolkit, developer-tools, decorators, retry, timeout, cache, memoize, memoization, rate-limit, throttle, validation, validators, email-validation, url-validation, data-validation, file-utils, file-operations, json, yaml, atomic-write, logging, logger, colored-logging, timing, timer, benchmark, profiling, productivity, python-tools, development, lightweight, zero-dependency, no-dependencies

<div align="center">

# 🛠️ DevGear

**Essential gear for Python developers**

[![PyPI version](https://img.shields.io/pypi/v/devgear.svg?logo=pypi&logoColor=white)](https://pypi.org/project/devgear/)
[![Python Versions](https://img.shields.io/pypi/pyversions/devgear.svg?logo=python&logoColor=white)](https://pypi.org/project/devgear/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Downloads](https://static.pepy.tech/badge/devgear)](https://pepy.tech/project/devgear)
[![Downloads/Month](https://static.pepy.tech/badge/devgear/month)](https://pepy.tech/project/devgear)

[![Tests](https://github.com/redstoner21/devgear/workflows/Tests/badge.svg)](https://github.com/redstoner21/devgear/actions)
[![Coverage](https://codecov.io/gh/redstoner21/devgear/branch/main/graph/badge.svg)](https://codecov.io/gh/redstoner21/devgear)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Type checked: mypy](https://img.shields.io/badge/type%20check-mypy-blue.svg)](https://mypy-lang.org/)

</div>

---

**DevGear** is a lightweight, zero-dependency Python toolkit that provides essential utilities every developer needs: powerful decorators, data validators, file operations, logging setup, and timing tools — all in one package.

## ✨ Why DevGear?

| Feature | Benefit |
|---------|---------|
| 🚀 **Zero Dependencies** | Pure Python — just install and use |
| 📦 **All-in-One** | No need for 10 different packages |
| 🎯 **Type Hints** | Full IDE support & autocompletion |
| ⚡ **Lightweight** | < 50KB, fast import |
| 🧪 **Well Tested** | 95%+ code coverage |
| 📚 **Well Documented** | Examples for everything |

## 📦 Installation

```bash
pip install devgear
```

# 🚀 Quick Start
🔄 Decorators
```python
from devgear import retry, timeout, memoize, timer, deprecated

# ═══════════════════════════════════════════════════
# Retry with exponential backoff
# ═══════════════════════════════════════════════════
@retry(max_attempts=3, delay=1.0, backoff=2.0)
def fetch_from_api():
    """Automatically retries on failure: 1s → 2s → 4s"""
    response = requests.get("https://api.example.com")
    response.raise_for_status()
    return response.json()

# With specific exceptions and callback
@retry(
    max_attempts=5,
    exceptions=(ConnectionError, TimeoutError),
    on_retry=lambda e, attempt: print(f"Retry {attempt}: {e}")
)
def connect_to_database():
    return Database.connect()

# ═══════════════════════════════════════════════════
# Timeout - prevent hanging operations
# ═══════════════════════════════════════════════════
@timeout(seconds=30)
def process_large_file(path):
    """Raises TimeoutExpiredError if takes > 30 seconds"""
    with open(path) as f:
        return analyze(f.read())

# ═══════════════════════════════════════════════════
# Memoize - cache expensive computations
# ═══════════════════════════════════════════════════
@memoize(maxsize=100, ttl=300)  # 100 items, 5 min TTL
def expensive_calculation(x, y):
    """Result cached for 5 minutes"""
    time.sleep(2)  # Simulate heavy computation
    return x ** y

# Cache info and management
expensive_calculation.cache_info()   # {'size': 10, 'maxsize': 100, 'ttl': 300}
expensive_calculation.cache_clear()  # Clear all cached values

# ═══════════════════════════════════════════════════
# Timer - measure execution time
# ═══════════════════════════════════════════════════
@timer()
def train_model():
    # ... training code ...
    pass
# Output: Function 'train_model' took 42.1234 seconds

# With custom logger
@timer(logger=logging.info, precision=2)
def quick_task():
    pass
# Logs: Function 'quick_task' took 0.01 seconds

# ═══════════════════════════════════════════════════
# Deprecated - mark old functions
# ═══════════════════════════════════════════════════
@deprecated(
    reason="Use 'new_api_call' for better performance",
    version="2.0.0",
    replacement="new_api_call"
)
def old_api_call():
    pass
# Warning: Function 'old_api_call' is deprecated (since version 2.0.0): 
#          Use 'new_api_call' for better performance. Use 'new_api_call' instead
```

✅ Validators
```python
from devgear import (
    validate_email, validate_url, validate_phone,
    validate_json, validate_uuid, validate_ip,
    validate_credit_card, is_empty, is_numeric
)

# ═══════════════════════════════════════════════════
# Email validation
# ═══════════════════════════════════════════════════
validate_email("user@example.com")           # ✅ True
validate_email("user.name+tag@domain.co.uk") # ✅ True
validate_email("invalid-email")              # ❌ False
validate_email("missing@domain")             # ❌ False

# Strict mode (additional RFC checks)
validate_email("valid@example.com", strict=True)

# ═══════════════════════════════════════════════════
# URL validation
# ═══════════════════════════════════════════════════
validate_url("https://github.com/user/repo")  # ✅ True
validate_url("http://localhost:8080/api")     # ✅ True
validate_url("ftp://files.example.com")       # ❌ False (not http/https)

# Require HTTPS
validate_url("http://example.com", require_https=True)  # ❌ False

# ═══════════════════════════════════════════════════
# Phone validation
# ═══════════════════════════════════════════════════
validate_phone("+1-234-567-8900")    # ✅ True
validate_phone("+7 (999) 123-45-67") # ✅ True
validate_phone("123")                # ❌ False (too short)

# ═══════════════════════════════════════════════════
# JSON validation
# ═══════════════════════════════════════════════════
validate_json('{"name": "John", "age": 30}')  # ✅ True
validate_json('[1, 2, 3]')                    # ✅ True
validate_json('{"invalid": }')                # ❌ False

# Get parsed data
is_valid, data = validate_json('{"key": "value"}', return_parsed=True)
if is_valid:
    print(data["key"])  # "value"

# ═══════════════════════════════════════════════════
# UUID validation
# ═══════════════════════════════════════════════════
validate_uuid("550e8400-e29b-41d4-a716-446655440000")  # ✅ True
validate_uuid("550e8400-e29b-41d4-a716-446655440000", version=4)  # ✅ True

# ═══════════════════════════════════════════════════
# IP address validation
# ═══════════════════════════════════════════════════
validate_ip("192.168.1.1")           # ✅ True (IPv4)
validate_ip("::1")                   # ✅ True (IPv6)
validate_ip("192.168.1.1", version=4) # ✅ True
validate_ip("::1", version=6)         # ✅ True

# ═══════════════════════════════════════════════════
# Credit card validation (Luhn algorithm)
# ═══════════════════════════════════════════════════
validate_credit_card("4532015112830366")    # ✅ True
validate_credit_card("4532-0151-1283-0366") # ✅ True (with dashes)
validate_credit_card("1234567890123456")    # ❌ False

# ═══════════════════════════════════════════════════
# Utility validators
# ═══════════════════════════════════════════════════
# Check for empty values
is_empty(None)     # ✅ True
is_empty("")       # ✅ True
is_empty("   ")    # ✅ True (whitespace only)
is_empty([])       # ✅ True
is_empty({})       # ✅ True
is_empty(0)        # ❌ False (0 is a value!)
is_empty("hello")  # ❌ False

# Check for numeric values
is_numeric(123)           # ✅ True
is_numeric(12.34)         # ✅ True
is_numeric("123")         # ✅ True
is_numeric("-45.67")      # ✅ True
is_numeric("abc")         # ❌ False
is_numeric("-5", allow_negative=False)  # ❌ False
is_numeric("3.14", allow_float=False)   # ❌ False
```

📁 File Operations
```python
from devgear import (
    read_file, write_file,
    read_json, write_json,
    read_yaml, write_yaml,
    atomic_write, safe_delete,
    ensure_dir, get_file_hash,
    list_files
)

# ═══════════════════════════════════════════════════
# Text files
# ═══════════════════════════════════════════════════
# Simple read/write
content = read_file("data.txt")
write_file("output.txt", "Hello, World!")

# With encoding
content = read_file("russian.txt", encoding="cp1251")

# With default value (no exception if missing)
config = read_file("optional.txt", default="")

# ═══════════════════════════════════════════════════
# JSON files
# ═══════════════════════════════════════════════════
# Read with default
config = read_json("config.json", default={"debug": False})

# Write with formatting
write_json("output.json", {
    "name": "DevGear",
    "version": "1.0.0",
    "features": ["decorators", "validators", "file-ops"]
}, indent=4)

# ═══════════════════════════════════════════════════
# YAML files (requires: pip install devgear[yaml])
# ═══════════════════════════════════════════════════
config = read_yaml("config.yaml", default={})
write_yaml("output.yaml", {"database": {"host": "localhost"}})

# ═══════════════════════════════════════════════════
# Atomic writes (safe for concurrent access)
# ═══════════════════════════════════════════════════
with atomic_write("critical_data.json") as f:
    json.dump(important_data, f)
# File is either fully written or not modified at all

# ═══════════════════════════════════════════════════
# Directory operations
# ═══════════════════════════════════════════════════
# Create directory tree
ensure_dir("path/to/nested/directory")

# Safe delete (no error if missing)
safe_delete("temp_file.txt")
safe_delete("temp_directory/", missing_ok=True)

# ═══════════════════════════════════════════════════
# File hashing
# ═══════════════════════════════════════════════════
sha256 = get_file_hash("document.pdf")
md5 = get_file_hash("document.pdf", algorithm="md5")

# ═══════════════════════════════════════════════════
# List files
# ═══════════════════════════════════════════════════
# All Python files in directory
py_files = list_files("src", "*.py")

# Recursive search
all_py = list_files("project", "*.py", recursive=True)
```

📝 Logging
```python
from devgear import setup_logger, get_logger, LogLevel

# ═══════════════════════════════════════════════════
# Quick setup with colors
# ═══════════════════════════════════════════════════
logger = setup_logger("myapp")
logger.debug("Debug message")      # Cyan
logger.info("Info message")        # Green
logger.warning("Warning message")  # Yellow
logger.error("Error message")      # Red
logger.critical("Critical!")       # Magenta

# ═══════════════════════════════════════════════════
# With file output and rotation
# ═══════════════════════════════════════════════════
logger = setup_logger(
    name="myapp",
    level=LogLevel.DEBUG,
    file_path="logs/app.log",
    file_max_bytes=10_000_000,  # 10 MB per file
    file_backup_count=5,        # Keep 5 backup files
    colored=True                # Colors in console
)

# ═══════════════════════════════════════════════════
# Custom format
# ═══════════════════════════════════════════════════
logger = setup_logger(
    "api",
    format_string="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s",
    date_format="%Y-%m-%d %H:%M:%S"
)

# ═══════════════════════════════════════════════════
# Get existing logger
# ═══════════════════════════════════════════════════
# In another module
logger = get_logger("myapp")
logger.info("Using the same logger")
```

⏱️ Timing & Rate Limiting
```python
from devgear import Timer, timeit, rate_limiter, RateLimiter

# ═══════════════════════════════════════════════════
# Timer context manager
# ═══════════════════════════════════════════════════
with Timer("Database query"):
    result = db.execute(complex_query)
# Output: Database query: 0.2345s

# Get elapsed time programmatically
with Timer(auto_print=False) as t:
    process_data()
print(f"Processing took {t.elapsed:.2f}s ({t.elapsed_ms:.0f}ms)")

# ═══════════════════════════════════════════════════
# Timer as decorator
# ═══════════════════════════════════════════════════
@timeit
def my_function():
    time.sleep(1)
# Output: my_function: 1.0012s

@timeit(name="Custom Name", logger=logging.debug)
def another_function():
    pass

# ═══════════════════════════════════════════════════
# Rate limiting - decorator
# ═══════════════════════════════════════════════════
@rate_limiter(rate=10, per=1.0)  # Max 10 calls per second
def api_call():
    return requests.get("https://api.example.com")

# Will automatically slow down to respect rate limit
for i in range(100):
    api_call()

# ═══════════════════════════════════════════════════
# Rate limiting - context manager
# ═══════════════════════════════════════════════════
limiter = RateLimiter(rate=5, per=1.0)  # 5 ops per second

for item in items:
    with limiter:
        process(item)

# Or manually
for item in items:
    wait_time = limiter.acquire()
    if wait_time > 0:
        print(f"Rate limited, waited {wait_time:.2f}s")
    process(item)
```

🔧 More Decorators
```python
from devgear import singleton, throttle, debug, catch_exceptions, run_async

# ═══════════════════════════════════════════════════
# Singleton pattern
# ═══════════════════════════════════════════════════
@singleton
class DatabaseConnection:
    def __init__(self, host="localhost"):
        self.host = host
        self.connect()

db1 = DatabaseConnection()
db2 = DatabaseConnection()
assert db1 is db2  # Same instance!

# ═══════════════════════════════════════════════════
# Throttle - limit call frequency
# ═══════════════════════════════════════════════════
@throttle(calls=5, period=60.0)  # Max 5 calls per minute
def send_notification(user, message):
    email.send(user, message)

# ═══════════════════════════════════════════════════
# Debug - print call info
# ═══════════════════════════════════════════════════
@debug(show_args=True, show_result=True, show_time=True)
def calculate(x, y, operation="add"):
    return x + y if operation == "add" else x - y

calculate(10, 5, operation="add")
# [DEBUG] Calling calculate(10, 5, operation='add')
# [DEBUG] calculate returned: 15
# [DEBUG] calculate took 0.000012s

# ═══════════════════════════════════════════════════
# Catch exceptions gracefully
# ═══════════════════════════════════════════════════
@catch_exceptions(ValueError, ZeroDivisionError, default=0)
def safe_divide(a, b):
    return a / b

safe_divide(10, 0)  # Returns 0 instead of raising

# With custom handler
@catch_exceptions(
    Exception,
    handler=lambda e: {"error": str(e), "status": "failed"}
)
def risky_operation():
    raise RuntimeError("Something went wrong")

result = risky_operation()  # {"error": "Something went wrong", "status": "failed"}

# ═══════════════════════════════════════════════════
# Run sync function asynchronously
# ═══════════════════════════════════════════════════
@run_async()
def blocking_io_operation():
    time.sleep(5)
    return "done"

future = blocking_io_operation()  # Returns immediately
# ... do other work ...
result = future.result()  # Wait for completion
```

</div>