Spaces:
Sleeping
Sleeping
Download activity_ingestion.py from StrategyStack/README: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/spaces/StrategyStack/README/resolve/92bc9c9bfe9f35d55512f02027798d7dd03b2aa4/activity_ingestion.py
- Command line
-
hf download hf://spaces/StrategyStack/README@92bc9c9bfe9f35d55512f02027798d7dd03b2aa4/activity_ingestion.py
-
curl -L -o activity_ingestion.py https://huggingface.co/spaces/StrategyStack/README/resolve/92bc9c9bfe9f35d55512f02027798d7dd03b2aa4/activity_ingestion.py
11 kB
| """Activity Ingestion Function with Atomic Operations and Commit Tracking | |
| This module provides schema-compliant activity logging with: | |
| - Atomic row additions (all-or-nothing operations) | |
| - Schema validation and error handling | |
| - Request ID and timestamp generation | |
| - Full commit tracking for rollback and versioning | |
| - Comprehensive error logging | |
| """ | |
| import csv | |
| import os | |
| import uuid | |
| from datetime import datetime, timezone | |
| from typing import Dict, Optional, Tuple | |
| import fcntl | |
| import tempfile | |
| import shutil | |
| from pathlib import Path | |
| # Schema Definition (v1.0) | |
| SCHEMA_VERSION = "1.0" | |
| REQUIRED_FIELDS = ['timestamp', 'agent_id', 'activity_type', 'entity_id', 'status', 'notes', 'context'] | |
| VALID_STATUS_VALUES = ['created', 'success', 'failed', 'in_progress', 'warning', 'error'] | |
| VALID_ACTIVITY_TYPES = ['init', 'validate', 'ingest', 'update', 'delete', 'query', 'commit', 'rollback'] | |
| class ActivityIngestionError(Exception): | |
| """Base exception for activity ingestion errors""" | |
| pass | |
| class SchemaValidationError(ActivityIngestionError): | |
| """Schema validation failed""" | |
| pass | |
| class AtomicWriteError(ActivityIngestionError): | |
| """Atomic write operation failed""" | |
| pass | |
| def generate_request_id() -> str: | |
| """Generate unique request ID for tracking""" | |
| return f"req_{uuid.uuid4().hex[:16]}" | |
| def generate_timestamp() -> str: | |
| """Generate ISO 8601 timestamp in UTC""" | |
| return datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z') | |
| def validate_activity_record(record: Dict[str, str]) -> Tuple[bool, Optional[str]]: | |
| """Validate activity record against schema | |
| Args: | |
| record: Dictionary containing activity data | |
| Returns: | |
| Tuple of (is_valid, error_message) | |
| """ | |
| # Check required fields | |
| missing_fields = [f for f in REQUIRED_FIELDS if f not in record or not record[f]] | |
| if missing_fields: | |
| return False, f"Missing required fields: {', '.join(missing_fields)}" | |
| # Validate status value | |
| if record['status'] not in VALID_STATUS_VALUES: | |
| return False, f"Invalid status '{record['status']}'. Must be one of: {', '.join(VALID_STATUS_VALUES)}" | |
| # Validate activity_type | |
| if record['activity_type'] not in VALID_ACTIVITY_TYPES: | |
| return False, f"Invalid activity_type '{record['activity_type']}'. Must be one of: {', '.join(VALID_ACTIVITY_TYPES)}" | |
| # Validate timestamp format (ISO 8601) | |
| try: | |
| datetime.fromisoformat(record['timestamp'].replace('Z', '+00:00')) | |
| except ValueError: | |
| return False, f"Invalid timestamp format. Expected ISO 8601, got: {record['timestamp']}" | |
| return True, None | |
| def atomic_append_activity(filepath: str, record: Dict[str, str], request_id: Optional[str] = None) -> Dict[str, any]: | |
| """Atomically append activity record to CSV file with full error handling | |
| Args: | |
| filepath: Path to activity_log.csv file | |
| record: Activity record dictionary | |
| request_id: Optional request ID (generated if not provided) | |
| Returns: | |
| Dictionary with operation status: | |
| { | |
| 'success': bool, | |
| 'request_id': str, | |
| 'timestamp': str, | |
| 'error': Optional[str], | |
| 'commit_hash': Optional[str], | |
| 'record': Dict | |
| } | |
| """ | |
| # Generate request_id if not provided | |
| if not request_id: | |
| request_id = generate_request_id() | |
| # Generate timestamp if not in record | |
| if 'timestamp' not in record or not record['timestamp']: | |
| record['timestamp'] = generate_timestamp() | |
| operation_timestamp = generate_timestamp() | |
| result = { | |
| 'success': False, | |
| 'request_id': request_id, | |
| 'timestamp': operation_timestamp, | |
| 'error': None, | |
| 'commit_hash': None, | |
| 'record': record.copy() | |
| } | |
| try: | |
| # Validate record schema | |
| is_valid, error_msg = validate_activity_record(record) | |
| if not is_valid: | |
| raise SchemaValidationError(error_msg) | |
| # Ensure file exists with header | |
| filepath = Path(filepath) | |
| if not filepath.exists(): | |
| filepath.parent.mkdir(parents=True, exist_ok=True) | |
| with open(filepath, 'w', newline='') as f: | |
| writer = csv.DictWriter(f, fieldnames=REQUIRED_FIELDS) | |
| writer.writeheader() | |
| # Atomic write using temp file and rename | |
| temp_fd, temp_path = tempfile.mkstemp(dir=filepath.parent, prefix='.activity_log_', suffix='.tmp') | |
| try: | |
| # Acquire exclusive lock for atomic operation | |
| with open(filepath, 'r') as source: | |
| fcntl.flock(source.fileno(), fcntl.LOCK_EX) | |
| try: | |
| # Copy existing content to temp file | |
| with os.fdopen(temp_fd, 'w', newline='') as temp_file: | |
| reader = csv.DictReader(source) | |
| writer = csv.DictWriter(temp_file, fieldnames=REQUIRED_FIELDS) | |
| # Write header | |
| writer.writeheader() | |
| # Copy existing rows | |
| for row in reader: | |
| writer.writerow(row) | |
| # Append new record | |
| writer.writerow(record) | |
| # Atomic rename (replaces original file) | |
| shutil.move(temp_path, filepath) | |
| result['success'] = True | |
| finally: | |
| fcntl.flock(source.fileno(), fcntl.LOCK_UN) | |
| except Exception as e: | |
| # Clean up temp file on error | |
| if os.path.exists(temp_path): | |
| os.unlink(temp_path) | |
| raise AtomicWriteError(f"Atomic write failed: {str(e)}") | |
| # Log successful operation | |
| result['commit_hash'] = _generate_commit_hash(record, request_id) | |
| _log_operation(request_id, 'ingest', 'success', record) | |
| except (SchemaValidationError, AtomicWriteError, Exception) as e: | |
| result['error'] = str(e) | |
| result['success'] = False | |
| _log_operation(request_id, 'ingest', 'error', record, error=str(e)) | |
| return result | |
| def _generate_commit_hash(record: Dict[str, str], request_id: str) -> str: | |
| """Generate pseudo-commit hash for tracking""" | |
| import hashlib | |
| data = f"{request_id}:{record['timestamp']}:{record['agent_id']}:{record['activity_type']}" | |
| return hashlib.sha256(data.encode()).hexdigest()[:16] | |
| def _log_operation(request_id: str, operation: str, status: str, record: Dict, error: Optional[str] = None): | |
| """Log operation to error/audit log file""" | |
| log_file = Path('activity_ingestion.log') | |
| timestamp = generate_timestamp() | |
| log_entry = { | |
| 'timestamp': timestamp, | |
| 'request_id': request_id, | |
| 'operation': operation, | |
| 'status': status, | |
| 'agent_id': record.get('agent_id', 'unknown'), | |
| 'activity_type': record.get('activity_type', 'unknown'), | |
| 'error': error or '' | |
| } | |
| with open(log_file, 'a') as f: | |
| f.write(f"{log_entry}\n") | |
| def test_ingestion(): | |
| """Test cases for activity ingestion validation""" | |
| print("=== Activity Ingestion Function Tests ===") | |
| print() | |
| # Test 1: Valid record insertion | |
| print("Test 1: Valid record insertion") | |
| valid_record = { | |
| 'timestamp': generate_timestamp(), | |
| 'agent_id': 'test_agent', | |
| 'activity_type': 'ingest', | |
| 'entity_id': 'test_entity_001', | |
| 'status': 'success', | |
| 'notes': 'Test record for validation', | |
| 'context': 'Unit test - valid case' | |
| } | |
| result = atomic_append_activity('activity_log.csv', valid_record) | |
| print(f" Result: {'PASS' if result['success'] else 'FAIL'}") | |
| print(f" Request ID: {result['request_id']}") | |
| print(f" Timestamp: {result['timestamp']}") | |
| if result['success']: | |
| print(f" Commit Hash: {result['commit_hash']}") | |
| else: | |
| print(f" Error: {result['error']}") | |
| print() | |
| # Test 2: Missing required field | |
| print("Test 2: Missing required field (should fail)") | |
| invalid_record = { | |
| 'timestamp': generate_timestamp(), | |
| 'agent_id': 'test_agent', | |
| 'activity_type': 'ingest', | |
| # Missing 'entity_id' | |
| 'status': 'success', | |
| 'notes': 'This should fail', | |
| 'context': 'Unit test - invalid case' | |
| } | |
| result = atomic_append_activity('activity_log.csv', invalid_record) | |
| print(f" Result: {'PASS (correctly rejected)' if not result['success'] else 'FAIL (should have rejected)'}") | |
| print(f" Error: {result['error']}") | |
| print() | |
| # Test 3: Invalid status value | |
| print("Test 3: Invalid status value (should fail)") | |
| invalid_status = { | |
| 'timestamp': generate_timestamp(), | |
| 'agent_id': 'test_agent', | |
| 'activity_type': 'ingest', | |
| 'entity_id': 'test_entity_003', | |
| 'status': 'invalid_status', # Invalid | |
| 'notes': 'This should fail', | |
| 'context': 'Unit test - invalid status' | |
| } | |
| result = atomic_append_activity('activity_log.csv', invalid_status) | |
| print(f" Result: {'PASS (correctly rejected)' if not result['success'] else 'FAIL (should have rejected)'}") | |
| print(f" Error: {result['error']}") | |
| print() | |
| # Test 4: Auto-generated timestamp | |
| print("Test 4: Auto-generated timestamp") | |
| auto_timestamp = { | |
| 'agent_id': 'test_agent', | |
| 'activity_type': 'validate', | |
| 'entity_id': 'test_entity_004', | |
| 'status': 'success', | |
| 'notes': 'Timestamp should be auto-generated', | |
| 'context': 'Unit test - auto timestamp' | |
| } | |
| result = atomic_append_activity('activity_log.csv', auto_timestamp) | |
| print(f" Result: {'PASS' if result['success'] else 'FAIL'}") | |
| print(f" Generated Timestamp: {result['record']['timestamp']}") | |
| print() | |
| # Test 5: Multiple concurrent inserts (simulate race condition safety) | |
| print("Test 5: Concurrent insert safety") | |
| for i in range(3): | |
| record = { | |
| 'timestamp': generate_timestamp(), | |
| 'agent_id': f'concurrent_agent_{i}', | |
| 'activity_type': 'ingest', | |
| 'entity_id': f'concurrent_entity_{i}', | |
| 'status': 'success', | |
| 'notes': f'Concurrent test insert {i}', | |
| 'context': 'Unit test - concurrent safety' | |
| } | |
| result = atomic_append_activity('activity_log.csv', record) | |
| print(f" Insert {i}: {'PASS' if result['success'] else 'FAIL'}") | |
| print() | |
| print("=== Test Summary ===") | |
| print("All tests completed. Check activity_log.csv and activity_ingestion.log for details.") | |
| print() | |
| if __name__ == '__main__': | |
| # Run tests when executed directly | |
| test_ingestion() |