From 2ace16f5f07e26fe7dae4f4250fa4be3c50ce52c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Oct 2025 19:41:39 +0000 Subject: [PATCH] feat: Add comprehensive configuration system and examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added complete configuration support with multiple formats and examples to help users easily configure py-wikijs for their projects. New Features: - Configuration file templates for ENV, YAML, JSON, and INI formats - config_helper.py: Universal configuration loader and client factory * Auto-detects and loads configs from multiple formats * Supports environment variables, YAML, JSON, and INI files * Provides create_client_from_config() for easy client creation * Validates configuration and provides helpful error messages Configuration Templates: - config.env.example: Environment variables (Docker, 12-factor apps) - config.yaml.example: YAML with multi-environment support - config.json.example: JSON for programmatic configuration - config.ini.example: INI for traditional setups Usage Examples: - using_env_config.py: Complete example using .env files - using_yaml_config.py: Complete example using YAML configuration - using_json_config.py: Complete example using JSON configuration Documentation: - docs/CONFIGURATION_GUIDE.md: Comprehensive configuration guide * All configuration methods explained * Security best practices * Environment-specific configurations * Troubleshooting guide Benefits: ✅ Flexible configuration (choose your preferred format) ✅ Keep credentials secure (no hardcoding) ✅ Environment-specific configs (dev/staging/prod) ✅ Docker/container-ready ✅ Full validation and error handling ✅ Comprehensive documentation and examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/CONFIGURATION_GUIDE.md | 545 ++++++++++++++++++++++++++++++++++ examples/config.env.example | 97 ++++++ examples/config.ini.example | 91 ++++++ examples/config.json.example | 51 ++++ examples/config.yaml.example | 161 ++++++++++ examples/config_helper.py | 487 ++++++++++++++++++++++++++++++ examples/using_env_config.py | 103 +++++++ examples/using_json_config.py | 108 +++++++ examples/using_yaml_config.py | 104 +++++++ 9 files changed, 1747 insertions(+) create mode 100644 docs/CONFIGURATION_GUIDE.md create mode 100644 examples/config.env.example create mode 100644 examples/config.ini.example create mode 100644 examples/config.json.example create mode 100644 examples/config.yaml.example create mode 100755 examples/config_helper.py create mode 100755 examples/using_env_config.py create mode 100755 examples/using_json_config.py create mode 100755 examples/using_yaml_config.py diff --git a/docs/CONFIGURATION_GUIDE.md b/docs/CONFIGURATION_GUIDE.md new file mode 100644 index 0000000..6201450 --- /dev/null +++ b/docs/CONFIGURATION_GUIDE.md @@ -0,0 +1,545 @@ +# Configuration Guide - py-wikijs + +Complete guide to configuring the py-wikijs client for your project. + +--- + +## 📋 Table of Contents + +- [Quick Start](#quick-start) +- [Configuration Methods](#configuration-methods) +- [Configuration Options](#configuration-options) +- [Examples](#examples) +- [Best Practices](#best-practices) +- [Troubleshooting](#troubleshooting) + +--- + +## 🚀 Quick Start + +### **Simplest Method (Hardcoded)** + +```python +from wikijs import WikiJSClient + +client = WikiJSClient( + url='https://wiki.example.com', + auth='your-api-key-here' +) +``` + +⚠️ **Not recommended for production** - credentials should not be hardcoded! + +### **Recommended Method (Environment Variables)** + +```bash +# Set environment variables +export WIKIJS_URL="https://wiki.example.com" +export WIKIJS_API_KEY="your-api-key-here" +``` + +```python +import os +from wikijs import WikiJSClient + +client = WikiJSClient( + url=os.getenv('WIKIJS_URL'), + auth=os.getenv('WIKIJS_API_KEY') +) +``` + +### **Best Method (Configuration File)** + +Use the config helper (recommended): + +```python +from examples.config_helper import create_client_from_config + +# Auto-detects config file +client = create_client_from_config() +``` + +--- + +## 📁 Configuration Methods + +py-wikijs supports multiple configuration methods. Choose the one that best fits your workflow. + +### **Method 1: Environment Variables (.env)** + +**Best for**: Development, Docker deployments, 12-factor apps + +#### Setup: + +1. **Copy the example file**: + ```bash + cp examples/config.env.example .env + ``` + +2. **Edit `.env`** with your settings: + ```bash + WIKIJS_URL=https://wiki.example.com + WIKIJS_API_KEY=your-api-key-here + WIKIJS_TIMEOUT=30.0 + WIKIJS_RATE_LIMIT=10.0 + ``` + +3. **Load in your code**: + ```python + from dotenv import load_dotenv + from examples.config_helper import create_client_from_config + + load_dotenv() + client = create_client_from_config() + ``` + +#### Advantages: +- ✅ Keep secrets out of code +- ✅ Easy to change without code modification +- ✅ Works great with Docker/containers +- ✅ Standard 12-factor app methodology + +#### Requirements: +```bash +pip install python-dotenv +``` + +--- + +### **Method 2: YAML Configuration** + +**Best for**: Complex configurations, multiple environments + +#### Setup: + +1. **Copy the example file**: + ```bash + cp examples/config.yaml.example config.yaml + ``` + +2. **Edit `config.yaml`**: + ```yaml + wikijs: + url: "https://wiki.example.com" + auth: + method: "api_key" + api_key: "your-api-key-here" + + client: + timeout: 30.0 + rate_limit: 10.0 + + logging: + level: "INFO" + ``` + +3. **Load in your code**: + ```python + from examples.config_helper import create_client_from_config + + client = create_client_from_config('config.yaml') + ``` + +#### Advantages: +- ✅ Human-readable and editable +- ✅ Supports complex nested structures +- ✅ Easy to comment and document +- ✅ Great for environment-specific configs + +#### Requirements: +```bash +pip install pyyaml +``` + +--- + +### **Method 3: JSON Configuration** + +**Best for**: Programmatic configuration, API-driven setups + +#### Setup: + +1. **Copy the example file**: + ```bash + cp examples/config.json.example config.json + ``` + +2. **Edit `config.json`**: + ```json + { + "wikijs": { + "url": "https://wiki.example.com", + "auth": { + "method": "api_key", + "api_key": "your-api-key-here" + } + }, + "client": { + "timeout": 30.0, + "rate_limit": 10.0 + } + } + ``` + +3. **Load in your code**: + ```python + from examples.config_helper import create_client_from_config + + client = create_client_from_config('config.json') + ``` + +#### Advantages: +- ✅ Standard format +- ✅ Easy to generate programmatically +- ✅ Works with JSON APIs +- ✅ No additional dependencies + +--- + +### **Method 4: INI Configuration** + +**Best for**: Traditional configurations, legacy systems + +#### Setup: + +1. **Copy the example file**: + ```bash + cp examples/config.ini.example config.ini + ``` + +2. **Edit `config.ini`**: + ```ini + [wikijs] + url = https://wiki.example.com + auth_method = api_key + api_key = your-api-key-here + + [client] + timeout = 30.0 + rate_limit = 10.0 + ``` + +3. **Load in your code**: + ```python + from examples.config_helper import create_client_from_config + + client = create_client_from_config('config.ini') + ``` + +#### Advantages: +- ✅ Simple format +- ✅ No dependencies +- ✅ Familiar to many developers + +--- + +### **Method 5: Python Dictionary** + +**Best for**: Dynamic configuration, testing + +```python +from examples.config_helper import create_client_from_config + +config = { + 'wikijs': { + 'url': 'https://wiki.example.com', + 'auth': { + 'method': 'api_key', + 'api_key': 'your-api-key-here' + } + }, + 'client': { + 'timeout': 30.0, + 'rate_limit': 10.0 + } +} + +client = create_client_from_config(config) +``` + +--- + +## ⚙️ Configuration Options + +### **Required Options** + +| Option | Type | Description | Example | +|--------|------|-------------|---------| +| `wikijs.url` | string | Wiki.js instance URL | `https://wiki.example.com` | +| `wikijs.auth.api_key` | string | API key for authentication | `ey12345...` | + +### **Client Options** + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `client.timeout` | float | 30.0 | Request timeout (seconds) | +| `client.rate_limit` | float | None | Max requests per second | +| `client.max_retries` | int | 3 | Number of retry attempts | +| `client.verify_ssl` | bool | true | Verify SSL certificates | +| `client.pool_size` | int | 10 | Connection pool size | +| `client.user_agent` | string | Auto | Custom User-Agent header | + +### **Authentication Options** + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `wikijs.auth.method` | string | "api_key" | Auth method (api_key, jwt, none) | +| `wikijs.auth.api_key` | string | - | API key (for api_key method) | +| `wikijs.auth.jwt_token` | string | - | JWT token (for jwt method) | + +### **Logging Options** + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `logging.debug` | bool | false | Enable debug logging | +| `logging.level` | string | "INFO" | Log level (DEBUG, INFO, WARNING, ERROR) | +| `logging.format` | string | - | Custom log format string | + +### **Metrics Options** + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `metrics.enabled` | bool | true | Enable metrics collection | + +--- + +## 💡 Examples + +### **Example 1: Basic Connection** + +```python +from examples.config_helper import create_client_from_config + +# Auto-detect config file +client = create_client_from_config() + +# List pages +pages = client.pages.list() +print(f"Found {len(pages)} pages") +``` + +### **Example 2: Multiple Environments** + +**config.yaml**: +```yaml +environments: + production: + wikijs: + url: "https://wiki.company.com" + logging: + level: "WARNING" + + staging: + wikijs: + url: "https://wiki-staging.company.com" + logging: + level: "INFO" + + development: + wikijs: + url: "http://localhost:3000" + logging: + level: "DEBUG" +``` + +**Code**: +```python +import os +from examples.config_helper import load_config, create_client_from_config + +# Load config +config = load_config('config.yaml') + +# Get environment +env = os.getenv('ENVIRONMENT', 'development') +env_config = config['environments'][env] + +# Merge with base config +config.update(env_config) + +# Create client +client = create_client_from_config(config) +``` + +### **Example 3: Override Configuration** + +```python +from examples.config_helper import create_client_from_config + +# Load from file but override specific values +client = create_client_from_config( + 'config.yaml', + timeout=60.0, # Override timeout + rate_limit=20.0, # Override rate limit + log_level='DEBUG' # Override log level +) +``` + +### **Example 4: Docker/Container Deployment** + +**docker-compose.yml**: +```yaml +version: '3' +services: + app: + environment: + - WIKIJS_URL=https://wiki.example.com + - WIKIJS_API_KEY=${WIKIJS_API_KEY} + - WIKIJS_TIMEOUT=30.0 + - WIKIJS_RATE_LIMIT=10.0 +``` + +**Code** (loads from environment automatically): +```python +from examples.config_helper import create_client_from_config + +client = create_client_from_config() # Auto-loads from env vars +``` + +### **Example 5: Testing with Mock Config** + +```python +# test_example.py +import pytest +from examples.config_helper import create_client_from_config + +def test_client_creation(): + config = { + 'wikijs': { + 'url': 'http://test.example.com', + 'auth': {'api_key': 'test-key'} + } + } + + client = create_client_from_config(config) + assert client.base_url == 'http://test.example.com' +``` + +--- + +## 🔒 Best Practices + +### **1. Security** + +#### ✅ **DO:** +- Store credentials in environment variables or secure vaults +- Use `.env` files for development (add to `.gitignore`) +- Rotate API keys regularly +- Use least-privilege API keys + +#### ❌ **DON'T:** +- Hardcode credentials in source code +- Commit `.env` files to git +- Share API keys in public channels +- Use production keys in development + +### **2. Configuration Management** + +#### ✅ **DO:** +- Use different configs for each environment +- Version control config templates (`.example` files) +- Validate configuration on startup +- Document all configuration options + +#### ❌ **DON'T:** +- Mix environments in the same config +- Leave sensitive data in example files +- Skip validation checks + +### **3. File Structure** + +Recommended project structure: + +``` +your-project/ +├── .env # Local dev config (gitignored) +├── config.yaml.example # Template (committed) +├── config/ +│ ├── production.yaml # Production config (no secrets) +│ ├── staging.yaml # Staging config +│ └── development.yaml # Dev config +├── src/ +│ └── your_app.py +└── .gitignore # Ignore .env and config with secrets +``` + +**.gitignore**: +```gitignore +.env +config.yaml +config.json +config.ini +*.local.yaml +*-secret.* +``` + +--- + +## 🔧 Troubleshooting + +### **Issue: "No configuration file found"** + +**Solution**: Create a config file from an example: +```bash +cp examples/config.env.example .env +# Edit .env with your settings +``` + +### **Issue: "WIKIJS_URL is required"** + +**Solution**: Ensure URL is set in your config: +```bash +# .env file +WIKIJS_URL=https://wiki.example.com +``` + +### **Issue: "PyYAML is required"** + +**Solution**: Install YAML support: +```bash +pip install pyyaml +``` + +### **Issue: "Authentication failed"** + +**Solution**: Check your API key: +1. Log in to Wiki.js admin panel +2. Go to Administration → API Access +3. Create or copy an API key +4. Update your config with the correct key + +### **Issue: SSL verification fails** + +**Solution**: For self-signed certificates (development only): +```python +client = create_client_from_config(verify_ssl=False) +``` + +Or in `.env`: +```bash +WIKIJS_VERIFY_SSL=false +``` + +⚠️ **Never disable SSL verification in production!** + +--- + +## 📚 Additional Resources + +- **Config Examples**: See `examples/` directory for full examples +- **API Reference**: [docs/api_reference.md](api_reference.md) +- **User Guide**: [docs/user_guide.md](user_guide.md) +- **Security**: [docs/SECURITY.md](SECURITY.md) + +--- + +## 🆘 Need Help? + +- 📖 **Documentation**: Check [README.md](../README.md) +- 🐛 **Issues**: https://github.com/l3ocho/py-wikijs/issues +- 💬 **Discussions**: https://github.com/l3ocho/py-wikijs/discussions + +--- + +**Last Updated**: 2025-10-25 +**py-wikijs Version**: v0.1.0 diff --git a/examples/config.env.example b/examples/config.env.example new file mode 100644 index 0000000..d943771 --- /dev/null +++ b/examples/config.env.example @@ -0,0 +1,97 @@ +# py-wikijs Configuration File +# Copy this file to .env and update with your actual values +# Usage: Load with python-dotenv package + +# ============================================================ +# REQUIRED: Wiki.js Connection Settings +# ============================================================ + +# Your Wiki.js instance URL (no trailing slash) +WIKIJS_URL=https://wiki.example.com + +# Your Wiki.js API key (get from Admin > API Access) +WIKIJS_API_KEY=your-api-key-here + +# ============================================================ +# OPTIONAL: Client Configuration +# ============================================================ + +# Request timeout in seconds (default: 30.0) +WIKIJS_TIMEOUT=30.0 + +# Rate limit in requests per second (default: None - no limit) +# Set to prevent overwhelming your Wiki.js server +WIKIJS_RATE_LIMIT=10.0 + +# Maximum number of retries for failed requests (default: 3) +WIKIJS_MAX_RETRIES=3 + +# Enable debug logging (default: false) +# Options: true, false +WIKIJS_DEBUG=false + +# Log level (default: INFO) +# Options: DEBUG, INFO, WARNING, ERROR, CRITICAL +WIKIJS_LOG_LEVEL=INFO + +# ============================================================ +# OPTIONAL: Authentication Settings +# ============================================================ + +# Authentication method (default: api_key) +# Options: api_key, jwt, none +WIKIJS_AUTH_METHOD=api_key + +# JWT Token (if using JWT authentication) +# WIKIJS_JWT_TOKEN=your-jwt-token-here + +# ============================================================ +# OPTIONAL: Advanced Settings +# ============================================================ + +# Verify SSL certificates (default: true) +# Set to false only for development with self-signed certificates +WIKIJS_VERIFY_SSL=true + +# Custom User-Agent header +# WIKIJS_USER_AGENT=MyApp/1.0 py-wikijs/0.1.0 + +# Connection pool size (default: 10) +WIKIJS_POOL_SIZE=10 + +# Enable metrics collection (default: true) +WIKIJS_ENABLE_METRICS=true + +# ============================================================ +# OPTIONAL: Cache Settings (for future versions) +# ============================================================ + +# Enable caching (default: false) +# WIKIJS_CACHE_ENABLED=false + +# Cache TTL in seconds (default: 300) +# WIKIJS_CACHE_TTL=300 + +# Cache backend (default: memory) +# Options: memory, redis, file +# WIKIJS_CACHE_BACKEND=memory + +# Redis URL (if using redis cache backend) +# WIKIJS_REDIS_URL=redis://localhost:6379/0 + +# ============================================================ +# NOTES +# ============================================================ +# +# 1. Security: NEVER commit .env files to git! +# Add .env to your .gitignore file +# +# 2. Loading: Use python-dotenv to load this file: +# pip install python-dotenv +# from dotenv import load_dotenv +# load_dotenv() +# +# 3. Override: Environment variables set in shell take precedence +# +# 4. Validation: Use the config helper to validate settings +# See examples/config_helper.py diff --git a/examples/config.ini.example b/examples/config.ini.example new file mode 100644 index 0000000..e5f8bde --- /dev/null +++ b/examples/config.ini.example @@ -0,0 +1,91 @@ +# py-wikijs INI Configuration File +# Copy this file to config.ini and update with your actual values +# Usage: Load with Python's configparser + +[wikijs] +# Your Wiki.js instance URL (required) +url = https://wiki.example.com + +# Authentication method: api_key, jwt, or none +auth_method = api_key + +# Your Wiki.js API key +api_key = your-api-key-here + +# JWT token (alternative to API key) +# jwt_token = your-jwt-token-here + +[client] +# Request timeout in seconds +timeout = 30.0 + +# Rate limit in requests per second (0 = no limit) +rate_limit = 10.0 + +# Maximum retry attempts +max_retries = 3 + +# Verify SSL certificates (true/false) +verify_ssl = true + +# Connection pool size +pool_size = 10 + +# Custom User-Agent header +user_agent = MyApp/1.0 py-wikijs/0.1.0 + +[logging] +# Enable debug logging (true/false) +debug = false + +# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL +level = INFO + +# Log format string +format = %%(asctime)s - %%(name)s - %%(levelname)s - %%(message)s + +# Enable file logging (true/false) +file_enabled = false + +# Log file path +file_path = /var/log/wikijs-client.log + +# Max log file size in bytes +file_max_bytes = 10485760 + +# Number of backup log files +file_backup_count = 5 + +[metrics] +# Enable metrics collection (true/false) +enabled = true + +# Metrics format: prometheus, json, statsd +format = prometheus + +[cache] +# Enable caching (true/false) +enabled = false + +# Cache backend: memory, redis, file +backend = memory + +# Cache TTL in seconds +ttl = 300 + +# Maximum cache size (entries) +max_size = 1000 + +[cache.redis] +# Redis connection URL (if using redis backend) +url = redis://localhost:6379/0 + +[features] +# Enable async support (true/false) +async_enabled = false + +# Enable batch operations (true/false) +batch_enabled = true + +# Enable auto-pagination (true/false) +auto_pagination = true diff --git a/examples/config.json.example b/examples/config.json.example new file mode 100644 index 0000000..b6895fb --- /dev/null +++ b/examples/config.json.example @@ -0,0 +1,51 @@ +{ + "_comment": "py-wikijs JSON Configuration File", + "_usage": "Copy to config.json and update with your values", + + "wikijs": { + "url": "https://wiki.example.com", + "auth": { + "method": "api_key", + "api_key": "your-api-key-here" + } + }, + + "client": { + "timeout": 30.0, + "rate_limit": 10.0, + "max_retries": 3, + "verify_ssl": true, + "pool_size": 10, + "user_agent": "MyApp/1.0 py-wikijs/0.1.0" + }, + + "logging": { + "debug": false, + "level": "INFO", + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", + "file": { + "enabled": false, + "path": "/var/log/wikijs-client.log", + "max_bytes": 10485760, + "backup_count": 5 + } + }, + + "metrics": { + "enabled": true, + "format": "prometheus" + }, + + "cache": { + "enabled": false, + "backend": "memory", + "ttl": 300, + "max_size": 1000 + }, + + "features": { + "async_enabled": false, + "batch_enabled": true, + "auto_pagination": true + } +} diff --git a/examples/config.yaml.example b/examples/config.yaml.example new file mode 100644 index 0000000..9f9573d --- /dev/null +++ b/examples/config.yaml.example @@ -0,0 +1,161 @@ +# py-wikijs YAML Configuration File +# Copy this file to config.yaml and update with your actual values +# Usage: Load with PyYAML package + +# ============================================================ +# Wiki.js Connection Settings +# ============================================================ +wikijs: + # Your Wiki.js instance URL (required) + url: "https://wiki.example.com" + + # Authentication settings + auth: + # Method: api_key, jwt, or none + method: "api_key" + + # API key (get from Admin > API Access) + api_key: "your-api-key-here" + + # JWT token (alternative to API key) + # jwt_token: "your-jwt-token-here" + +# ============================================================ +# Client Configuration +# ============================================================ +client: + # Request timeout in seconds + timeout: 30.0 + + # Rate limiting (requests per second) + # Set to null for no limit + rate_limit: 10.0 + + # Maximum retry attempts + max_retries: 3 + + # Verify SSL certificates + verify_ssl: true + + # Connection pool size + pool_size: 10 + + # Custom User-Agent + user_agent: "MyApp/1.0 py-wikijs/0.1.0" + +# ============================================================ +# Logging Configuration +# ============================================================ +logging: + # Enable debug logging + debug: false + + # Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL + level: "INFO" + + # Log format + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + + # Log to file + file: + enabled: false + path: "/var/log/wikijs-client.log" + max_bytes: 10485760 # 10MB + backup_count: 5 + +# ============================================================ +# Metrics Configuration +# ============================================================ +metrics: + # Enable metrics collection + enabled: true + + # Metrics export format + format: "prometheus" # prometheus, json, statsd + + # Prometheus settings + prometheus: + port: 9090 + path: "/metrics" + +# ============================================================ +# Cache Configuration (for future versions) +# ============================================================ +cache: + # Enable caching + enabled: false + + # Cache backend: memory, redis, file + backend: "memory" + + # Cache TTL in seconds + ttl: 300 + + # Maximum cache size (entries) + max_size: 1000 + + # Redis settings (if backend is redis) + redis: + url: "redis://localhost:6379/0" + password: null + ssl: false + + # File cache settings (if backend is file) + file: + path: "/tmp/wikijs-cache" + +# ============================================================ +# Feature Flags +# ============================================================ +features: + # Enable async support (requires aiohttp) + async_enabled: false + + # Enable batch operations + batch_enabled: true + + # Enable auto-pagination + auto_pagination: true + +# ============================================================ +# Development Settings +# ============================================================ +development: + # Enable development mode + enabled: false + + # Mock API responses + mock_api: false + + # Save requests/responses for debugging + save_debug_data: false + debug_data_path: "/tmp/wikijs-debug" + +# ============================================================ +# Environment-Specific Overrides +# ============================================================ +environments: + production: + logging: + level: "WARNING" + client: + verify_ssl: true + development: + enabled: false + + staging: + wikijs: + url: "https://wiki-staging.example.com" + logging: + level: "INFO" + + development: + wikijs: + url: "http://localhost:3000" + logging: + level: "DEBUG" + debug: true + client: + verify_ssl: false + development: + enabled: true diff --git a/examples/config_helper.py b/examples/config_helper.py new file mode 100755 index 0000000..7dcfa47 --- /dev/null +++ b/examples/config_helper.py @@ -0,0 +1,487 @@ +#!/usr/bin/env python3 +"""Configuration helper for py-wikijs. + +This module provides utilities to load configuration from various sources: +- Environment variables (.env files) +- YAML files +- JSON files +- INI files +- Python dictionaries + +Usage: + from config_helper import load_config, create_client_from_config + + # Load config from file + config = load_config('config.yaml') + + # Create client from config + client = create_client_from_config(config) + + # Or use auto-detection + client = create_client_from_config() # Searches for config files +""" + +import json +import logging +import os +from configparser import ConfigParser +from pathlib import Path +from typing import Any, Dict, Optional, Union + +# Optional dependencies +try: + import yaml + + YAML_AVAILABLE = True +except ImportError: + YAML_AVAILABLE = False + +try: + from dotenv import load_dotenv + + DOTENV_AVAILABLE = True +except ImportError: + DOTENV_AVAILABLE = False + + +class ConfigLoader: + """Load configuration from various sources.""" + + def __init__(self, config_dir: Optional[str] = None): + """Initialize config loader. + + Args: + config_dir: Directory to search for config files (default: current dir) + """ + self.config_dir = Path(config_dir) if config_dir else Path.cwd() + + def load_env(self, env_file: Optional[str] = None) -> Dict[str, Any]: + """Load configuration from environment variables. + + Args: + env_file: Path to .env file (optional) + + Returns: + Configuration dictionary + """ + # Load .env file if available + if env_file and DOTENV_AVAILABLE: + load_dotenv(env_file) + elif DOTENV_AVAILABLE: + # Try to find .env in config directory + env_path = self.config_dir / ".env" + if env_path.exists(): + load_dotenv(env_path) + + config = { + "wikijs": { + "url": os.getenv("WIKIJS_URL"), + "auth": { + "method": os.getenv("WIKIJS_AUTH_METHOD", "api_key"), + "api_key": os.getenv("WIKIJS_API_KEY"), + "jwt_token": os.getenv("WIKIJS_JWT_TOKEN"), + }, + }, + "client": { + "timeout": float(os.getenv("WIKIJS_TIMEOUT", "30.0")), + "rate_limit": ( + float(os.getenv("WIKIJS_RATE_LIMIT")) + if os.getenv("WIKIJS_RATE_LIMIT") + else None + ), + "max_retries": int(os.getenv("WIKIJS_MAX_RETRIES", "3")), + "verify_ssl": os.getenv("WIKIJS_VERIFY_SSL", "true").lower() + == "true", + "pool_size": int(os.getenv("WIKIJS_POOL_SIZE", "10")), + "user_agent": os.getenv("WIKIJS_USER_AGENT"), + }, + "logging": { + "debug": os.getenv("WIKIJS_DEBUG", "false").lower() == "true", + "level": os.getenv("WIKIJS_LOG_LEVEL", "INFO"), + }, + "metrics": { + "enabled": os.getenv("WIKIJS_ENABLE_METRICS", "true").lower() + == "true", + }, + } + + # Remove None values + return self._clean_dict(config) + + def load_yaml(self, yaml_file: Union[str, Path]) -> Dict[str, Any]: + """Load configuration from YAML file. + + Args: + yaml_file: Path to YAML file + + Returns: + Configuration dictionary + + Raises: + ImportError: If PyYAML is not installed + """ + if not YAML_AVAILABLE: + raise ImportError( + "PyYAML is required for YAML config files. " + "Install with: pip install pyyaml" + ) + + yaml_path = Path(yaml_file) + if not yaml_path.is_absolute(): + yaml_path = self.config_dir / yaml_path + + with open(yaml_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + + def load_json(self, json_file: Union[str, Path]) -> Dict[str, Any]: + """Load configuration from JSON file. + + Args: + json_file: Path to JSON file + + Returns: + Configuration dictionary + """ + json_path = Path(json_file) + if not json_path.is_absolute(): + json_path = self.config_dir / json_path + + with open(json_path, "r", encoding="utf-8") as f: + return json.load(f) + + def load_ini(self, ini_file: Union[str, Path]) -> Dict[str, Any]: + """Load configuration from INI file. + + Args: + ini_file: Path to INI file + + Returns: + Configuration dictionary + """ + ini_path = Path(ini_file) + if not ini_path.is_absolute(): + ini_path = self.config_dir / ini_path + + parser = ConfigParser() + parser.read(ini_path) + + config: Dict[str, Any] = {} + + # Convert INI sections to nested dict + for section in parser.sections(): + section_dict: Dict[str, Any] = {} + for key, value in parser.items(section): + # Convert string values to appropriate types + section_dict[key] = self._parse_value(value) + + # Handle nested sections (e.g., cache.redis) + if "." in section: + parent, child = section.split(".", 1) + if parent not in config: + config[parent] = {} + config[parent][child] = section_dict + else: + config[section] = section_dict + + # Restructure for consistency with other formats + if "wikijs" in config: + config["wikijs"]["auth"] = { + "method": config["wikijs"].pop("auth_method", "api_key"), + "api_key": config["wikijs"].pop("api_key", None), + "jwt_token": config["wikijs"].pop("jwt_token", None), + } + + if "logging" in config: + if "file_enabled" in config["logging"]: + config["logging"]["file"] = { + "enabled": config["logging"].pop("file_enabled"), + "path": config["logging"].pop("file_path", None), + "max_bytes": config["logging"].pop("file_max_bytes", None), + "backup_count": config["logging"].pop("file_backup_count", None), + } + + return self._clean_dict(config) + + def auto_load(self) -> Dict[str, Any]: + """Auto-detect and load configuration from available sources. + + Searches for config files in this order: + 1. .env file + 2. config.yaml + 3. config.yml + 4. config.json + 5. config.ini + + Returns: + Configuration dictionary + + Raises: + FileNotFoundError: If no config file is found + """ + # Try .env + env_path = self.config_dir / ".env" + if env_path.exists(): + return self.load_env(env_path) + + # Try YAML + for yaml_name in ["config.yaml", "config.yml"]: + yaml_path = self.config_dir / yaml_name + if yaml_path.exists(): + return self.load_yaml(yaml_path) + + # Try JSON + json_path = self.config_dir / "config.json" + if json_path.exists(): + return self.load_json(json_path) + + # Try INI + ini_path = self.config_dir / "config.ini" + if ini_path.exists(): + return self.load_ini(ini_path) + + raise FileNotFoundError( + f"No configuration file found in {self.config_dir}. " + "Tried: .env, config.yaml, config.yml, config.json, config.ini" + ) + + def _parse_value(self, value: str) -> Any: + """Parse string value to appropriate type.""" + # Boolean + if value.lower() in ("true", "yes", "1", "on"): + return True + if value.lower() in ("false", "no", "0", "off"): + return False + + # None/null + if value.lower() in ("none", "null", ""): + return None + + # Number + try: + if "." in value: + return float(value) + return int(value) + except ValueError: + pass + + # String + return value + + def _clean_dict(self, d: Dict[str, Any]) -> Dict[str, Any]: + """Remove None values from nested dictionary.""" + cleaned = {} + for key, value in d.items(): + if isinstance(value, dict): + nested = self._clean_dict(value) + if nested: # Only add if not empty + cleaned[key] = nested + elif value is not None: + cleaned[key] = value + return cleaned + + +def load_config( + source: Optional[Union[str, Path, Dict[str, Any]]] = None, + config_dir: Optional[str] = None, +) -> Dict[str, Any]: + """Load configuration from a source. + + Args: + source: Config source (file path, dict, or None for auto-detection) + config_dir: Directory to search for config files + + Returns: + Configuration dictionary + + Examples: + >>> config = load_config('config.yaml') + >>> config = load_config('.env') + >>> config = load_config() # Auto-detect + >>> config = load_config({'wikijs': {'url': '...'}}) + """ + loader = ConfigLoader(config_dir) + + # If source is a dict, return it directly + if isinstance(source, dict): + return source + + # If source is provided, load it + if source: + source_path = Path(source) + + # Determine file type by extension + suffix = source_path.suffix.lower() + + if suffix in [".yaml", ".yml"]: + return loader.load_yaml(source) + elif suffix == ".json": + return loader.load_json(source) + elif suffix == ".ini": + return loader.load_ini(source) + elif suffix == ".env" or source_path.name == ".env": + return loader.load_env(source) + else: + raise ValueError(f"Unknown config file type: {suffix}") + + # Auto-detect + return loader.auto_load() + + +def create_client_from_config( + config: Optional[Union[str, Path, Dict[str, Any]]] = None, + **kwargs: Any, +) -> Any: + """Create WikiJSClient from configuration. + + Args: + config: Config source (file path, dict, or None for auto-detection) + **kwargs: Additional arguments to override config values + + Returns: + Configured WikiJSClient instance + + Examples: + >>> client = create_client_from_config('config.yaml') + >>> client = create_client_from_config() # Auto-detect + >>> client = create_client_from_config(timeout=60.0) # Override + """ + from wikijs import WikiJSClient + + # Load configuration + if config is None: + # Try to auto-load, fall back to env vars + try: + cfg = load_config() + except FileNotFoundError: + cfg = ConfigLoader().load_env() + else: + cfg = load_config(config) + + # Extract Wiki.js settings + wikijs_cfg = cfg.get("wikijs", {}) + url = kwargs.pop("url", wikijs_cfg.get("url")) + + if not url: + raise ValueError("Wiki.js URL is required (set WIKIJS_URL or in config file)") + + # Determine auth + auth_cfg = wikijs_cfg.get("auth", {}) + auth_method = auth_cfg.get("method", "api_key") + + if "auth" not in kwargs: + if auth_method == "api_key": + auth = auth_cfg.get("api_key") + if not auth: + raise ValueError( + "API key is required (set WIKIJS_API_KEY or in config file)" + ) + elif auth_method == "jwt": + auth = auth_cfg.get("jwt_token") + if not auth: + raise ValueError( + "JWT token is required (set WIKIJS_JWT_TOKEN or in config file)" + ) + else: + auth = None + else: + auth = kwargs.pop("auth") + + # Extract client settings + client_cfg = cfg.get("client", {}) + timeout = kwargs.pop("timeout", client_cfg.get("timeout", 30.0)) + rate_limit = kwargs.pop("rate_limit", client_cfg.get("rate_limit")) + max_retries = kwargs.pop("max_retries", client_cfg.get("max_retries", 3)) + verify_ssl = kwargs.pop("verify_ssl", client_cfg.get("verify_ssl", True)) + + # Extract logging settings + logging_cfg = cfg.get("logging", {}) + log_level_str = kwargs.pop("log_level", logging_cfg.get("level", "INFO")) + + # Convert log level string to logging constant + log_level = getattr(logging, log_level_str.upper(), logging.INFO) + + # Create client + client = WikiJSClient( + url=url, + auth=auth, + timeout=timeout, + rate_limit=rate_limit, + max_retries=max_retries, + verify_ssl=verify_ssl, + log_level=log_level, + **kwargs, + ) + + return client + + +def validate_config(config: Dict[str, Any]) -> bool: + """Validate configuration dictionary. + + Args: + config: Configuration dictionary + + Returns: + True if valid + + Raises: + ValueError: If configuration is invalid + """ + # Check required fields + if "wikijs" not in config: + raise ValueError("Missing 'wikijs' section in config") + + wikijs = config["wikijs"] + + if "url" not in wikijs: + raise ValueError("Missing 'wikijs.url' in config") + + # Validate URL format + url = wikijs["url"] + if not url.startswith(("http://", "https://")): + raise ValueError(f"Invalid URL format: {url}") + + # Check auth + if "auth" in wikijs: + auth = wikijs["auth"] + method = auth.get("method", "api_key") + + if method == "api_key" and not auth.get("api_key"): + raise ValueError("Missing API key for api_key auth method") + + if method == "jwt" and not auth.get("jwt_token"): + raise ValueError("Missing JWT token for jwt auth method") + + return True + + +if __name__ == "__main__": + # Example usage + print("🔧 py-wikijs Configuration Helper\n") + + try: + # Try to load config + config = load_config() + print(f"✅ Loaded configuration from auto-detected source") + print(f"\nConfiguration:") + print(json.dumps(config, indent=2)) + + # Validate + validate_config(config) + print("\n✅ Configuration is valid") + + # Create client + client = create_client_from_config(config) + print(f"\n✅ Created WikiJSClient") + print(f" URL: {client.base_url}") + + except FileNotFoundError as e: + print(f"❌ {e}") + print("\nℹ️ Create a config file from one of the examples:") + print(" - config.env.example → .env") + print(" - config.yaml.example → config.yaml") + print(" - config.json.example → config.json") + print(" - config.ini.example → config.ini") + + except Exception as e: + print(f"❌ Error: {e}") diff --git a/examples/using_env_config.py b/examples/using_env_config.py new file mode 100755 index 0000000..f95ae2b --- /dev/null +++ b/examples/using_env_config.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""Example: Using .env configuration file with py-wikijs. + +This example shows how to use environment variables (.env file) to configure +the WikiJS client. + +Prerequisites: + pip install python-dotenv + +Setup: + 1. Copy config.env.example to .env + 2. Update .env with your Wiki.js credentials + 3. Run this script + +Usage: + python using_env_config.py +""" + +import sys +from pathlib import Path + +# Add examples directory to path +sys.path.insert(0, str(Path(__file__).parent)) + +try: + from dotenv import load_dotenv +except ImportError: + print("❌ python-dotenv is required for .env files") + print("Install with: pip install python-dotenv") + sys.exit(1) + +from config_helper import create_client_from_config, load_config + + +def main(): + """Main function.""" + print("=" * 60) + print("Using .env Configuration with py-wikijs") + print("=" * 60) + + # Method 1: Load .env explicitly + print("\n📁 Method 1: Load .env explicitly") + env_file = Path(__file__).parent / ".env" + + if not env_file.exists(): + print(f"⚠️ .env file not found at {env_file}") + print(" Copy config.env.example to .env and update it") + print("\n📁 Method 2: Using config helper") + + # Load the .env file + load_dotenv(env_file) + + # Method 2: Use config helper (recommended) + try: + config = load_config(".env") + print(f"✅ Loaded configuration from .env") + print(f"\nConfiguration:") + print(f" URL: {config.get('wikijs', {}).get('url')}") + print(f" Auth method: {config.get('wikijs', {}).get('auth', {}).get('method')}") + print(f" Timeout: {config.get('client', {}).get('timeout')}s") + print(f" Rate limit: {config.get('client', {}).get('rate_limit')} req/s") + + # Create client + print("\n🔌 Creating WikiJS client...") + client = create_client_from_config(config) + print(f"✅ Client created successfully") + print(f" Base URL: {client.base_url}") + + # Test connection + print("\n🔍 Testing connection...") + pages = client.pages.list() + print(f"✅ Connected! Found {len(pages)} page(s)") + + if pages: + print(f"\n📄 First page:") + print(f" ID: {pages[0].id}") + print(f" Title: {pages[0].title}") + print(f" Path: {pages[0].path}") + + # Show metrics + metrics = client.get_metrics() + print(f"\n📊 Metrics:") + print(f" Total requests: {metrics['total_requests']}") + print(f" Successful: {metrics['successful_requests']}") + print(f" Failed: {metrics['failed_requests']}") + + except FileNotFoundError: + print("❌ .env file not found") + print(" 1. Copy config.env.example to .env") + print(" 2. Update with your Wiki.js credentials") + + except Exception as e: + print(f"❌ Error: {e}") + return 1 + + print("\n" + "=" * 60) + print("✅ Example completed successfully!") + print("=" * 60) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/using_json_config.py b/examples/using_json_config.py new file mode 100755 index 0000000..9c6b2ee --- /dev/null +++ b/examples/using_json_config.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Example: Using JSON configuration file with py-wikijs. + +This example shows how to use a JSON configuration file to configure +the WikiJS client. + +Setup: + 1. Copy config.json.example to config.json + 2. Update config.json with your Wiki.js credentials + 3. Run this script + +Usage: + python using_json_config.py +""" + +import json +import sys +from pathlib import Path + +# Add examples directory to path +sys.path.insert(0, str(Path(__file__).parent)) + +from config_helper import create_client_from_config, load_config + + +def main(): + """Main function.""" + print("=" * 60) + print("Using JSON Configuration with py-wikijs") + print("=" * 60) + + json_file = Path(__file__).parent / "config.json" + + if not json_file.exists(): + print(f"\n⚠️ config.json not found at {json_file}") + print(" Copy config.json.example to config.json and update it") + return 1 + + try: + # Load configuration + print("\n📁 Loading configuration from config.json...") + config = load_config(json_file) + print("✅ Configuration loaded successfully") + + # Pretty print config (without sensitive data) + safe_config = config.copy() + if "wikijs" in safe_config and "auth" in safe_config["wikijs"]: + if "api_key" in safe_config["wikijs"]["auth"]: + safe_config["wikijs"]["auth"]["api_key"] = "***REDACTED***" + + print(f"\nConfiguration (sanitized):") + print(json.dumps(safe_config, indent=2)) + + # Create client + print("\n🔌 Creating WikiJS client...") + client = create_client_from_config(config) + print(f"✅ Client created successfully") + + # Test connection + print("\n🔍 Testing connection...") + pages = client.pages.list() + print(f"✅ Connected! Found {len(pages)} page(s)") + + # Create a new page (example) + if input("\n❓ Create a test page? (y/N): ").lower() == "y": + from wikijs.models import PageCreate + + test_page = PageCreate( + title="Test Page from JSON Config", + path="test/json-config-example", + content="# Test Page\n\nCreated using JSON configuration!", + description="Example page created from JSON config", + is_published=False, + locale="en", + tags=["test", "json", "config"], + ) + + print("\n📝 Creating test page...") + created = client.pages.create(test_page) + print(f"✅ Page created: {created.title} (ID: {created.id})") + print(f" Path: {created.path}") + + # Show metrics + metrics = client.get_metrics() + print(f"\n📊 Client Metrics:") + print(f" Total requests: {metrics['total_requests']}") + print(f" Successful: {metrics['successful_requests']}") + print(f" Failed: {metrics['failed_requests']}") + + except FileNotFoundError as e: + print(f"❌ {e}") + return 1 + + except Exception as e: + print(f"❌ Error: {e}") + import traceback + + traceback.print_exc() + return 1 + + print("\n" + "=" * 60) + print("✅ Example completed successfully!") + print("=" * 60) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/using_yaml_config.py b/examples/using_yaml_config.py new file mode 100755 index 0000000..a94bb64 --- /dev/null +++ b/examples/using_yaml_config.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Example: Using YAML configuration file with py-wikijs. + +This example shows how to use a YAML configuration file to configure +the WikiJS client. + +Prerequisites: + pip install pyyaml + +Setup: + 1. Copy config.yaml.example to config.yaml + 2. Update config.yaml with your Wiki.js credentials + 3. Run this script + +Usage: + python using_yaml_config.py +""" + +import sys +from pathlib import Path + +# Add examples directory to path +sys.path.insert(0, str(Path(__file__).parent)) + +try: + import yaml +except ImportError: + print("❌ PyYAML is required for YAML config files") + print("Install with: pip install pyyaml") + sys.exit(1) + +from config_helper import create_client_from_config, load_config + + +def main(): + """Main function.""" + print("=" * 60) + print("Using YAML Configuration with py-wikijs") + print("=" * 60) + + yaml_file = Path(__file__).parent / "config.yaml" + + if not yaml_file.exists(): + print(f"\n⚠️ config.yaml not found at {yaml_file}") + print(" Copy config.yaml.example to config.yaml and update it") + return 1 + + try: + # Load configuration + print("\n📁 Loading configuration from config.yaml...") + config = load_config(yaml_file) + print("✅ Configuration loaded successfully") + + print(f"\nConfiguration:") + print(f" URL: {config.get('wikijs', {}).get('url')}") + print(f" Auth method: {config.get('wikijs', {}).get('auth', {}).get('method')}") + print(f" Timeout: {config.get('client', {}).get('timeout')}s") + print(f" Rate limit: {config.get('client', {}).get('rate_limit')} req/s") + print(f" Log level: {config.get('logging', {}).get('level')}") + + # Create client with environment-specific config + env = config.get("environments", {}).get("development", {}) + if env: + print(f"\n🔧 Using development environment settings") + + print("\n🔌 Creating WikiJS client...") + client = create_client_from_config(config) + print(f"✅ Client created successfully") + + # Test connection + print("\n🔍 Testing connection...") + pages = client.pages.list() + print(f"✅ Connected! Found {len(pages)} page(s)") + + if pages: + print(f"\n📄 Sample pages:") + for page in pages[:3]: # Show first 3 + print(f" - [{page.id}] {page.title} ({page.path})") + + # Show metrics + metrics = client.get_metrics() + print(f"\n📊 Client Metrics:") + print(f" Total requests: {metrics['total_requests']}") + print(f" Error rate: {metrics.get('error_rate', 0):.2f}%") + + except FileNotFoundError as e: + print(f"❌ {e}") + return 1 + + except Exception as e: + print(f"❌ Error: {e}") + import traceback + + traceback.print_exc() + return 1 + + print("\n" + "=" * 60) + print("✅ Example completed successfully!") + print("=" * 60) + return 0 + + +if __name__ == "__main__": + sys.exit(main())