#!/usr/bin/env python3 """ Auth0 User Export Script Schedules a user export job, polls for completion, and downloads the result. """ import gzip import json import logging import shutil import subprocess import time from pathlib import Path from typing import Any import requests from src.auth0.auth import check_auth0_login # Configure logging at module level logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) class Auth0UserExporter: def __init__(self, output_dir: str = '.'): self.output_dir = Path(output_dir) self.output_dir.mkdir(exist_ok=True) def _run_auth0_command(self, *args: str) -> dict[str, Any]: """Run an auth0 CLI command and return parsed JSON response.""" cmd = ['auth0', 'api'] + list(args) logger.info(f'Running: {" ".join(cmd)}') result = subprocess.run(cmd, capture_output=True, text=True, check=True) response: dict[str, Any] = json.loads(result.stdout) return response def create_export_job(self, fields: list[dict[str, str]], format: str = 'csv') -> str: """ Create a new user export job. Args: fields: List of field specifications, e.g., [{"name": "last_login", "export_as": "Last Login"}] format: Export format (default: "csv") Returns: Job ID """ payload = {'format': format, 'fields': fields} response = self._run_auth0_command('post', 'jobs/users-exports', '--data', json.dumps(payload)) job_id: str = response['id'] logger.info(f'✓ Export job created: {job_id}') return job_id def get_job_status(self, job_id: str) -> dict[str, Any]: """Get the current status of an export job.""" return self._run_auth0_command('get', f'jobs/{job_id}') def wait_for_job_completion(self, job_id: str, poll_interval: int = 5, max_wait: int = 300) -> dict[str, Any]: """ Poll the job status until completion or timeout. Args: job_id: The job ID to monitor poll_interval: Seconds between status checks max_wait: Maximum seconds to wait before timing out Returns: Final job status response """ logger.info(f'Waiting for job {job_id} to complete...') start_time = time.time() while True: elapsed = time.time() - start_time if elapsed > max_wait: raise TimeoutError(f'Job did not complete within {max_wait} seconds') status_response = self.get_job_status(job_id) status = status_response['status'] logger.info(f' Status: {status} (elapsed: {int(elapsed)}s)') if status == 'completed': logger.info('✓ Job completed successfully') return status_response elif status == 'failed': raise RuntimeError(f'Job failed: {status_response}') time.sleep(poll_interval) def download_export(self, location_url: str, job_id: str, format: str) -> Path: """ Download the export file from the provided S3 URL. Args: location_url: Pre-signed S3 URL job_id: Job ID (used for filename) Returns: Path to the downloaded and unzipped CSV file """ logger.info('Downloading export file...') # Download the gzipped file response = requests.get(location_url, stream=True) response.raise_for_status() gz_file = self.output_dir / f'{job_id}.{format}.gz' out_file = self.output_dir / f'{job_id}.{format}' # Save gzipped file with open(gz_file, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) logger.info(f'✓ Downloaded: {gz_file}') # Unzip the file logger.info('Unzipping...') with gzip.open(gz_file, 'rb') as f_in: with open(out_file, 'wb') as f_out: shutil.copyfileobj(f_in, f_out) # Clean up the gzipped file gz_file.unlink() logger.info(f'✓ Export saved: {out_file}') return out_file def export_users(self, fields: list[dict[str, str]], format: str = 'csv', poll_interval: int = 5, max_wait: int = 300) -> Path: """ Complete user export workflow: create job, wait for completion, download. Args: fields: List of field specifications format: Export format (default: "csv") poll_interval: Seconds between status checks max_wait: Maximum seconds to wait Returns: Path to the downloaded CSV file """ # Create the export job job_id = self.create_export_job(fields, format) # Wait for completion completed_job = self.wait_for_job_completion(job_id, poll_interval=poll_interval, max_wait=max_wait) # Download the file location_url = completed_job['location'] return self.download_export(location_url, job_id, format) def main() -> None: """Example usage""" check_auth0_login() exporter = Auth0UserExporter(output_dir='./data/input') # Define the fields you want to export fields = [ {'name': 'user_id', 'export_as': 'Id'}, {'name': 'nickname', 'export_as': 'Nickname'}, {'name': 'name', 'export_as': 'Name'}, {'name': 'email', 'export_as': 'Email'}, {'name': 'email_verified', 'export_as': 'Email Verified'}, {'name': 'identities[0].connection', 'export_as': 'Connection'}, {'name': 'created_at', 'export_as': 'Created At'}, {'name': 'updated_at', 'export_as': 'Updated At'}, {'name': 'last_login', 'export_as': 'Last Login'}, {'name': 'user_metadata.orchardIdentityId', 'export_as': 'Identity Id'}, {'name': 'user_metadata.vend_contact_id', 'export_as': 'Vend Contact Id'}, ] # auth0_login() try: json_file = exporter.export_users(fields, format='json') logger.info(f'\n✓ Export complete! File saved to: {json_file}') except subprocess.CalledProcessError as e: logger.error(f'\n✗ Auth0 CLI error: {e.stderr}') except Exception as e: logger.error(f'\n✗ Error: {e}') if __name__ == '__main__': main()