#!/usr/bin/env python3 """ LabArchives Entry Operations Utilities for creating entries, uploading attachments, and managing notebook content. """ import argparse import sys import yaml import os from pathlib import Path from datetime import datetime def load_config(config_path='config.yaml'): """Load configuration from YAML file""" try: with open(config_path, 'r') as f: return yaml.safe_load(f) except FileNotFoundError: print(f"❌ Configuration file not found: {config_path}") print(" Run setup_config.py first to create configuration") sys.exit(1) except Exception as e: print(f"❌ Error loading configuration: {e}") sys.exit(1) def init_client(config): """Initialize LabArchives API client""" try: from labarchivespy.client import Client return Client( config['api_url'], config['access_key_id'], config['access_password'] ) except ImportError: print("❌ labarchives-py package not installed") print(" Install with: pip install git+https://github.com/mcmero/labarchives-py") sys.exit(1) def get_user_id(client, config): """Get user ID via authentication""" import xml.etree.ElementTree as ET login_params = { 'login_or_email': config['user_email'], 'password': config['user_external_password'] } try: response = client.make_call('users', 'user_access_info', params=login_params) if response.status_code == 200: uid = ET.fromstring(response.content)[0].text return uid else: print(f"❌ Authentication failed: HTTP {response.status_code}") print(f" Response: {response.content.decode('utf-8')[:200]}") sys.exit(1) except Exception as e: print(f"❌ Error during authentication: {e}") sys.exit(1) def create_entry(client, uid, nbid, title, content=None, date=None): """Create a new entry in a notebook""" print(f"\n📝 Creating entry: {title}") # Prepare parameters params = { 'uid': uid, 'nbid': nbid, 'title': title } if content: # Ensure content is HTML formatted if not content.startswith('<'): content = f'
{content}
' params['content'] = content if date: params['date'] = date try: response = client.make_call('entries', 'create_entry', params=params) if response.status_code == 200: print("✅ Entry created successfully") # Try to extract entry ID from response try: import xml.etree.ElementTree as ET root = ET.fromstring(response.content) entry_id = root.find('.//entry_id') if entry_id is not None: print(f" Entry ID: {entry_id.text}") return entry_id.text except: pass return True else: print(f"❌ Entry creation failed: HTTP {response.status_code}") print(f" Response: {response.content.decode('utf-8')[:200]}") return None except Exception as e: print(f"❌ Error creating entry: {e}") return None def create_comment(client, uid, nbid, entry_id, comment): """Add a comment to an existing entry""" print(f"\n💬 Adding comment to entry {entry_id}") params = { 'uid': uid, 'nbid': nbid, 'entry_id': entry_id, 'comment': comment } try: response = client.make_call('entries', 'create_comment', params=params) if response.status_code == 200: print("✅ Comment added successfully") return True else: print(f"❌ Comment creation failed: HTTP {response.status_code}") return False except Exception as e: print(f"❌ Error creating comment: {e}") return False def upload_attachment(client, config, uid, nbid, entry_id, file_path): """Upload a file attachment to an entry""" import requests file_path = Path(file_path) if not file_path.exists(): print(f"❌ File not found: {file_path}") return False print(f"\n📎 Uploading attachment: {file_path.name}") print(f" Size: {file_path.stat().st_size / 1024:.2f} KB") url = f"{config['api_url']}/entries/upload_attachment" try: with open(file_path, 'rb') as f: files = {'file': f} data = { 'uid': uid, 'nbid': nbid, 'entry_id': entry_id, 'filename': file_path.name, 'access_key_id': config['access_key_id'], 'access_password': config['access_password'] } response = requests.post(url, files=files, data=data) if response.status_code == 200: print("✅ Attachment uploaded successfully") return True else: print(f"❌ Upload failed: HTTP {response.status_code}") print(f" Response: {response.content.decode('utf-8')[:200]}") return False except Exception as e: print(f"❌ Error uploading attachment: {e}") return False def batch_upload(client, config, uid, nbid, entry_id, directory): """Upload all files from a directory as attachments""" directory = Path(directory) if not directory.is_dir(): print(f"❌ Directory not found: {directory}") return files = list(directory.glob('*')) files = [f for f in files if f.is_file()] if not files: print(f"❌ No files found in {directory}") return print(f"\n📦 Batch uploading {len(files)} files from {directory}") successful = 0 failed = 0 for file_path in files: if upload_attachment(client, config, uid, nbid, entry_id, file_path): successful += 1 else: failed += 1 print("\n" + "="*60) print(f"Batch upload complete: {successful} successful, {failed} failed") print("="*60) def create_entry_with_attachments(client, config, uid, nbid, title, content, attachments): """Create entry and upload multiple attachments""" # Create entry entry_id = create_entry(client, uid, nbid, title, content) if not entry_id: print("❌ Cannot upload attachments without entry ID") return False # Upload attachments for attachment_path in attachments: upload_attachment(client, config, uid, nbid, entry_id, attachment_path) return True def main(): """Main command-line interface""" parser = argparse.ArgumentParser( description='LabArchives Entry Operations', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Create simple entry python3 entry_operations.py create --nbid 12345 --title "Experiment Results" # Create entry with content python3 entry_operations.py create --nbid 12345 --title "Results" \\ --content "PCR amplification successful" # Create entry with HTML content python3 entry_operations.py create --nbid 12345 --title "Results" \\ --content "Results: