fix: complete the unified script to add add the new features as streaming parallel; update the upload_all_dataset.py to use the unified script #41

Merged
aditya merged 1 commits from unified-final into stream-download-convert-upload-merge-15-refactor-rdf-converters 2025-12-26 10:42:54 +00:00
2 changed files with 562 additions and 138 deletions
+505 -53
View File
@@ -25,6 +25,10 @@ USAGE:
python convert_rdf_to_hf_dataset_unified.py input.ttl output/ \
--format turtle --description "My Dataset" --license "CC BY 4.0"
# Upload directly to HuggingFace Hub
python convert_rdf_to_hf_dataset_unified.py input.ttl \
--format turtle --push-to-hub --hub-repo-id username/dataset-name
REQUIREMENTS:
pip install rdflib datasets rich pyarrow
"""
@@ -67,6 +71,104 @@ from rich.progress import (
logger = logging.getLogger(__name__)
# =============================================================================
# MODULE-LEVEL FUNCTIONS FOR MULTIPROCESSING
# =============================================================================
def process_geonames_lines(lines):
"""Process a batch of lines from GeoNames file.
Module-level function for multiprocessing.
Args:
lines: List of lines containing one or more XML documents
Returns:
List of triple dictionaries
"""
from rdflib import Graph, Literal, URIRef
triples = []
current_xml = []
for line in lines:
# Check if this is a document boundary (URL line)
if line.startswith("http://") or line.startswith("https://"):
# Process previous document if exists
if current_xml:
xml_str = "".join(current_xml)
try:
# Parse the RDF/XML document
graph = Graph()
graph.parse(data=xml_str, format="xml")
# Extract triples
for s, p, o in graph:
triples.append(extract_triple(s, p, o))
except Exception as e:
# Skip malformed documents
logger.error(f"Error parsing XML document: {e}. Skipping document.")
pass
# Reset for next document
current_xml = []
# Skip the URL line itself
continue
# Accumulate XML content
current_xml.append(line)
# Process final document if exists
if current_xml:
xml_str = "".join(current_xml)
try:
graph = Graph()
graph.parse(data=xml_str, format="xml")
for s, p, o in graph:
triples.append(extract_triple(s, p, o))
except Exception as e:
logger.error(f"Error parsing final XML document: {e}. Skipping document.")
pass
return triples
def process_ntriples_lines(lines):
"""Process a batch of N-Triples lines.
Args:
lines: List of N-Triple lines
Returns:
List of triple dictionaries
"""
from rdflib import Graph
triples = []
for line in lines:
line = line.strip()
if not line or line.startswith("#"):
continue
try:
# Parse single N-Triple line
mini_graph = Graph()
mini_graph.parse(data=line, format="nt")
for s, p, o in mini_graph:
triples.append(extract_triple(s, p, o))
except Exception as e:
# Skip malformed lines
logger.error(f"Error parsing N-Triple line: {line}. Skipping line. Error: {e}")
continue
return triples
# =============================================================================
# DATA CLASSES - Configuration and Results
# =============================================================================
@@ -75,7 +177,7 @@ logger = logging.getLogger(__name__)
class ConversionConfig:
"""Configuration for RDF to HuggingFace conversion."""
input_path: Path
output_path: Path
output_path: Path | None
rdf_format: str = "turtle"
chunk_size: int = 10000
num_workers: int | None = None
@@ -84,6 +186,8 @@ class ConversionConfig:
test_size: float = 0.05
clean_cache: bool = False
verbose: bool = False
push_to_hub: bool = False
hub_repo_id: str | None = None
@dataclass
class ConversionResult:
@@ -196,6 +300,80 @@ class FileHandler:
logger.error(f"Error getting file size for {file_path}: {e}")
raise
def batch_file_lines(file_path: Path, batch_size: int = 1000, format: str = "geonames"):
"""Generator that yields batches of lines from file.
For GeoNames format, ensures complete XML documents are kept together.
Args:
file_path: Path to file
batch_size: Number of lines per batch
format: File format (geonames or ntriples)
Yields:
Batches of lines
"""
# Handle compressed files
if file_path.suffix == ".gz":
try:
file_obj = gzip.open(file_path, "rt", encoding="utf-8")
# Test read to check if file is valid
test_line = file_obj.readline()
if not test_line and file_path.stat().st_size > 0:
raise EOFError("Compressed file appears to be empty or corrupted")
file_obj.seek(0)
except (gzip.BadGzipFile, EOFError, OSError) as e:
console = Console()
console.print(f"[red]Error: Compressed file is corrupted or incomplete: {e}[/red]")
console.print(f"[yellow]File: {file_path}[/yellow]")
console.print("[yellow]Please re-download the dataset or use an uncompressed version[/yellow]")
raise
else:
file_obj = open(file_path, encoding="utf-8")
try:
current_batch = []
current_doc = []
if format == "geonames":
# For GeoNames, keep documents together
for line in file_obj:
# Check if this is a document boundary
if line.startswith("http://") or line.startswith("https://"):
if current_doc:
# Add completed document to batch
current_batch.extend(current_doc)
current_doc = [line]
# Yield batch if large enough
if len(current_batch) >= batch_size:
yield current_batch
current_batch = []
else:
current_doc = [line]
else:
current_doc.append(line)
# Yield final batch
if current_doc:
current_batch.extend(current_doc)
if current_batch:
yield current_batch
else: # ntriples or other line-based format
for line in file_obj:
current_batch.append(line)
if len(current_batch) >= batch_size:
yield current_batch
current_batch = []
# Yield final batch
if current_batch:
yield current_batch
finally:
file_obj.close()
class MetadataHandler:
"""Handle metadata for the HuggingFace dataset."""
@staticmethod
@@ -290,9 +468,18 @@ class ConversionStrategy(ABC):
def _save_dataset(self, dataset_dict: DatasetDict, config: ConversionConfig) -> None:
try:
config.output_path.mkdir(parents=True, exist_ok=True)
dataset_dict.save_to_disk(str(config.output_path))
self.progress.print(f"Dataset saved to {config.output_path}", "green")
if config.push_to_hub:
if not config.hub_repo_id:
raise ValueError("hub_repo_id is required when push_to_hub is True")
self.progress.print(f"Uploading to HuggingFace Hub: {config.hub_repo_id}...", "yellow")
dataset_dict.push_to_hub(config.hub_repo_id, private=False)
self.progress.print(f"✓ Successfully uploaded to {config.hub_repo_id}", "green")
else:
if not config.output_path:
raise ValueError("output_path is required when push_to_hub is False")
config.output_path.mkdir(parents=True, exist_ok=True)
dataset_dict.save_to_disk(str(config.output_path))
self.progress.print(f"Dataset saved to {config.output_path}", "green")
except Exception as e:
logger.error(f"Error saving dataset: {e}")
raise
@@ -383,6 +570,124 @@ class ConversionStrategy(ABC):
if current_chunk:
yield current_chunk
def _stream_geonames_parallel(self, config: ConversionConfig, num_workers: int) -> Iterator[list[RDFTriple]]:
"""Stream GeoNames RDF file with parallel processing."""
self.progress.print(f"[yellow]Using parallel GeoNames parser with {num_workers} workers[/yellow]")
# Create batches of lines
batches = batch_file_lines(config.input_path, batch_size=config.chunk_size, format="geonames")
# Process batches in parallel
try:
with mp.Pool(processes=num_workers) as pool:
for triples in pool.imap_unordered(process_geonames_lines, batches, chunksize=1):
if triples:
yield triples
except Exception as e:
logger.error(f"Error processing GeoNames file: {e}")
raise
def _stream_ntriples_parallel(self, config: ConversionConfig, num_workers: int) -> Iterator[list[RDFTriple]]:
"""Stream N-Triples file with parallel processing."""
self.progress.print(f"[yellow]Using parallel N-Triples parser with {num_workers} workers[/yellow]")
# Create batches of lines
batches = batch_file_lines(config.input_path, batch_size=config.chunk_size, format="ntriples")
# Process batches in parallel
try:
with mp.Pool(processes=num_workers) as pool:
for triples in pool.imap_unordered(process_ntriples_lines, batches, chunksize=1):
if triples:
yield triples
except Exception as e:
logger.error(f"Error processing N-Triples file: {e}")
raise
def _stream_turtle_chunks(self, config: ConversionConfig) -> Iterator[list[RDFTriple]]:
"""Stream Turtle file in chunks using incremental parsing."""
self.progress.print("[yellow]Streaming Turtle file (line-by-line parser)[/yellow]")
# Open file with compression support
file_obj = self.file_handler.open_file(config.input_path)
try:
current_chunk: list[str] = []
triple_count = 0
line_count = 0
# Collect prefixes first
prefix_lines: list[str] = []
in_prefixes = True
for line_bytes in file_obj:
line_count += 1
# Ensure we have a string
line = line_bytes if isinstance(line_bytes, str) else line_bytes.decode('utf-8', errors='ignore')
stripped = line.strip()
# Skip empty lines and comments
if not stripped or stripped.startswith("#"):
continue
# Collect prefix declarations
if in_prefixes and (stripped.startswith("@prefix") or stripped.startswith("@base")):
prefix_lines.append(line)
continue
elif in_prefixes:
# End of prefixes, now we're in the data
in_prefixes = False
# Parse in batches of lines ending with '.'
if stripped.endswith("."):
triple_count += 1
if triple_count >= config.chunk_size:
# Try to parse this chunk
chunk_text = "".join(prefix_lines) + "\n" + "".join(current_chunk) + "\n" + line
try:
graph = Graph()
graph.parse(data=chunk_text, format="turtle")
# Extract triples
triples = [extract_triple(s, p, o) for s, p, o in graph]
if triples:
yield triples
except Exception as e:
logger.error(f"Error parsing Turtle chunk: {e}")
# Reset for next chunk
current_chunk = []
triple_count = 0
else:
current_chunk.append(line)
else:
# Part of a multi-line statement
current_chunk.append(line)
# Log progress
if line_count % 100000 == 0:
self.progress.print(f"[dim]Processed {line_count:,} lines...[/dim]")
# Process remaining lines
if current_chunk:
chunk_text = "".join(prefix_lines) + "\n" + "".join(current_chunk)
try:
graph = Graph()
graph.parse(data=chunk_text, format="turtle")
triples = [extract_triple(s, p, o) for s, p, o in graph]
if triples:
yield triples
except Exception as e:
logger.error(f"Error parsing final Turtle chunk: {e}")
finally:
file_obj.close()
# =============================================================================
# CONCRETE STRATEGIES
# =============================================================================
@@ -402,6 +707,10 @@ class StandardStrategy(ConversionStrategy):
file_size_mb = self.file_handler.get_file_size_mb(config.input_path)
self.progress.print(f"Converting RDF file: {config.input_path}", "cyan")
self.progress.print(f"File size: {file_size_mb:.2f} MB", "dim")
if config.push_to_hub:
self.progress.print(f"Destination: {config.hub_repo_id} (HuggingFace Hub)", "dim")
else:
self.progress.print(f"Output: {config.output_path}", "dim")
self.progress.emit_progress(5)
# Choose parsing method based on rdf_format
@@ -426,7 +735,8 @@ class StandardStrategy(ConversionStrategy):
self._save_dataset(dataset_dict, config)
self.progress.emit_progress(100)
elapsed = time.time() - start_time
self._save_dataset_info_json(config, len(triples), dataset_dict, elapsed)
if config.output_path and not config.push_to_hub:
self._save_dataset_info_json(config, len(triples), dataset_dict, elapsed)
self.progress.print(f"✓ Converted {len(triples):,} triples in {elapsed:.1f}s", "bold green")
return ConversionResult(success=True, total_triples=len(triples),
processing_time_seconds=elapsed,
@@ -455,10 +765,20 @@ class StreamingStrategy(ConversionStrategy):
self.progress.print(f"Converting RDF file: {config.input_path}", "cyan")
self.progress.print(f"File size: {file_size_mb:.2f} MB", "dim")
self.progress.print(f"Chunk size: {config.chunk_size:,} triples", "dim")
if config.push_to_hub:
self.progress.print(f"Destination: {config.hub_repo_id} (HuggingFace Hub)", "dim")
else:
self.progress.print(f"Output: {config.output_path}", "dim")
self.progress.emit_progress(5)
config.output_path.mkdir(parents=True, exist_ok=True)
temp_chunks_dir = config.output_path / "temp_chunks"
temp_chunks_dir.mkdir(exist_ok=True)
# Create temp directory for chunks
if config.push_to_hub:
temp_chunks_dir = Path(tempfile.mkdtemp(prefix="rdf_chunks_"))
else:
config.output_path.mkdir(parents=True, exist_ok=True)
temp_chunks_dir = config.output_path / "temp_chunks"
temp_chunks_dir.mkdir(exist_ok=True)
total_triples = 0
chunk_count = 0
with self.progress.progress_bar("Processing RDF chunks...", total=None) as (progress, task):
@@ -474,9 +794,14 @@ class StreamingStrategy(ConversionStrategy):
self.progress.emit_progress(80)
dataset_dict = self._merge_parquet_chunks(temp_chunks_dir, config, create_splits=True)
shutil.rmtree(temp_chunks_dir)
if config.push_to_hub:
self._save_dataset(dataset_dict, config)
self.progress.emit_progress(100)
elapsed = time.time() - start_time
self._save_dataset_info_json(config, total_triples, dataset_dict, elapsed)
if config.output_path and not config.push_to_hub:
self._save_dataset_info_json(config, total_triples, dataset_dict, elapsed)
self.progress.print("✓ Converted to HuggingFace dataset", "green")
return ConversionResult(success=True, total_triples=total_triples,
processing_time_seconds=elapsed,
@@ -505,10 +830,20 @@ class StreamingTurtleStrategy(ConversionStrategy):
self.progress.print(f"Converting Turtle: {config.input_path.name}", "cyan")
self.progress.print(f"File size: {file_size_mb:.2f} MB", "dim")
self.progress.print(f"Chunk size: {config.chunk_size:,} statements", "dim")
if config.push_to_hub:
self.progress.print(f"Destination: {config.hub_repo_id} (HuggingFace Hub)", "dim")
else:
self.progress.print(f"Output: {config.output_path}", "dim")
self.progress.emit_progress(5)
config.output_path.mkdir(parents=True, exist_ok=True)
temp_chunks_dir = config.output_path / "temp_chunks"
temp_chunks_dir.mkdir(exist_ok=True)
# Create temp directory for chunks
if config.push_to_hub:
temp_chunks_dir = Path(tempfile.mkdtemp(prefix="rdf_chunks_"))
else:
config.output_path.mkdir(parents=True, exist_ok=True)
temp_chunks_dir = config.output_path / "temp_chunks"
temp_chunks_dir.mkdir(exist_ok=True)
total_triples = 0
chunk_count = 0
# Use a specialized streaming method for Turtle with prefix handling.
@@ -537,9 +872,14 @@ class StreamingTurtleStrategy(ConversionStrategy):
self.progress.emit_progress(80)
dataset_dict = self._merge_parquet_chunks(temp_chunks_dir, config)
shutil.rmtree(temp_chunks_dir)
if config.push_to_hub:
self._save_dataset(dataset_dict, config)
self.progress.emit_progress(100)
elapsed = time.time() - start_time
self._save_dataset_info_json(config, total_triples, dataset_dict, elapsed)
if config.output_path and not config.push_to_hub:
self._save_dataset_info_json(config, total_triples, dataset_dict, elapsed)
self.progress.print("✓ Converted to HuggingFace dataset", "green")
return ConversionResult(success=True, total_triples=total_triples,
processing_time_seconds=elapsed,
@@ -568,9 +908,19 @@ class SimpleStreamingStrategy(ConversionStrategy):
self.progress.print(f"Converting: {config.input_path.name}", "cyan")
self.progress.print(f"File size: {file_size_mb:.2f} MB", "dim")
self.progress.print(f"Chunk size: {config.chunk_size:,} triples", "dim")
config.output_path.mkdir(parents=True, exist_ok=True)
temp_chunks_dir = config.output_path / "temp_chunks"
temp_chunks_dir.mkdir(exist_ok=True)
if config.push_to_hub:
self.progress.print(f"Destination: {config.hub_repo_id} (HuggingFace Hub)", "dim")
else:
self.progress.print(f"Output: {config.output_path}", "dim")
# Create temp directory for chunks
if config.push_to_hub:
temp_chunks_dir = Path(tempfile.mkdtemp(prefix="rdf_chunks_"))
else:
config.output_path.mkdir(parents=True, exist_ok=True)
temp_chunks_dir = config.output_path / "temp_chunks"
temp_chunks_dir.mkdir(exist_ok=True)
total_triples = 0
chunk_count = 0
with self.progress.progress_bar("Processing simple streaming chunks...", total=None) as (progress, task):
@@ -585,8 +935,13 @@ class SimpleStreamingStrategy(ConversionStrategy):
self.progress.print(f"✓ Processed {total_triples:,} triples in {chunk_count} chunks", "green")
dataset_dict = self._merge_parquet_chunks(temp_chunks_dir, config)
shutil.rmtree(temp_chunks_dir)
if config.push_to_hub:
self._save_dataset(dataset_dict, config)
elapsed = time.time() - start_time
self._save_dataset_info_json(config, total_triples, dataset_dict, elapsed)
if config.output_path and not config.push_to_hub:
self._save_dataset_info_json(config, total_triples, dataset_dict, elapsed)
self.progress.print("✓ Converted to HuggingFace dataset", "green")
return ConversionResult(success=True, total_triples=total_triples,
processing_time_seconds=elapsed,
@@ -606,50 +961,129 @@ class ParallelStreamingStrategy(ConversionStrategy):
@property
def description(self) -> str:
return "Parallel streaming conversion using multiprocessing."
return "Parallel streaming conversion using multiprocessing with Dataset.from_generator()."
def convert(self, config: ConversionConfig) -> ConversionResult:
start_time = time.time()
num_workers = config.num_workers or mp.cpu_count()
num_workers = config.num_workers or max(1, mp.cpu_count() - 1)
try:
file_size_mb = self.file_handler.get_file_size_mb(config.input_path)
self.progress.print(f"Converting: {config.input_path.name}", "cyan")
self.progress.print(f"File size: {file_size_mb:.2f} MB", "dim")
self.progress.print(f"Chunk size: {config.chunk_size:,} lines", "dim")
self.progress.print(f"Chunk size: {config.chunk_size:,} lines per batch", "dim")
self.progress.print(f"Workers: {num_workers} CPU cores", "dim")
if config.push_to_hub:
self.progress.print(f"Destination: {config.hub_repo_id} (HuggingFace Hub)", "dim")
else:
self.progress.print(f"Output: {config.output_path}", "dim")
self.progress.emit_progress(5)
config.output_path.mkdir(parents=True, exist_ok=True)
temp_chunks_dir = config.output_path / "temp_chunks"
temp_chunks_dir.mkdir(exist_ok=True)
total_triples = 0
chunk_count = 0
# Check if this is GeoNames format
is_geonames = "geonames" in str(config.input_path).lower()
self.progress.print("\n[yellow]Creating dataset from streamed chunks...[/yellow]")
self.progress.emit_progress(10)
# Estimate total chunks based on file size (rough estimate)
estimated_chunks = max(10, int(file_size_mb * 1024 * 1024 / (config.chunk_size * 100)))
with self.progress.progress_bar("Processing parallel chunks...", total=None) as (progress, task):
# For parallel processing, choose method based on format
if config.rdf_format in ("nt", "ntriples"):
batches = list(self._stream_ntriples(config))
# Create generator function that yields individual triples
def dataset_generator():
triple_count = 0
chunk_count = 0
last_print_count = 0
# Select appropriate streaming method based on format
if is_geonames and config.rdf_format in ("xml", "application/rdf+xml"):
# Check if it's the special GeoNames format (URLs followed by XML)
with open(config.input_path, encoding="utf-8", errors="ignore") as f:
first_line = f.readline().strip()
if first_line.startswith("http://") or first_line.startswith("https://"):
chunk_iter = self._stream_geonames_parallel(config, num_workers)
else:
chunk_iter = self._stream_generic_rdf(config)
elif config.rdf_format in ("nt", "ntriples"):
chunk_iter = self._stream_ntriples_parallel(config, num_workers)
elif config.rdf_format in ("turtle", "ttl"):
chunk_iter = self._stream_turtle_chunks(config)
else:
batches = list(self._stream_generic_rdf(config))
# Use multiprocessing Pool for parallel processing the batches
with mp.Pool(processes=num_workers) as pool:
results = pool.map(lambda b: b, batches)
for batch in results:
schema = self.schema_manager.get_arrow_schema()
table = pa.Table.from_pylist(batch, schema=schema)
chunk_file = temp_chunks_dir / f"chunk-{chunk_count:05d}.parquet"
pq.write_table(table, chunk_file)
total_triples += len(batch)
chunk_count += 1
progress.update(task, advance=1)
self.progress.print(f"✓ Processed {total_triples:,} triples in {chunk_count} chunks", "green")
self.progress.emit_progress(80)
dataset_dict = self._merge_parquet_chunks(temp_chunks_dir, config)
shutil.rmtree(temp_chunks_dir)
self.progress.emit_progress(100)
# For other formats, use generic streaming
chunk_iter = self._stream_generic_rdf(config)
# Yield individual triples from chunks
for chunk in chunk_iter:
chunk_count += 1
for triple in chunk:
triple_count += 1
yield triple
if triple_count - last_print_count >= 100000:
elapsed = time.time() - start_time
rate = triple_count / elapsed if elapsed > 0 else 0
progress_pct = 10 + min(60, int((chunk_count / estimated_chunks) * 60))
self.progress.print(
f" Processing: {chunk_count:,} chunks • "
f"{triple_count:,} triples • {rate:.0f} triples/sec",
)
self.progress.emit_progress(progress_pct)
last_print_count = triple_count
# Create dataset using from_generator for true streaming
features = self.schema_manager.get_hf_features()
dataset = Dataset.from_generator(dataset_generator, features=features)
self.progress.emit_progress(75)
self.progress.print("[green]✓ Dataset generation complete[/green]")
# Get statistics after generation
total_triples = len(dataset)
# Wrap in DatasetDict
dataset_dict = DatasetDict({"data": dataset})
# Add metadata
if config.metadata:
dataset_dict = self.metadata_handler.add_metadata(dataset_dict, config.metadata)
elapsed = time.time() - start_time
self._save_dataset_info_json(config, total_triples, dataset_dict, elapsed,
extra_info={"num_workers": num_workers})
self.progress.print("✓ Converted to HuggingFace dataset", "green")
if config.push_to_hub:
self.progress.print(f"[yellow]Uploading to HuggingFace Hub: {config.hub_repo_id}...[/yellow]")
self.progress.emit_progress(95)
dataset_dict.push_to_hub(config.hub_repo_id, private=False)
self.progress.print(f"[bold green]✓ Successfully uploaded to {config.hub_repo_id}[/bold green]")
self.progress.emit_progress(100)
else:
self.progress.print("[yellow]Saving final dataset...[/yellow]")
self.progress.emit_progress(95)
config.output_path.mkdir(parents=True, exist_ok=True)
dataset_dict.save_to_disk(str(config.output_path), num_proc=num_workers)
self.progress.emit_progress(100)
# Save dataset info
info = {
"format": "parquet",
"total_triples": total_triples,
"data_size": len(dataset_dict["data"]),
"source_format": config.rdf_format,
"chunk_size": config.chunk_size,
"num_workers": num_workers,
"processing_time_seconds": round(elapsed, 2),
"triples_per_second": round(total_triples / elapsed, 2) if elapsed > 0 else 0,
"conversion_date": time.strftime("%Y-%m-%d %H:%M:%S"),
}
with open(config.output_path / "dataset_info.json", "w") as f:
json.dump(info, f, indent=2)
self.progress.print("\n[bold green]✓ Successfully converted to HuggingFace dataset[/bold green]")
self.progress.print(f"[green] Data split: {len(dataset_dict['data']):,} triples[/green]")
self.progress.print(f"[green] Processing time: {elapsed:.1f} seconds[/green]")
self.progress.print(f"[green] Speed: {total_triples / elapsed:.0f} triples/second[/green]")
if config.push_to_hub:
self.progress.print(f"[green] Repository: {config.hub_repo_id}[/green]")
else:
self.progress.print(f"[green] Location: {config.output_path}[/green]")
return ConversionResult(success=True, total_triples=total_triples,
processing_time_seconds=elapsed,
output_path=config.output_path,
@@ -725,7 +1159,7 @@ Strategies:
"""
)
parser.add_argument("input", type=Path, help="Input RDF file")
parser.add_argument("output", type=Path, help="Output directory for HuggingFace dataset")
parser.add_argument("output", nargs="?", type=Path, help="Output directory for HuggingFace dataset (optional if --push-to-hub)")
parser.add_argument("-f", "--format", default="turtle",
choices=["turtle", "nt", "ntriples", "xml", "n3", "trig", "nquads", "tsv"],
help="RDF format (default: turtle)")
@@ -747,10 +1181,20 @@ Strategies:
parser.add_argument("--citation", type=str, help="Dataset citation")
parser.add_argument("--homepage", type=str, help="Dataset homepage URL")
parser.add_argument("--license", type=str, help="Dataset license")
parser.add_argument("--push-to-hub", action="store_true",
help="Upload directly to HuggingFace Hub without saving to disk")
parser.add_argument("--hub-repo-id", type=str,
help="HuggingFace Hub repository ID (required if --push-to-hub)")
parser.add_argument("-v", "--verbose", action="store_true",
help="Verbose output including warnings")
args = parser.parse_args()
# Validate arguments
if args.push_to_hub and not args.hub_repo_id:
parser.error("--hub-repo-id is required when using --push-to-hub")
if not args.push_to_hub and not args.output:
parser.error("output directory is required when not using --push-to-hub")
log_level = logging.WARNING if args.verbose else logging.ERROR
logging.basicConfig(level=log_level, format="%(levelname)s:%(name)s:%(message)s", force=True)
logging.getLogger("rdflib").setLevel(log_level)
@@ -789,14 +1233,19 @@ Strategies:
create_train_test_split=args.create_splits,
test_size=args.test_size,
clean_cache=args.clean_cache,
verbose=args.verbose
verbose=args.verbose,
push_to_hub=args.push_to_hub,
hub_repo_id=args.hub_repo_id
)
progress.print("\n" + "="*60, "cyan")
progress.print("Unified RDF to HuggingFace Dataset Converter", "bold cyan")
progress.print("="*60, "cyan")
progress.print(f"Input: {config.input_path}", "")
progress.print(f"Output: {config.output_path}", "")
if config.push_to_hub:
progress.print(f"Destination: {config.hub_repo_id} (HuggingFace Hub)", "")
else:
progress.print(f"Output: {config.output_path}", "")
progress.print(f"Format: {config.rdf_format}", "")
progress.print(f"Strategy: {strategy.name}", "")
progress.print("", "")
@@ -808,7 +1257,10 @@ Strategies:
progress.print("="*60, "green")
progress.print(f"Total triples: {result.total_triples:,}", "green")
progress.print(f"Processing time: {result.processing_time_seconds:.1f}s", "green")
progress.print(f"Output: {result.output_path}", "green")
if config.push_to_hub:
progress.print(f"Repository: {config.hub_repo_id}", "green")
else:
progress.print(f"Output: {result.output_path}", "green")
return 0
else:
progress.print("\n" + "="*60, "red")
+57 -85
View File
@@ -731,11 +731,11 @@ def get_rdf_format(dataset_info: DatasetInfo) -> str:
# Get format mappings from config
config = get_dataset_config()
format_map = config.get("format_mappings", {})
# Default mappings for common formats
default_mappings = {
"rdf": "xml",
"rdf/xml": "xml",
"rdf/xml": "xml",
"xml": "xml",
"turtle": "turtle",
"ttl": "turtle",
@@ -745,18 +745,18 @@ def get_rdf_format(dataset_info: DatasetInfo) -> str:
"jsonld": "json-ld",
"json-ld": "json-ld"
}
format_lower = dataset_info.format.lower()
# First try config mappings, then default mappings, then fallback
mapped_format = format_map.get(format_lower)
if mapped_format:
return mapped_format
mapped_format = default_mappings.get(format_lower)
if mapped_format:
return mapped_format
# Fallback to turtle for unknown formats
return "turtle"
@@ -831,10 +831,11 @@ This dataset was prepared using the CleverErnie GISM framework:
python scripts/rdf_dataset_downloader.py {dataset_info.id} -o datasets/
# Convert to HuggingFace format
python scripts/convert_rdf_to_hf_dataset.py \\
python scripts/convert_rdf_to_hf_dataset_unified.py \\
datasets/{dataset_info.id}/[file] \\
hf_datasets/{dataset_info.id} \\
--format {get_rdf_format(dataset_info)}
--format {get_rdf_format(dataset_info)} \\
--strategy auto
# Upload to HuggingFace Hub
python scripts/upload_all_datasets.py --dataset {dataset_info.id}
@@ -849,8 +850,9 @@ python scripts/upload_all_datasets.py --dataset {dataset_info.id}
### Conversion Details
- Converted using: [CleverErnie GISM](https://github.com/cleverthis/cleverernie)
- Conversion script: `scripts/convert_rdf_to_hf_dataset.py`
- Conversion script: `scripts/convert_rdf_to_hf_dataset_unified.py`
- Dataset format: Single 'data' split with all triples
- Strategy: Auto-selected based on dataset size and format
### Maintenance
@@ -1009,85 +1011,52 @@ def convert(args: argparse.Namespace, rdf_file: Path, dataset_id: str) -> None:
console.print(f"[green]Created dataset card: {readme_path}[/green]")
else:
# Original conversion logic for other datasets
# Determine if we should use streaming converter based on dataset size
use_streaming = False
streaming_script = (
# Use unified converter which automatically selects best strategy
unified_script = (
Path(__file__).parent
/ "convert_rdf_to_hf_dataset_streaming_parallel.py"
/ "convert_rdf_to_hf_dataset_unified.py"
)
# Use streaming for medium and larger datasets, or specifically for geonames
if (
dataset_info.category in ["medium", "large", "xlarge"]
or "geonames" in dataset_id.lower()
):
use_streaming = streaming_script.exists()
if use_streaming:
console.print(
"[dim]Using parallel streaming converter for "
f"{dataset_info.category} dataset[/dim]"
)
# Unified script will auto-select best strategy based on file size and format
console.print(
"[dim]Using unified converter (auto-selects best strategy)[/dim]"
)
if args.dry_run:
script_name = (
"convert_rdf_to_hf_dataset_streaming_parallel.py"
if use_streaming
else "convert_rdf_to_hf_dataset.py"
)
console.print(
f"[dim]Would run: python scripts/{script_name} "
"{rdf_file} {hf_dataset_dir}[/dim]"
f"[dim]Would run: python scripts/convert_rdf_to_hf_dataset_unified.py "
"{rdf_file} {hf_dataset_dir} --strategy auto[/dim]"
)
else:
# Choose appropriate converter script
if use_streaming:
# Use streaming converter with the appropriate chunk size
# based on dataset size. Larger chunk sizes are more memory
# efficient but still reasonable
chunk_size = 50000 # Default for streaming
if dataset_info.size_gb > 10:
chunk_size = 100000 # Larger chunks for very large datasets
elif dataset_info.size_gb < 1:
chunk_size = 10000 # Smaller chunks for small datasets
# Use unified converter with auto-strategy selection
# Adjust chunk size based on dataset size
chunk_size = 50000 # Default
if dataset_info.size_gb > 10:
chunk_size = 100000 # Larger chunks for very large datasets
elif dataset_info.size_gb < 1:
chunk_size = 10000 # Smaller chunks for small datasets
result = subprocess.run(
[
sys.executable,
str(streaming_script),
str(rdf_file),
str(hf_dataset_dir),
"--format",
get_rdf_format(dataset_info),
"--chunk-size",
str(chunk_size),
"--description",
dataset_info.description,
"--homepage",
dataset_info.url,
"--license",
dataset_info.license if dataset_info.license else "Unknown",
],
capture_output=False,
)
else:
# Use original converter for small datasets
result = subprocess.run(
[
sys.executable,
str(Path(__file__).parent / "convert_rdf_to_hf_dataset.py"),
str(rdf_file),
str(hf_dataset_dir),
"--format",
get_rdf_format(dataset_info),
"--description",
dataset_info.description,
"--homepage",
dataset_info.url,
"--license",
dataset_info.license if dataset_info.license else "Unknown",
],
capture_output=False,
)
result = subprocess.run(
[
sys.executable,
str(unified_script),
str(rdf_file),
str(hf_dataset_dir),
"--format",
get_rdf_format(dataset_info),
"--strategy",
"auto", # Let the unified script auto-select best strategy
"--chunk-size",
str(chunk_size),
"--description",
dataset_info.description,
"--homepage",
dataset_info.url,
"--license",
dataset_info.license if dataset_info.license else "Unknown",
],
capture_output=False,
)
if result.returncode != 0:
console.print(
@@ -1290,13 +1259,16 @@ def main() -> int:
print(f"`download` took {one_dataset_download_duration:.2f} seconds:")
# Decompress
console.print(f"[bold cyan]Decompressing {dataset_id}...[/bold cyan]")
if args.dry_run:
console.print(f"[dim][DRY RUN] Would decompress {dataset_id}...[/dim]")
else:
console.print(f"[bold cyan]Decompressing {dataset_id}...[/bold cyan]")
assert rdf_file is not None
result = decompress(rdf_file)
if not result:
console.print("[red]Decompression failed. Aborting.[/red]")
return 1
assert rdf_file is not None
result = decompress(rdf_file)
if not result:
console.print("[red]Decompression failed. Aborting.[/red]")
return 1
one_dataset_decompress_time = time.monotonic()
one_dataset_decompress_duration = one_dataset_decompress_time - one_dataset_download_time