Created Python-Version/ directory structure: ✅ Complete folder hierarchy (scripts, config, logs, temp, tests) ✅ Virtual environment setup script ✅ Python 3.6+ compatible dependencies ✅ Configuration system with env var substitution ✅ DAM API client (complete) Components Implemented: 1. setup.sh - venv creation and dependency installation 2. requirements.txt - Python 3.6/3.10 compatible packages 3. config/config.yaml - Main configuration (URLs, credentials, settings) 4. config/field_mappings.yaml - MVP fields list (easy to edit!) 5. config_loader.py - YAML config with ${VAR} substitution 6. dam_client.py - Complete DAM API wrapper: - OAuth2 with auto-refresh - search_campaigns(status) - get_master_assets(campaign_id) - download_asset(asset_id) - upload_asset() with video metadata - update_campaign_status() - Helper methods Features: - Python 3.6 compatible (shared hosting requirement) - Python 3.10 compatible (local development) - Configuration-driven (no hardcoded values) - Environment-specific configs (staging/production) - Comprehensive error handling - Logging built-in Next: Box client, Database client, FilenameParser, MetadataExtractorMVP, Notifier, then main scripts (A1→A2, A2→A3) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
90 lines
2.5 KiB
Python
90 lines
2.5 KiB
Python
"""
|
|
Configuration Loader - Load YAML config with environment variable substitution
|
|
Compatible with Python 3.6+
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import yaml
|
|
from dotenv import load_dotenv
|
|
|
|
def load_config(config_path='config/config.yaml'):
|
|
"""
|
|
Load configuration from YAML file with environment variable substitution
|
|
|
|
Supports:
|
|
- ${VAR_NAME} - Required environment variable
|
|
- ${VAR_NAME:-default} - Optional with default value
|
|
"""
|
|
# Load environment variables from .env file
|
|
load_dotenv()
|
|
|
|
# Read YAML file
|
|
with open(config_path, 'r') as f:
|
|
config_text = f.read()
|
|
|
|
# Substitute environment variables
|
|
config_text = substitute_env_vars(config_text)
|
|
|
|
# Parse YAML
|
|
config = yaml.safe_load(config_text)
|
|
|
|
# Load environment-specific overrides if specified
|
|
env = config.get('environment', 'staging')
|
|
env_config_path = 'config/environments/{}.yaml'.format(env)
|
|
|
|
if os.path.exists(env_config_path):
|
|
with open(env_config_path, 'r') as f:
|
|
env_config_text = f.read()
|
|
env_config_text = substitute_env_vars(env_config_text)
|
|
env_config = yaml.safe_load(env_config_text)
|
|
|
|
# Merge environment-specific config
|
|
config = deep_merge(config, env_config)
|
|
|
|
return config
|
|
|
|
def substitute_env_vars(text):
|
|
"""
|
|
Substitute ${VAR_NAME} and ${VAR_NAME:-default} patterns with environment variables
|
|
"""
|
|
def replacer(match):
|
|
var_expr = match.group(1)
|
|
|
|
# Check for default value syntax: VAR:-default
|
|
if ':-' in var_expr:
|
|
var_name, default = var_expr.split(':-', 1)
|
|
return os.getenv(var_name, default)
|
|
else:
|
|
# Required variable
|
|
value = os.getenv(var_expr)
|
|
if value is None:
|
|
raise ValueError("Required environment variable not set: {}".format(var_expr))
|
|
return value
|
|
|
|
# Pattern: ${VAR_NAME} or ${VAR_NAME:-default}
|
|
pattern = r'\$\{([^}]+)\}'
|
|
return re.sub(pattern, replacer, text)
|
|
|
|
def deep_merge(base, override):
|
|
"""
|
|
Deep merge two dictionaries
|
|
"""
|
|
result = base.copy()
|
|
|
|
for key, value in override.items():
|
|
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
result[key] = deep_merge(result[key], value)
|
|
else:
|
|
result[key] = value
|
|
|
|
return result
|
|
|
|
def load_field_mappings(config):
|
|
"""
|
|
Load field mappings configuration
|
|
"""
|
|
mappings_file = config['fields']['mappings_file']
|
|
|
|
with open(mappings_file, 'r') as f:
|
|
return yaml.safe_load(f)
|