2029 lines
90 KiB
Python
Executable File
2029 lines
90 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Download, convert, and upload all RDF datasets to HuggingFace Hub.
|
|
|
|
This script orchestrates the complete dataset preparation pipeline for RDF
|
|
knowledge graphs, automating download, conversion, and upload to HuggingFace Hub.
|
|
Part of the CleverErnie dataset preparation infrastructure.
|
|
|
|
PARALLEL PROCESSING:
|
|
The script supports parallel processing of multiple datasets to speed up
|
|
bulk operations. When using --parallel/-p with a value > 1, datasets are
|
|
processed concurrently with rich progress display showing all datasets
|
|
being processed simultaneously.
|
|
|
|
MEMORY OPTIMIZATION:
|
|
The script automatically uses a streaming converter for medium and larger datasets
|
|
(or datasets identified as memory-intensive like GeoNames) to prevent excessive
|
|
memory usage. The streaming converter:
|
|
- Processes RDF files in chunks instead of loading entirely into memory
|
|
- Uses parallel processing for suitable formats
|
|
- Writes data incrementally to Parquet files
|
|
- Typical memory usage: 2-4GB instead of 100GB+ for large datasets
|
|
|
|
USAGE:
|
|
# Process a single dataset
|
|
python scripts/upload_all_datasets.py --dataset <dataset_id>
|
|
|
|
# Process multiple specific datasets in parallel
|
|
python scripts/upload_all_datasets.py --dataset wordnet --dataset yago-4.5 --parallel 2
|
|
|
|
# Process all datasets in a category with 4 parallel workers
|
|
python scripts/upload_all_datasets.py --category small --parallel 4
|
|
|
|
# Dry run (preview without executing)
|
|
python scripts/upload_all_datasets.py --category small --dry-run
|
|
|
|
# List datasets that would be processed
|
|
python scripts/upload_all_datasets.py --category small --list
|
|
|
|
# Skip certain steps (use existing downloads/conversions)
|
|
python scripts/upload_all_datasets.py --dataset wordnet --skip-download
|
|
python scripts/upload_all_datasets.py --dataset wordnet --skip-convert
|
|
python scripts/upload_all_datasets.py --dataset wordnet --skip-upload
|
|
|
|
# Remove mode: delete downloads after processing (save disk space)
|
|
python scripts/upload_all_datasets.py --category small --rm
|
|
|
|
ARGUMENTS:
|
|
-d, --dataset ID Specific dataset ID(s) to process (repeatable)
|
|
-c, --category CAT Process datasets by category: small, medium, large, xlarge, all
|
|
-p, --parallel N Number of datasets to process in parallel (default: 1)
|
|
--base-dir DIR Base directory for operations (default: ./dataset_processing)
|
|
--skip-download Skip download step (use existing downloads)
|
|
--skip-convert Skip conversion step (use existing conversions)
|
|
--skip-upload Skip upload step (only download and convert)
|
|
--dry-run Show what would be done without executing
|
|
--list List datasets that would be processed
|
|
--rm Remove mode: wipe downloads at start, delete each after processing
|
|
|
|
WORKFLOW:
|
|
For each dataset, the script executes three phases:
|
|
|
|
Step 1: Download
|
|
- Downloads raw RDF from public source URL
|
|
- Handles compressed formats (.gz, .bz2, .zip)
|
|
- Extracts to usable RDF format
|
|
- Progress bars with download speed
|
|
|
|
Step 2: Convert
|
|
- Parses RDF file (Turtle, N-Triples, RDF/XML, etc.)
|
|
- Converts to HuggingFace dataset format
|
|
- Preserves all semantic information (lossless)
|
|
- Creates dataset card (README.md) with metadata
|
|
|
|
Step 3: Upload
|
|
- Uploads to HuggingFace Hub under CleverThis organization
|
|
- Creates/updates dataset repository
|
|
- Uploads dataset card with comprehensive documentation
|
|
- Makes dataset publicly accessible
|
|
|
|
DATASET CATEGORIES:
|
|
small < 1 GB (WordNet, Schema.org, Gene Ontology)
|
|
medium 1-10 GB (YAGO 4.5, DisGeNET, GeoNames)
|
|
large 10-100 GB (DBpedia, UniProt, DBLP)
|
|
xlarge > 100 GB (Wikidata, BabelNet, Bio2RDF)
|
|
|
|
DIRECTORY STRUCTURE:
|
|
dataset_processing/
|
|
├── downloads/ # Raw RDF downloads
|
|
│ ├── wordnet/
|
|
│ │ ├── english-wordnet-2024.ttl.gz
|
|
│ │ └── english-wordnet-2024.ttl
|
|
│ └── yago-4.5/
|
|
│ └── yago-4.5.0.2-tiny.ttl
|
|
│
|
|
└── hf_datasets/ # Converted HuggingFace datasets
|
|
├── wordnet/
|
|
│ ├── data/
|
|
│ ├── dataset_info.json
|
|
│ └── README.md
|
|
└── yago-4.5/
|
|
├── data/
|
|
├── dataset_info.json
|
|
└── README.md
|
|
|
|
REMOVE MODE (--rm):
|
|
When --rm flag is set:
|
|
1. Start: Removes all previous downloads from dataset_processing/downloads/
|
|
2. Per dataset: After successful processing, removes downloaded RDF files
|
|
3. Result: Only converted HuggingFace datasets remain on disk
|
|
|
|
Use cases:
|
|
- Processing large datasets with limited disk space
|
|
- Automated pipelines where raw downloads aren't needed
|
|
- Batch processing where intermediate files can be discarded
|
|
|
|
EXAMPLES:
|
|
# Test with small dataset
|
|
python scripts/upload_all_datasets.py --dataset wordnet
|
|
|
|
# Process all small datasets with cleanup
|
|
python scripts/upload_all_datasets.py --category small --rm
|
|
|
|
# Preview what would happen
|
|
python scripts/upload_all_datasets.py --category medium --dry-run
|
|
|
|
# Re-upload with updated metadata (skip download/convert)
|
|
python scripts/upload_all_datasets.py --dataset wordnet --skip-download --skip-convert
|
|
|
|
# Process specific datasets from different categories
|
|
python scripts/upload_all_datasets.py --dataset wordnet --dataset yago-4.5 --dataset dbpedia-core
|
|
|
|
AUTHENTICATION:
|
|
To upload to HuggingFace Hub, authentication is required:
|
|
|
|
# Option 1: Login via CLI (one-time)
|
|
huggingface-cli login
|
|
|
|
# Option 2: Set environment variable
|
|
export HF_TOKEN="your_token_here"
|
|
|
|
Get a token from: https://huggingface.co/settings/tokens
|
|
Required permissions: Write access to create/update datasets
|
|
|
|
REQUIREMENTS:
|
|
pip install rdflib datasets huggingface-hub rich httpx pyarrow
|
|
|
|
SYSTEM REQUIREMENTS:
|
|
Small datasets: 4 GB RAM, 5 GB disk, 5-15 minutes
|
|
Medium datasets: 16 GB RAM, 30 GB disk, 30-90 minutes
|
|
Large datasets: 32 GB RAM, 300 GB disk, 2-6 hours
|
|
XLarge datasets: 64 GB RAM, 1+ TB disk, 6-24+ hours
|
|
|
|
NOTES:
|
|
- Datasets marked as unavailable will be automatically skipped
|
|
- Processing summary provided at end with success/failure status
|
|
- Upload resumes if interrupted (HuggingFace Hub handles this)
|
|
- Large datasets may take hours; monitor progress with -v flag
|
|
|
|
RELATED:
|
|
- rdf_dataset_downloader.py: Download raw RDF datasets (Step 1)
|
|
- convert_rdf_to_hf_dataset.py: Convert RDF to HuggingFace (Step 2)
|
|
- dataset_registry.py: Registry of available datasets with metadata
|
|
|
|
For detailed documentation, see: docs/rdf_dataset_scripts.md
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import bz2
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, Tuple
|
|
|
|
from rich.console import Console
|
|
from rich.live import Live
|
|
from rich.progress import (
|
|
BarColumn,
|
|
MofNCompleteColumn,
|
|
Progress,
|
|
SpinnerColumn,
|
|
TextColumn,
|
|
TimeElapsedColumn,
|
|
)
|
|
from rich.table import Table
|
|
|
|
# Import our modules
|
|
from dataset_registry import DATASET_REGISTRY, DatasetInfo, get_dataset_config
|
|
|
|
console = Console()
|
|
|
|
|
|
def get_valid_hf_license(license_string: str | None) -> str:
|
|
"""Map dataset license to valid HuggingFace license identifier.
|
|
|
|
Args:
|
|
license_string: License string from dataset registry
|
|
|
|
Returns:
|
|
Valid HuggingFace license identifier
|
|
"""
|
|
if not license_string:
|
|
return "other"
|
|
|
|
# Get license mappings from config
|
|
config = get_dataset_config()
|
|
license_map = config.get("license_mappings", {})
|
|
|
|
# Convert keys to lowercase for comparison
|
|
license_map_lower = {k.lower(): v for k, v in license_map.items()}
|
|
|
|
normalized = license_string.lower().strip()
|
|
return license_map_lower.get(normalized, "other")
|
|
|
|
|
|
def get_rdf_format(dataset_info: DatasetInfo) -> str:
|
|
"""Get RDF format string for rdflib.
|
|
|
|
Args:
|
|
dataset_info: Dataset information
|
|
|
|
Returns:
|
|
Format string for rdflib
|
|
"""
|
|
# Get format mappings from config
|
|
config = get_dataset_config()
|
|
format_map = config.get("format_mappings", {})
|
|
return format_map.get(dataset_info.format.lower(), "turtle")
|
|
|
|
|
|
def get_rdf_format_documentation(dataset_info: DatasetInfo) -> str:
|
|
"""Get comprehensive RDF format documentation for README.
|
|
|
|
Args:
|
|
dataset_info: Dataset information
|
|
|
|
Returns:
|
|
Markdown documentation for RDF format
|
|
"""
|
|
return f"""
|
|
## Dataset Format: Lossless RDF Representation
|
|
|
|
This dataset uses a **standard lossless format** for representing RDF (Resource Description Framework)
|
|
data in HuggingFace Datasets. All semantic information from the original RDF knowledge graph is preserved,
|
|
enabling perfect round-trip conversion between RDF and HuggingFace formats.
|
|
|
|
### Schema
|
|
|
|
Each RDF triple is represented as a row with **6 fields**:
|
|
|
|
| Field | Type | Description | Example |
|
|
|-------|------|-------------|---------|
|
|
| `subject` | string | Subject of the triple (URI or blank node) | `"http://schema.org/Person"` |
|
|
| `predicate` | string | Predicate URI | `"http://www.w3.org/1999/02/22-rdf-syntax-ns#type"` |
|
|
| `object` | string | Object of the triple | `"John Doe"` or `"http://schema.org/Thing"` |
|
|
| `object_type` | string | Type of object: `"uri"`, `"literal"`, or `"blank_node"` | `"literal"` |
|
|
| `object_datatype` | string | XSD datatype URI (for typed literals) | `"http://www.w3.org/2001/XMLSchema#integer"` |
|
|
| `object_language` | string | Language tag (for language-tagged literals) | `"en"` |
|
|
|
|
### Example: RDF Triple Representation
|
|
|
|
**Original RDF (Turtle)**:
|
|
```turtle
|
|
<http://example.org/John> <http://schema.org/name> "John Doe"@en .
|
|
```
|
|
|
|
**HuggingFace Dataset Row**:
|
|
```python
|
|
{{
|
|
"subject": "http://example.org/John",
|
|
"predicate": "http://schema.org/name",
|
|
"object": "John Doe",
|
|
"object_type": "literal",
|
|
"object_datatype": None,
|
|
"object_language": "en"
|
|
}}
|
|
```
|
|
|
|
### Loading the Dataset
|
|
|
|
```python
|
|
from datasets import load_dataset
|
|
|
|
# Load the dataset
|
|
dataset = load_dataset("CleverThis/{dataset_info.id}")
|
|
|
|
# Access the data
|
|
data = dataset["data"]
|
|
|
|
# Iterate over triples
|
|
for row in data:
|
|
subject = row["subject"]
|
|
predicate = row["predicate"]
|
|
obj = row["object"]
|
|
obj_type = row["object_type"]
|
|
|
|
print(f"Triple: ({{subject}}, {{predicate}}, {{obj}})")
|
|
print(f" Object type: {{obj_type}}")
|
|
if row["object_language"]:
|
|
print(f" Language: {{row['object_language']}}")
|
|
if row["object_datatype"]:
|
|
print(f" Datatype: {{row['object_datatype']}}")
|
|
```
|
|
|
|
### Converting Back to RDF
|
|
|
|
The dataset can be converted back to any RDF format (Turtle, N-Triples, RDF/XML, etc.) with **zero information loss**:
|
|
|
|
```python
|
|
from datasets import load_dataset
|
|
from rdflib import Graph, URIRef, Literal, BNode
|
|
|
|
def convert_to_rdf(dataset_name, output_file="output.ttl", split="data"):
|
|
\"\"\"Convert HuggingFace dataset back to RDF Turtle format.\"\"\"
|
|
# Load dataset
|
|
dataset = load_dataset(dataset_name)
|
|
|
|
# Create RDF graph
|
|
graph = Graph()
|
|
|
|
# Convert each row to RDF triple
|
|
for row in dataset[split]:
|
|
# Subject
|
|
if row["subject"].startswith("_:"):
|
|
subject = BNode(row["subject"][2:])
|
|
else:
|
|
subject = URIRef(row["subject"])
|
|
|
|
# Predicate (always URI)
|
|
predicate = URIRef(row["predicate"])
|
|
|
|
# Object (depends on object_type)
|
|
if row["object_type"] == "uri":
|
|
obj = URIRef(row["object"])
|
|
elif row["object_type"] == "blank_node":
|
|
obj = BNode(row["object"][2:])
|
|
elif row["object_type"] == "literal":
|
|
if row["object_datatype"]:
|
|
obj = Literal(row["object"], datatype=URIRef(row["object_datatype"]))
|
|
elif row["object_language"]:
|
|
obj = Literal(row["object"], lang=row["object_language"])
|
|
else:
|
|
obj = Literal(row["object"])
|
|
|
|
graph.add((subject, predicate, obj))
|
|
|
|
# Serialize to Turtle (or any RDF format)
|
|
graph.serialize(output_file, format="turtle")
|
|
print(f"Exported {{len(graph)}} triples to {{output_file}}")
|
|
return graph
|
|
|
|
# Usage
|
|
graph = convert_to_rdf("CleverThis/{dataset_info.id}", "reconstructed.ttl")
|
|
```
|
|
|
|
### Information Preservation Guarantee
|
|
|
|
This format preserves **100% of RDF information**:
|
|
|
|
- ✅ **URIs**: Exact string representation preserved
|
|
- ✅ **Literals**: Full text content preserved
|
|
- ✅ **Datatypes**: XSD and custom datatypes preserved (e.g., `xsd:integer`, `xsd:dateTime`)
|
|
- ✅ **Language Tags**: BCP 47 language tags preserved (e.g., `@en`, `@fr`, `@ja`)
|
|
- ✅ **Blank Nodes**: Node structure preserved (identifiers may change but graph isomorphism maintained)
|
|
|
|
**Round-trip guarantee**: Original RDF → HuggingFace → Reconstructed RDF produces **semantically identical** graphs.
|
|
|
|
### Querying the Dataset
|
|
|
|
You can filter and query the dataset like any HuggingFace dataset:
|
|
|
|
```python
|
|
from datasets import load_dataset
|
|
|
|
dataset = load_dataset("CleverThis/{dataset_info.id}")
|
|
|
|
# Find all triples with English literals
|
|
english_literals = dataset["data"].filter(
|
|
lambda x: x["object_type"] == "literal" and x["object_language"] == "en"
|
|
)
|
|
print(f"Found {{len(english_literals)}} English literals")
|
|
|
|
# Find all rdf:type statements
|
|
type_statements = dataset["data"].filter(
|
|
lambda x: "rdf-syntax-ns#type" in x["predicate"]
|
|
)
|
|
print(f"Found {{len(type_statements)}} type statements")
|
|
|
|
# Convert to Pandas for analysis
|
|
import pandas as pd
|
|
df = dataset["data"].to_pandas()
|
|
|
|
# Analyze predicate distribution
|
|
print(df["predicate"].value_counts())
|
|
```
|
|
|
|
### Dataset Format
|
|
|
|
The dataset contains all triples in a single **data** split, suitable for machine learning tasks such as:
|
|
|
|
- Knowledge graph completion
|
|
- Link prediction
|
|
- Entity embedding
|
|
- Relation extraction
|
|
- Graph neural networks
|
|
|
|
### Format Specification
|
|
|
|
For complete technical documentation of the RDF-to-HuggingFace format, see:
|
|
|
|
📖 [RDF to HuggingFace Format Specification](https://github.com/CleverThis/cleverernie/blob/master/docs/rdf_huggingface_format_specification.md)
|
|
|
|
The specification includes:
|
|
- Detailed schema definition
|
|
- All RDF node type mappings
|
|
- Performance benchmarks
|
|
- Edge cases and limitations
|
|
- Complete code examples
|
|
|
|
### Conversion Metadata
|
|
|
|
- **Source Format**: {dataset_info.format}
|
|
- **Original Size**: {dataset_info.size_gb} GB
|
|
- **Conversion Tool**: [CleverErnie RDF Pipeline](https://github.com/CleverThis/cleverernie)
|
|
- **Format Version**: 1.0
|
|
- **Conversion Date**: {datetime.now().strftime("%Y-%m-%d")}
|
|
"""
|
|
|
|
|
|
def create_dataset_card(dataset_info: DatasetInfo) -> str:
|
|
"""Create README.md content for the dataset card.
|
|
|
|
Args:
|
|
dataset_info: Dataset information
|
|
|
|
Returns:
|
|
Markdown content for dataset card
|
|
"""
|
|
return f"""---
|
|
license: {get_valid_hf_license(dataset_info.license)}
|
|
task_categories:
|
|
- text-generation
|
|
- feature-extraction
|
|
language:
|
|
- en
|
|
tags:
|
|
- rdf
|
|
- knowledge-graph
|
|
- semantic-web
|
|
- triples
|
|
size_categories:
|
|
- {get_size_category(dataset_info)}
|
|
---
|
|
|
|
# {dataset_info.name}
|
|
|
|
## Dataset Description
|
|
|
|
{dataset_info.description}
|
|
|
|
**Original Source:** {dataset_info.url}
|
|
|
|
### Dataset Summary
|
|
|
|
This dataset contains RDF triples from {dataset_info.name} converted to HuggingFace dataset format
|
|
for easy use in machine learning pipelines.
|
|
|
|
- **Format:** Originally {dataset_info.format}, converted to HuggingFace Dataset
|
|
- **Size:** {dataset_info.size_gb} GB (extracted)
|
|
- **Entities:** {dataset_info.entities if dataset_info.entities else "N/A"}
|
|
- **Triples:** {dataset_info.triples if dataset_info.triples else "N/A"}
|
|
- **Original License:** {dataset_info.license if dataset_info.license else "See original source"}
|
|
|
|
### Recommended Use
|
|
|
|
{dataset_info.recommended_for}
|
|
|
|
{f"### Notes\\n\\n{dataset_info.notes}" if dataset_info.notes else ""}
|
|
|
|
{get_rdf_format_documentation(dataset_info)}
|
|
|
|
## Citation
|
|
|
|
If you use this dataset, please cite the original source:
|
|
|
|
**Original Dataset:** {dataset_info.name}
|
|
**URL:** {dataset_info.url}
|
|
**License:** {dataset_info.license if dataset_info.license else "See original source"}
|
|
|
|
## Dataset Preparation
|
|
|
|
This dataset was prepared using the CleverErnie GISM framework:
|
|
|
|
```bash
|
|
# Download original dataset
|
|
python scripts/rdf_dataset_downloader.py {dataset_info.id} -o datasets/
|
|
|
|
# Convert to HuggingFace format
|
|
python scripts/convert_rdf_to_hf_dataset.py \\
|
|
datasets/{dataset_info.id}/[file] \\
|
|
hf_datasets/{dataset_info.id} \\
|
|
--format {get_rdf_format(dataset_info)}
|
|
|
|
# Upload to HuggingFace Hub
|
|
python scripts/upload_all_datasets.py --dataset {dataset_info.id}
|
|
```
|
|
|
|
## Additional Information
|
|
|
|
### Original Source
|
|
|
|
{dataset_info.url}
|
|
|
|
### Conversion Details
|
|
|
|
- Converted using: [CleverErnie GISM](https://github.com/cleverthis/cleverernie)
|
|
- Conversion script: `scripts/convert_rdf_to_hf_dataset.py`
|
|
- Dataset format: Single 'data' split with all triples
|
|
|
|
### Maintenance
|
|
|
|
This dataset is maintained by the CleverThis organization.
|
|
"""
|
|
|
|
|
|
def get_size_category(dataset_info: DatasetInfo) -> str:
|
|
"""Get HuggingFace size category.
|
|
|
|
Args:
|
|
dataset_info: Dataset information
|
|
|
|
Returns:
|
|
Size category string
|
|
"""
|
|
if dataset_info.size_gb < 0.01:
|
|
return "n<1K"
|
|
elif dataset_info.size_gb < 1:
|
|
return "1K<n<10K"
|
|
elif dataset_info.size_gb < 10:
|
|
return "10K<n<100K"
|
|
elif dataset_info.size_gb < 100:
|
|
return "100K<n<1M"
|
|
elif dataset_info.size_gb < 1000:
|
|
return "1M<n<10M"
|
|
else:
|
|
return "10M<n<100M"
|
|
|
|
|
|
def process_dataset(
|
|
dataset_id: str,
|
|
base_dir: Path,
|
|
skip_download: bool = False,
|
|
skip_convert: bool = False,
|
|
skip_upload: bool = False,
|
|
dry_run: bool = False,
|
|
remove_after: bool = False,
|
|
) -> bool:
|
|
"""Process a single dataset: download, convert, upload.
|
|
|
|
Args:
|
|
dataset_id: Dataset identifier
|
|
base_dir: Base directory for operations
|
|
skip_download: Skip download step
|
|
skip_convert: Skip conversion step
|
|
skip_upload: Skip upload step
|
|
dry_run: Show what would be done without doing it
|
|
remove_after: Remove downloaded dataset after processing
|
|
|
|
Returns:
|
|
True if successful, False otherwise
|
|
"""
|
|
dataset_info = DATASET_REGISTRY.get(dataset_id)
|
|
if not dataset_info:
|
|
console.print(f"[red]Dataset '{dataset_id}' not found[/red]")
|
|
return False
|
|
|
|
# Check if dataset is available
|
|
if not dataset_info.available:
|
|
console.print(f"\n[bold yellow]⊘ Skipping '{dataset_info.name}'[/bold yellow]")
|
|
console.print("[yellow]Status:[/yellow] Dataset currently unavailable")
|
|
console.print(f"[yellow]Reason:[/yellow] {dataset_info.notes}")
|
|
console.print(f"[dim]URL: {dataset_info.url}[/dim]")
|
|
return None # Return None to indicate skipped (not failed)
|
|
|
|
console.print(f"\n[bold cyan]{'[DRY RUN] ' if dry_run else ''}Processing: {dataset_info.name}[/bold cyan]")
|
|
|
|
# Setup directories
|
|
download_dir = base_dir / "downloads" / dataset_id
|
|
hf_dataset_dir = base_dir / "hf_datasets" / dataset_id
|
|
download_dir.mkdir(parents=True, exist_ok=True)
|
|
hf_dataset_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Step 1: Download
|
|
if not skip_download:
|
|
console.print("\n[yellow]Step 1: Downloading raw RDF dataset...[/yellow]")
|
|
if dry_run:
|
|
console.print(
|
|
f"[dim]Would run: python scripts/rdf_dataset_downloader.py {dataset_id} -o {download_dir}[/dim]"
|
|
)
|
|
else:
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(Path(__file__).parent / "rdf_dataset_downloader.py"),
|
|
dataset_id,
|
|
"-o",
|
|
str(download_dir),
|
|
],
|
|
capture_output=False,
|
|
)
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Download failed for {dataset_id}[/red]")
|
|
return False
|
|
else:
|
|
console.print("[dim]Skipping download[/dim]")
|
|
|
|
# Find the downloaded file (only if we need it for conversion)
|
|
rdf_file = None
|
|
if not skip_convert:
|
|
# Special handling for FB15k-237 and similar TSV datasets
|
|
if dataset_info.format == "tsv" and dataset_id == "fb15k-237":
|
|
# For FB15k-237, look for train.txt in the Release directory
|
|
train_files = list(download_dir.rglob("train.txt"))
|
|
if train_files:
|
|
rdf_file = train_files[0]
|
|
console.print(f"[green]Found FB15k-237 train file: {rdf_file}[/green]")
|
|
else:
|
|
console.print(f"[red]No train.txt file found for FB15k-237 in {download_dir}[/red]")
|
|
return False
|
|
else:
|
|
# Original file finding logic for other datasets
|
|
rdf_files = (
|
|
list(download_dir.rglob("*.ttl"))
|
|
+ list(download_dir.rglob("*.nt"))
|
|
+ list(download_dir.rglob("*.rdf"))
|
|
+ list(download_dir.rglob("*.owl"))
|
|
+ list(download_dir.rglob("*.n3"))
|
|
+ list(download_dir.rglob("*.trig"))
|
|
+ list(download_dir.rglob("*.nq"))
|
|
+ list(download_dir.rglob("*.xml"))
|
|
# Also look for compressed versions
|
|
+ list(download_dir.rglob("*.ttl.bz2"))
|
|
+ list(download_dir.rglob("*.ttl.gz"))
|
|
+ list(download_dir.rglob("*.nt.bz2"))
|
|
+ list(download_dir.rglob("*.nt.gz"))
|
|
+ list(download_dir.rglob("*.rdf.bz2"))
|
|
+ list(download_dir.rglob("*.rdf.gz"))
|
|
)
|
|
|
|
# Special handling for geonames - it's XML format but stored as .txt
|
|
if dataset_id == "geonames" and dataset_info.format == "xml":
|
|
rdf_files += list(download_dir.rglob("*.txt"))
|
|
|
|
# For TSV datasets (other than FB15k-237), look for .txt or .tsv files
|
|
if dataset_info.format == "tsv":
|
|
rdf_files += list(download_dir.rglob("*.txt")) + list(download_dir.rglob("*.tsv"))
|
|
|
|
# For CSV datasets (like ConceptNet), look for .csv files
|
|
if dataset_info.format == "csv":
|
|
rdf_files += list(download_dir.rglob("*.csv")) + list(download_dir.rglob("*.csv.gz"))
|
|
|
|
# Check if there are unextracted compressed files
|
|
if not rdf_files:
|
|
# Check for ZIP files
|
|
zip_files = list(download_dir.rglob("*.zip"))
|
|
if zip_files:
|
|
console.print(f"[yellow]Found {len(zip_files)} ZIP file(s), attempting extraction...[/yellow]")
|
|
for zip_file in zip_files:
|
|
try:
|
|
import zipfile
|
|
|
|
# Check if it's a valid ZIP file
|
|
if zipfile.is_zipfile(zip_file):
|
|
extract_dir = zip_file.parent / zip_file.stem
|
|
extract_dir.mkdir(parents=True, exist_ok=True)
|
|
with zipfile.ZipFile(zip_file, "r") as zip_ref:
|
|
console.print(f"[cyan]Extracting {zip_file.name} to {extract_dir}...[/cyan]")
|
|
zip_ref.extractall(extract_dir)
|
|
console.print(
|
|
f"[green]Successfully extracted {len(zip_ref.namelist())} files[/green]"
|
|
)
|
|
else:
|
|
console.print(f"[red]Invalid or incomplete ZIP file: {zip_file}[/red]")
|
|
console.print(
|
|
f"[yellow]File size: {zip_file.stat().st_size / (1024**3):.2f} GB[/yellow]"
|
|
)
|
|
if (
|
|
dataset_info.compressed_size_gb
|
|
and zip_file.stat().st_size / (1024**3) < dataset_info.compressed_size_gb * 0.9
|
|
):
|
|
console.print(
|
|
f"[red]File appears incomplete. Expected ~{dataset_info.compressed_size_gb} GB[/red]"
|
|
)
|
|
console.print(
|
|
f"[yellow]Please re-download using: python scripts/rdf_dataset_downloader.py {dataset_id} --force[/yellow]"
|
|
)
|
|
return False
|
|
except Exception as e:
|
|
console.print(f"[red]Failed to extract {zip_file}: {e}[/red]")
|
|
if "end-of-stream marker" in str(e).lower() or "compressed file ended" in str(e).lower():
|
|
console.print(f"[red]The compressed file is incomplete or corrupted.[/red]")
|
|
console.print(
|
|
f"[yellow]File size: {zip_file.stat().st_size / (1024**3):.3f} GB[/yellow]"
|
|
)
|
|
if dataset_info.compressed_size_gb:
|
|
console.print(
|
|
f"[yellow]Expected size: ~{dataset_info.compressed_size_gb:.2f} GB[/yellow]"
|
|
)
|
|
console.print(
|
|
f"[yellow]Please re-download using: python scripts/rdf_dataset_downloader.py {dataset_id} --force[/yellow]"
|
|
)
|
|
return False
|
|
|
|
# Check for BZ2 files that haven't been extracted
|
|
bz2_files = list(download_dir.rglob("*.bz2"))
|
|
if bz2_files:
|
|
console.print(f"[yellow]Found {len(bz2_files)} BZ2 file(s), checking integrity...[/yellow]")
|
|
for bz2_file in bz2_files:
|
|
# First check file size if we have expected size
|
|
file_size_gb = bz2_file.stat().st_size / (1024**3)
|
|
if dataset_info.compressed_size_gb:
|
|
expected_gb = dataset_info.compressed_size_gb
|
|
if file_size_gb < expected_gb * 0.9:
|
|
console.print(f"[red]Incomplete BZ2 file: {bz2_file.name}[/red]")
|
|
console.print(
|
|
f"[yellow]File size: {file_size_gb:.3f} GB, Expected: ~{expected_gb:.2f} GB[/yellow]"
|
|
)
|
|
console.print(
|
|
f"[red]File is only {(file_size_gb / expected_gb * 100):.1f}% of expected size[/red]"
|
|
)
|
|
console.print(f"[yellow]Removing incomplete file and re-downloading...[/yellow]")
|
|
bz2_file.unlink()
|
|
# Trigger re-download by not skipping it
|
|
console.print(f"[cyan]Re-downloading {dataset_id} with proper size...[/cyan]")
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(Path(__file__).parent / "rdf_dataset_downloader.py"),
|
|
dataset_id,
|
|
"-o",
|
|
str(download_dir.parent),
|
|
"--force",
|
|
],
|
|
capture_output=False,
|
|
)
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Re-download failed for {dataset_id}[/red]")
|
|
return False
|
|
# After re-download, find the new file
|
|
bz2_files = list(download_dir.rglob("*.bz2"))
|
|
if not bz2_files:
|
|
console.print(f"[red]No BZ2 file found after re-download[/red]")
|
|
return False
|
|
bz2_file = bz2_files[0]
|
|
|
|
try:
|
|
# Test the bz2 file integrity
|
|
test_result = subprocess.run(
|
|
["bunzip2", "-t", str(bz2_file)], capture_output=True, text=True
|
|
)
|
|
if test_result.returncode != 0:
|
|
console.print(f"[red]Corrupted BZ2 file: {bz2_file.name}[/red]")
|
|
console.print(
|
|
f"[yellow]File size: {bz2_file.stat().st_size / (1024**2):.2f} MB[/yellow]"
|
|
)
|
|
console.print(f"[yellow]Removing corrupted file and attempting re-download...[/yellow]")
|
|
bz2_file.unlink()
|
|
# Trigger re-download
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(Path(__file__).parent / "rdf_dataset_downloader.py"),
|
|
dataset_id,
|
|
"-o",
|
|
str(download_dir.parent),
|
|
"--force",
|
|
],
|
|
capture_output=False,
|
|
)
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Re-download failed for {dataset_id}[/red]")
|
|
return False
|
|
# After re-download, find the new file
|
|
bz2_files = list(download_dir.rglob("*.bz2"))
|
|
if not bz2_files:
|
|
console.print(f"[red]No BZ2 file found after re-download[/red]")
|
|
return False
|
|
bz2_file = bz2_files[0]
|
|
# Test again
|
|
test_result = subprocess.run(
|
|
["bunzip2", "-t", str(bz2_file)], capture_output=True, text=True
|
|
)
|
|
if test_result.returncode != 0:
|
|
console.print(
|
|
f"[red]File still corrupted after re-download. Manual intervention required.[/red]"
|
|
)
|
|
return False
|
|
|
|
# Try to extract the bz2 file
|
|
console.print(f"[cyan]Extracting {bz2_file.name}...[/cyan]")
|
|
extracted_file = bz2_file.with_suffix("")
|
|
with bz2.open(bz2_file, "rb") as f_in:
|
|
with extracted_file.open("wb") as f_out:
|
|
shutil.copyfileobj(f_in, f_out, length=1024 * 1024)
|
|
console.print(f"[green]Successfully extracted to {extracted_file.name}[/green]")
|
|
# Re-scan for RDF files after extraction
|
|
rdf_files = (
|
|
list(download_dir.rglob("*.ttl"))
|
|
+ list(download_dir.rglob("*.nt"))
|
|
+ list(download_dir.rglob("*.rdf"))
|
|
+ list(download_dir.rglob("*.owl"))
|
|
+ list(download_dir.rglob("*.xml"))
|
|
)
|
|
except Exception as e:
|
|
console.print(f"[red]Failed to process {bz2_file}: {e}[/red]")
|
|
if "end-of-stream marker" in str(e).lower() or "compressed file ended" in str(e).lower():
|
|
console.print(f"[yellow]The file is incomplete. Attempting to re-download...[/yellow]")
|
|
bz2_file.unlink()
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(Path(__file__).parent / "rdf_dataset_downloader.py"),
|
|
dataset_id,
|
|
"-o",
|
|
str(download_dir.parent),
|
|
"--force",
|
|
],
|
|
capture_output=False,
|
|
)
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Re-download failed for {dataset_id}[/red]")
|
|
return False
|
|
# Try again with the new file
|
|
bz2_files = list(download_dir.rglob("*.bz2"))
|
|
if bz2_files:
|
|
bz2_file = bz2_files[0]
|
|
try:
|
|
console.print(f"[cyan]Extracting re-downloaded {bz2_file.name}...[/cyan]")
|
|
extracted_file = bz2_file.with_suffix("")
|
|
with bz2.open(bz2_file, "rb") as f_in:
|
|
with extracted_file.open("wb") as f_out:
|
|
shutil.copyfileobj(f_in, f_out, length=1024 * 1024)
|
|
console.print(f"[green]Successfully extracted to {extracted_file.name}[/green]")
|
|
# Re-scan for RDF files after extraction
|
|
rdf_files = (
|
|
list(download_dir.rglob("*.ttl"))
|
|
+ list(download_dir.rglob("*.nt"))
|
|
+ list(download_dir.rglob("*.rdf"))
|
|
+ list(download_dir.rglob("*.owl"))
|
|
+ list(download_dir.rglob("*.xml"))
|
|
)
|
|
except Exception as e2:
|
|
console.print(f"[red]Failed again after re-download: {e2}[/red]")
|
|
return False
|
|
else:
|
|
console.print(f"[red]No BZ2 file found after re-download[/red]")
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
# Re-scan for RDF files after extraction
|
|
rdf_files = (
|
|
list(download_dir.rglob("*.ttl"))
|
|
+ list(download_dir.rglob("*.nt"))
|
|
+ list(download_dir.rglob("*.rdf"))
|
|
+ list(download_dir.rglob("*.owl"))
|
|
+ list(download_dir.rglob("*.n3"))
|
|
+ list(download_dir.rglob("*.trig"))
|
|
+ list(download_dir.rglob("*.nq"))
|
|
+ list(download_dir.rglob("*.xml"))
|
|
# Also look for compressed versions
|
|
+ list(download_dir.rglob("*.ttl.bz2"))
|
|
+ list(download_dir.rglob("*.ttl.gz"))
|
|
+ list(download_dir.rglob("*.nt.bz2"))
|
|
+ list(download_dir.rglob("*.nt.gz"))
|
|
+ list(download_dir.rglob("*.rdf.bz2"))
|
|
+ list(download_dir.rglob("*.rdf.gz"))
|
|
)
|
|
|
|
if not rdf_files:
|
|
console.print(f"[red]No RDF files found in {download_dir}[/red]")
|
|
return False
|
|
|
|
# Filter out README files if there are other options
|
|
non_readme_files = [f for f in rdf_files if "readme" not in f.name.lower()]
|
|
if non_readme_files:
|
|
rdf_file = non_readme_files[0]
|
|
else:
|
|
rdf_file = rdf_files[0]
|
|
|
|
# Check if the selected file is a compressed file and validate its size
|
|
if str(rdf_file).endswith(".bz2"):
|
|
file_size_gb = rdf_file.stat().st_size / (1024**3)
|
|
if dataset_info.compressed_size_gb:
|
|
expected_gb = dataset_info.compressed_size_gb
|
|
if file_size_gb < expected_gb * 0.9:
|
|
console.print(f"[red]Incomplete BZ2 file: {rdf_file.name}[/red]")
|
|
console.print(
|
|
f"[yellow]File size: {file_size_gb:.3f} GB, Expected: ~{expected_gb:.2f} GB[/yellow]"
|
|
)
|
|
console.print(
|
|
f"[red]File is only {(file_size_gb / expected_gb * 100):.1f}% of expected size[/red]"
|
|
)
|
|
console.print(f"[yellow]Removing incomplete file and re-downloading...[/yellow]")
|
|
rdf_file.unlink()
|
|
|
|
if skip_download:
|
|
console.print(f"[red]Cannot re-download because --skip-download is set[/red]")
|
|
console.print(
|
|
f"[yellow]Please run: python scripts/rdf_dataset_downloader.py {dataset_id} --force[/yellow]"
|
|
)
|
|
return False
|
|
|
|
# Trigger re-download
|
|
console.print(f"[cyan]Re-downloading {dataset_id} with proper size...[/cyan]")
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(Path(__file__).parent / "rdf_dataset_downloader.py"),
|
|
dataset_id,
|
|
"-o",
|
|
str(download_dir.parent),
|
|
"--force",
|
|
],
|
|
capture_output=False,
|
|
)
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Re-download failed for {dataset_id}[/red]")
|
|
return False
|
|
|
|
# After re-download, find the new file again
|
|
rdf_files = (
|
|
list(download_dir.rglob("*.ttl"))
|
|
+ list(download_dir.rglob("*.nt"))
|
|
+ list(download_dir.rglob("*.rdf"))
|
|
+ list(download_dir.rglob("*.owl"))
|
|
+ list(download_dir.rglob("*.n3"))
|
|
+ list(download_dir.rglob("*.trig"))
|
|
+ list(download_dir.rglob("*.nq"))
|
|
+ list(download_dir.rglob("*.xml"))
|
|
+ list(download_dir.rglob("*.ttl.bz2"))
|
|
+ list(download_dir.rglob("*.ttl.gz"))
|
|
+ list(download_dir.rglob("*.nt.bz2"))
|
|
+ list(download_dir.rglob("*.nt.gz"))
|
|
+ list(download_dir.rglob("*.rdf.bz2"))
|
|
+ list(download_dir.rglob("*.rdf.gz"))
|
|
)
|
|
|
|
if not rdf_files:
|
|
console.print(f"[red]No RDF files found after re-download[/red]")
|
|
return False
|
|
|
|
# Filter out README files again
|
|
non_readme_files = [f for f in rdf_files if "readme" not in f.name.lower()]
|
|
if non_readme_files:
|
|
rdf_file = non_readme_files[0]
|
|
else:
|
|
rdf_file = rdf_files[0]
|
|
|
|
# Also test BZ2 file integrity
|
|
console.print(f"[cyan]Testing BZ2 file integrity...[/cyan]")
|
|
test_result = subprocess.run(["bunzip2", "-t", str(rdf_file)], capture_output=True, text=True)
|
|
if test_result.returncode != 0:
|
|
console.print(f"[red]Corrupted BZ2 file: {rdf_file.name}[/red]")
|
|
console.print(f"[red]Error: {test_result.stderr}[/red]")
|
|
|
|
if skip_download:
|
|
console.print(f"[red]Cannot re-download because --skip-download is set[/red]")
|
|
console.print(
|
|
f"[yellow]Please run: python scripts/rdf_dataset_downloader.py {dataset_id} --force[/yellow]"
|
|
)
|
|
return False
|
|
|
|
console.print(f"[yellow]Removing corrupted file and re-downloading...[/yellow]")
|
|
rdf_file.unlink()
|
|
|
|
# Re-download
|
|
console.print(f"[cyan]Re-downloading {dataset_id}...[/cyan]")
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(Path(__file__).parent / "rdf_dataset_downloader.py"),
|
|
dataset_id,
|
|
"-o",
|
|
str(download_dir.parent),
|
|
"--force",
|
|
],
|
|
capture_output=False,
|
|
)
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Re-download failed for {dataset_id}[/red]")
|
|
return False
|
|
|
|
# Find the file again
|
|
rdf_files = (
|
|
list(download_dir.rglob("*.ttl.bz2"))
|
|
+ list(download_dir.rglob("*.nt.bz2"))
|
|
+ list(download_dir.rglob("*.rdf.bz2"))
|
|
)
|
|
if not rdf_files:
|
|
console.print(f"[red]No BZ2 files found after re-download[/red]")
|
|
return False
|
|
rdf_file = rdf_files[0]
|
|
|
|
# Test again
|
|
test_result = subprocess.run(["bunzip2", "-t", str(rdf_file)], capture_output=True, text=True)
|
|
if test_result.returncode != 0:
|
|
console.print(
|
|
f"[red]File still corrupted after re-download. Manual intervention required.[/red]"
|
|
)
|
|
return False
|
|
|
|
console.print(f"[green]BZ2 file integrity check passed[/green]")
|
|
|
|
console.print(f"[green]Found RDF file: {rdf_file}[/green]")
|
|
|
|
# Step 2: Convert to HuggingFace format
|
|
if not skip_convert:
|
|
console.print("\n[yellow]Step 2: Converting to HuggingFace format...[/yellow]")
|
|
|
|
# Special handling for FB15k-237
|
|
if dataset_id == "fb15k-237":
|
|
# Use specialized FB15k-237 converter that handles all splits
|
|
fb15k_script = Path(__file__).parent / "convert_fb15k237_to_hf.py"
|
|
fb15k_dir = download_dir / "fb15k-237" / "FB15K-237.2"
|
|
|
|
if dry_run:
|
|
console.print(
|
|
f"[dim]Would run: python scripts/convert_fb15k237_to_hf.py {fb15k_dir} {hf_dataset_dir}[/dim]"
|
|
)
|
|
else:
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(fb15k_script),
|
|
str(fb15k_dir),
|
|
str(hf_dataset_dir),
|
|
],
|
|
capture_output=False,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Conversion failed for {dataset_id}[/red]")
|
|
return False
|
|
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
console.print(f"[green]Created dataset card: {readme_path}[/green]")
|
|
# Special handling for NELL-995
|
|
elif dataset_id == "nell-995":
|
|
# Use specialized NELL-995 converter that handles raw.kb format
|
|
nell_script = Path(__file__).parent / "convert_nell995_to_hf.py"
|
|
|
|
if dry_run:
|
|
console.print(
|
|
f"[dim]Would run: python scripts/convert_nell995_to_hf.py {download_dir} {hf_dataset_dir}[/dim]"
|
|
)
|
|
else:
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(nell_script),
|
|
str(download_dir),
|
|
str(hf_dataset_dir),
|
|
],
|
|
capture_output=False,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Conversion failed for {dataset_id}[/red]")
|
|
return False
|
|
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
console.print(f"[green]Created dataset card: {readme_path}[/green]")
|
|
# Special handling for ConceptNet
|
|
elif dataset_id == "conceptnet":
|
|
# Use specialized ConceptNet converter that handles CSV format
|
|
conceptnet_script = Path(__file__).parent / "convert_conceptnet_to_hf.py"
|
|
|
|
# Find the downloaded CSV file
|
|
csv_files = list(download_dir.rglob("*.csv")) + list(download_dir.rglob("*.csv.gz"))
|
|
if not csv_files:
|
|
console.print(f"[red]No CSV files found for ConceptNet in {download_dir}[/red]")
|
|
return False
|
|
|
|
csv_file = csv_files[0]
|
|
console.print(f"[green]Found ConceptNet CSV file: {csv_file}[/green]")
|
|
|
|
if dry_run:
|
|
console.print(
|
|
f"[dim]Would run: python scripts/convert_conceptnet_to_hf.py {csv_file} {hf_dataset_dir}[/dim]"
|
|
)
|
|
else:
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(conceptnet_script),
|
|
str(csv_file),
|
|
str(hf_dataset_dir),
|
|
],
|
|
capture_output=False,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Conversion failed for {dataset_id}[/red]")
|
|
return False
|
|
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
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 = Path(__file__).parent / "convert_rdf_to_hf_dataset_streaming_parallel.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(f"[dim]Using parallel streaming converter for {dataset_info.category} dataset[/dim]")
|
|
|
|
if 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]")
|
|
else:
|
|
# Choose appropriate converter script
|
|
if use_streaming:
|
|
# Use streaming converter with 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
|
|
|
|
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,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
console.print(f"[red]Conversion failed for {dataset_id}[/red]")
|
|
|
|
# If compressed file failed, suggest decompressing first
|
|
if rdf_file.suffix == ".gz":
|
|
console.print(f"[yellow]The compressed file may be corrupted or incomplete.[/yellow]")
|
|
console.print(f"[yellow]Attempting to decompress and use partial data...[/yellow]")
|
|
|
|
# Try to decompress what we can
|
|
decompressed_file = rdf_file.with_suffix("")
|
|
decompress_result = subprocess.run(
|
|
["gunzip", "-c", str(rdf_file)], capture_output=True, text=False
|
|
)
|
|
|
|
if decompress_result.stdout:
|
|
# Write whatever we could decompress
|
|
decompressed_file.write_bytes(decompress_result.stdout)
|
|
console.print(
|
|
f"[green]Decompressed {len(decompress_result.stdout) / (1024**2):.2f} MB of data[/green]"
|
|
)
|
|
|
|
# Try conversion again with decompressed file
|
|
console.print(f"[yellow]Retrying conversion with decompressed file...[/yellow]")
|
|
retry_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(Path(__file__).parent / "convert_rdf_to_hf_dataset.py"),
|
|
str(decompressed_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,
|
|
)
|
|
|
|
if retry_result.returncode == 0:
|
|
console.print(f"[green]Successfully converted partial dataset[/green]")
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
console.print(f"[green]Created dataset card: {readme_path}[/green]")
|
|
return True
|
|
else:
|
|
console.print(f"[red]Retry also failed[/red]")
|
|
return False
|
|
else:
|
|
console.print(f"[red]Could not decompress any data from the file[/red]")
|
|
return False
|
|
|
|
return False
|
|
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
console.print(f"[green]Created dataset card: {readme_path}[/green]")
|
|
else:
|
|
console.print("[dim]Skipping conversion[/dim]")
|
|
|
|
# Step 3: Upload to HuggingFace Hub
|
|
if not skip_upload:
|
|
console.print("\n[yellow]Step 3: Uploading to HuggingFace Hub...[/yellow]")
|
|
# Get repository ID from dataset info or config
|
|
if hasattr(dataset_info, 'huggingface_repo') and dataset_info.huggingface_repo:
|
|
repo_id = dataset_info.huggingface_repo
|
|
else:
|
|
# Fallback to default pattern from config
|
|
config = get_dataset_config()
|
|
org = config.get("organization", "CleverThis")
|
|
repo_id = f"{org}/{dataset_id}"
|
|
if dry_run:
|
|
console.print(f"[dim]Would upload to: {repo_id}[/dim]")
|
|
else:
|
|
try:
|
|
from datasets import load_from_disk
|
|
from huggingface_hub import HfApi
|
|
|
|
# Load dataset
|
|
dataset = load_from_disk(str(hf_dataset_dir))
|
|
|
|
# Upload
|
|
console.print(f"[cyan]Uploading to {repo_id}...[/cyan]")
|
|
dataset.push_to_hub(repo_id, private=False)
|
|
|
|
# Upload README separately if it exists
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
if readme_path.exists():
|
|
api = HfApi()
|
|
api.upload_file(
|
|
path_or_fileobj=str(readme_path),
|
|
path_in_repo="README.md",
|
|
repo_id=repo_id,
|
|
repo_type="dataset",
|
|
)
|
|
else:
|
|
# Create README if it doesn't exist (e.g., when skipping conversion)
|
|
console.print("[yellow]README.md not found, creating it now...[/yellow]")
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
api = HfApi()
|
|
api.upload_file(
|
|
path_or_fileobj=str(readme_path),
|
|
path_in_repo="README.md",
|
|
repo_id=repo_id,
|
|
repo_type="dataset",
|
|
)
|
|
|
|
console.print(f"[bold green]✓ Successfully uploaded to {repo_id}[/bold green]")
|
|
except Exception as e:
|
|
console.print(f"[red]Upload failed: {e}[/red]")
|
|
return False
|
|
else:
|
|
console.print("[dim]Skipping upload[/dim]")
|
|
|
|
# Step 4: Cleanup if --rm flag is set
|
|
if remove_after and not dry_run:
|
|
console.print(f"\n[yellow]--rm: Removing downloaded dataset for {dataset_id}[/yellow]")
|
|
if download_dir.exists():
|
|
shutil.rmtree(download_dir)
|
|
console.print(f"[green]✓ Removed {download_dir}[/green]")
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
parser = argparse.ArgumentParser(description="Download, convert, and upload RDF datasets to HuggingFace")
|
|
parser.add_argument(
|
|
"--dataset",
|
|
"-d",
|
|
type=str,
|
|
action="append",
|
|
help="Specific dataset(s) to process (can be specified multiple times)",
|
|
)
|
|
parser.add_argument(
|
|
"--category",
|
|
"-c",
|
|
choices=["small", "medium", "large", "xlarge", "all"],
|
|
default="all",
|
|
help="Process datasets by category",
|
|
)
|
|
parser.add_argument("--base-dir", type=Path, default=Path("./dataset_processing"), help="Base directory for work")
|
|
parser.add_argument("--skip-download", action="store_true", help="Skip download step")
|
|
parser.add_argument("--skip-convert", action="store_true", help="Skip conversion step")
|
|
parser.add_argument("--skip-upload", action="store_true", help="Skip upload step")
|
|
parser.add_argument("--dry-run", action="store_true", help="Show what would be done without doing it")
|
|
parser.add_argument("--list", action="store_true", help="List datasets that would be processed")
|
|
parser.add_argument(
|
|
"--rm",
|
|
action="store_true",
|
|
help="Remove mode: wipe all downloads at start, delete each dataset after processing",
|
|
)
|
|
parser.add_argument(
|
|
"--parallel",
|
|
"-p",
|
|
type=int,
|
|
default=1,
|
|
help="Number of datasets to process in parallel (default: 1, sequential)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Determine which datasets to process
|
|
if args.dataset:
|
|
datasets_to_process = args.dataset
|
|
elif args.category == "all":
|
|
datasets_to_process = list(DATASET_REGISTRY.keys())
|
|
else:
|
|
datasets_to_process = [d_id for d_id, d_info in DATASET_REGISTRY.items() if d_info.category == args.category]
|
|
|
|
# List mode
|
|
if args.list:
|
|
console.print("\n[bold cyan]Datasets to Process:[/bold cyan]\n")
|
|
table = Table(show_header=True)
|
|
table.add_column("ID", style="cyan")
|
|
table.add_column("Name", style="green")
|
|
table.add_column("Category", style="yellow")
|
|
table.add_column("Size", style="blue")
|
|
table.add_column("Available", style="magenta")
|
|
|
|
for dataset_id in datasets_to_process:
|
|
info = DATASET_REGISTRY[dataset_id]
|
|
available = "[green]✓[/green]" if info.available else "[red]✗[/red]"
|
|
table.add_row(dataset_id, info.name, info.category, f"{info.size_gb} GB", available)
|
|
|
|
console.print(table)
|
|
available_count = sum(1 for d_id in datasets_to_process if DATASET_REGISTRY[d_id].available)
|
|
console.print(f"\n[bold]Total: {len(datasets_to_process)} datasets ({available_count} available)[/bold]")
|
|
return
|
|
|
|
# Process datasets
|
|
console.print(f"\n[bold]Processing {len(datasets_to_process)} dataset(s)[/bold]")
|
|
console.print(f"Base directory: {args.base_dir}")
|
|
console.print(f"Dry run: {args.dry_run}")
|
|
console.print(f"Parallel workers: {args.parallel}\n")
|
|
|
|
# If --rm flag is set, wipe all previously downloaded datasets at start
|
|
if args.rm and not args.dry_run:
|
|
downloads_dir = args.base_dir / "downloads"
|
|
if downloads_dir.exists():
|
|
console.print(f"[yellow]--rm: Removing all previous downloads from {downloads_dir}[/yellow]")
|
|
shutil.rmtree(downloads_dir)
|
|
console.print("[green]✓ Previous downloads removed[/green]\n")
|
|
|
|
results = {}
|
|
|
|
# Check if parallel processing is requested
|
|
if args.parallel > 1:
|
|
console.print(f"[cyan]Using parallel processing with {args.parallel} workers[/cyan]\n")
|
|
|
|
# Create progress tracking for parallel execution
|
|
overall_progress = Progress(
|
|
SpinnerColumn(),
|
|
TextColumn("[bold blue]Overall Progress"),
|
|
BarColumn(),
|
|
MofNCompleteColumn(),
|
|
TimeElapsedColumn(),
|
|
)
|
|
|
|
dataset_progress = Progress(
|
|
TextColumn("[bold cyan]{task.fields[dataset_id]:30}"),
|
|
SpinnerColumn(),
|
|
TextColumn("{task.description}"),
|
|
BarColumn(),
|
|
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
TimeElapsedColumn(),
|
|
expand=True,
|
|
)
|
|
|
|
# Create progress table
|
|
def generate_progress_table() -> Table:
|
|
"""Generate a table showing all progress bars."""
|
|
table = Table(title="Dataset Processing Progress", expand=True)
|
|
table.add_column("Progress", ratio=1)
|
|
|
|
# Add overall progress
|
|
table.add_row(overall_progress)
|
|
table.add_row("") # Spacer
|
|
|
|
# Add individual dataset progress
|
|
table.add_row(dataset_progress)
|
|
|
|
return table
|
|
|
|
# Process datasets with live progress display
|
|
with Live(generate_progress_table(), console=console, refresh_per_second=4) as live:
|
|
# Create a lock for thread-safe progress updates
|
|
progress_lock = threading.Lock()
|
|
|
|
# Track tasks for each dataset
|
|
task_ids = {}
|
|
for dataset_id in datasets_to_process:
|
|
task_id = dataset_progress.add_task("[dim]Waiting...[/dim]", dataset_id=dataset_id, total=100)
|
|
task_ids[dataset_id] = task_id
|
|
|
|
# Create overall progress task
|
|
overall_task = overall_progress.add_task("Processing datasets", total=len(datasets_to_process))
|
|
|
|
# Function to monitor subprocess and update progress
|
|
def monitor_subprocess(proc, task_id, stage_name, stage_color, start_pct, end_pct):
|
|
"""Monitor subprocess output and update progress in real-time."""
|
|
import re
|
|
import time
|
|
|
|
stage_range = end_pct - start_pct
|
|
last_progress = 0
|
|
last_update_time = time.time()
|
|
|
|
# For initial stage update
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id,
|
|
description=f"{stage_color}{stage_name}[/{stage_color.strip('[]')}] (0%)",
|
|
completed=start_pct,
|
|
)
|
|
|
|
# Time-based progress estimation as fallback
|
|
start_time = time.time()
|
|
estimated_duration = 30 # Default estimate in seconds
|
|
|
|
while True:
|
|
line = proc.stdout.readline()
|
|
if not line:
|
|
break
|
|
|
|
line_str = line.strip()
|
|
|
|
# Look for PROGRESS markers first
|
|
progress_match = re.search(r"PROGRESS:\s*(\d+)", line_str)
|
|
if progress_match:
|
|
progress_pct = float(progress_match.group(1))
|
|
# Calculate the absolute progress across all stages
|
|
overall_pct = start_pct + (progress_pct / 100) * stage_range
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id,
|
|
description=f"{stage_color}{stage_name}[/{stage_color.strip('[]')}] ({progress_pct:.0f}%)",
|
|
completed=overall_pct,
|
|
)
|
|
last_progress = progress_pct
|
|
last_update_time = time.time()
|
|
continue
|
|
|
|
# Also try other patterns for incremental updates
|
|
patterns = [
|
|
(r"\[(\d+)%\]", lambda m: float(m.group(1))), # [45%]
|
|
(r"completed.*?(\d+)%", lambda m: float(m.group(1))), # completed: 45%
|
|
(
|
|
r"(\d+\.?\d*)\s*/\s*(\d+\.?\d*)\s*MB",
|
|
lambda m: (float(m.group(1)) / float(m.group(2))) * 100,
|
|
), # 45.5/100 MB
|
|
(
|
|
r"(\d+)\s*/\s*(\d+)\s+triples",
|
|
lambda m: (float(m.group(1)) / float(m.group(2))) * 100,
|
|
), # 45/100 triples
|
|
(r"Downloading", lambda m: 5), # Starting download
|
|
(r"Extracting", lambda m: 85), # Extracting phase
|
|
(r"Phase 1", lambda m: 10), # Phase 1 marker
|
|
(r"Phase 2", lambda m: 40), # Phase 2 marker
|
|
(r"Converting", lambda m: 70), # Converting phase
|
|
(r"✓.*Successfully", lambda m: 95), # Success marker
|
|
]
|
|
|
|
for pattern, extractor in patterns:
|
|
match = re.search(pattern, line_str, re.IGNORECASE)
|
|
if match:
|
|
try:
|
|
progress_pct = extractor(match)
|
|
if progress_pct > last_progress:
|
|
last_progress = progress_pct
|
|
overall_pct = start_pct + (progress_pct / 100) * stage_range
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id,
|
|
description=f"{stage_color}{stage_name}[/{stage_color.strip('[]')}] ({progress_pct:.0f}%)",
|
|
completed=overall_pct,
|
|
)
|
|
last_update_time = time.time()
|
|
break
|
|
except:
|
|
pass
|
|
|
|
# If no progress for 2 seconds, update based on elapsed time
|
|
current_time = time.time()
|
|
if current_time - last_update_time > 2:
|
|
elapsed = current_time - start_time
|
|
time_progress = min(90, (elapsed / estimated_duration) * 90) # Cap at 90%
|
|
if time_progress > last_progress + 5:
|
|
last_progress = time_progress
|
|
overall_pct = start_pct + (time_progress / 100) * stage_range
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id,
|
|
description=f"{stage_color}{stage_name}[/{stage_color.strip('[]')}] (~{time_progress:.0f}%)",
|
|
completed=overall_pct,
|
|
)
|
|
last_update_time = current_time
|
|
|
|
proc.wait()
|
|
# Ensure we reach the end percentage for this stage if successful
|
|
if proc.returncode == 0:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id,
|
|
description=f"{stage_color}{stage_name}[/{stage_color.strip('[]')}] (100%)",
|
|
completed=end_pct,
|
|
)
|
|
return proc.returncode == 0
|
|
|
|
# Function to process dataset and update progress
|
|
def process_with_progress(dataset_id: str) -> Tuple[str, bool]:
|
|
"""Process a dataset and update progress bar with real-time updates."""
|
|
task_id = task_ids[dataset_id]
|
|
|
|
try:
|
|
# Get dataset info
|
|
dataset_info = DATASET_REGISTRY.get(dataset_id)
|
|
if not dataset_info:
|
|
with progress_lock:
|
|
dataset_progress.update(task_id, description="[red]✗ Unknown dataset[/red]", completed=100)
|
|
return (dataset_id, False)
|
|
|
|
if not dataset_info.available:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[yellow]⊘ Unavailable[/yellow]", completed=100
|
|
)
|
|
return (dataset_id, None)
|
|
|
|
# Setup directories
|
|
download_dir = args.base_dir / "downloads" / dataset_id
|
|
hf_dataset_dir = args.base_dir / "hf_datasets" / dataset_id
|
|
|
|
# Calculate progress increments
|
|
total_steps = 3 - (args.skip_download + args.skip_convert + args.skip_upload)
|
|
if total_steps == 0:
|
|
total_steps = 1
|
|
step_size = 100 / total_steps
|
|
current_step = 0
|
|
|
|
# Step 1: Download with real-time progress
|
|
if not args.skip_download:
|
|
start_pct = current_step * step_size
|
|
end_pct = (current_step + 1) * step_size
|
|
|
|
if not args.dry_run:
|
|
# Check if wrapper script exists for progress monitoring
|
|
download_wrapper = Path(__file__).parent / "download_with_progress.py"
|
|
if download_wrapper.exists():
|
|
download_script = download_wrapper
|
|
else:
|
|
download_script = Path(__file__).parent / "rdf_dataset_downloader.py"
|
|
|
|
# Use Popen for real-time output monitoring
|
|
proc = subprocess.Popen(
|
|
[
|
|
sys.executable,
|
|
"-u", # Unbuffered output
|
|
str(download_script),
|
|
dataset_id,
|
|
"-o",
|
|
str(download_dir),
|
|
],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1, # Line buffering
|
|
)
|
|
|
|
# Monitor and update progress
|
|
success = monitor_subprocess(
|
|
proc, task_id, "📥 Downloading", "[yellow]", start_pct, end_pct
|
|
)
|
|
|
|
if not success:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[red]✗ Download failed[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
current_step += 1
|
|
|
|
# Step 2: Convert with real-time progress
|
|
if not args.skip_convert:
|
|
start_pct = current_step * step_size
|
|
end_pct = (current_step + 1) * step_size
|
|
|
|
if not args.dry_run:
|
|
# Special handling for FB15k-237
|
|
if dataset_id == "fb15k-237":
|
|
# Use specialized FB15k-237 converter
|
|
fb_script = Path(__file__).parent / "convert_fb15k237_to_hf.py"
|
|
fb15k_dir = download_dir / "fb15k-237" / "FB15K-237.2"
|
|
|
|
proc = subprocess.Popen(
|
|
[
|
|
sys.executable,
|
|
"-u",
|
|
str(fb_script),
|
|
str(fb15k_dir),
|
|
str(hf_dataset_dir),
|
|
],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
|
|
success = monitor_subprocess(
|
|
proc, task_id, "🔄 Converting FB15k", "[cyan]", start_pct, end_pct
|
|
)
|
|
|
|
if not success:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[red]✗ Conversion failed[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
|
|
# Special handling for NELL-995
|
|
elif dataset_id == "nell-995":
|
|
# Use specialized NELL-995 converter
|
|
nell_script = Path(__file__).parent / "convert_nell995_to_hf.py"
|
|
|
|
proc = subprocess.Popen(
|
|
[
|
|
sys.executable,
|
|
"-u",
|
|
str(nell_script),
|
|
str(download_dir),
|
|
str(hf_dataset_dir),
|
|
],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
|
|
success = monitor_subprocess(
|
|
proc, task_id, "🔄 Converting NELL", "[cyan]", start_pct, end_pct
|
|
)
|
|
|
|
if not success:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[red]✗ Conversion failed[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
|
|
# Special handling for ConceptNet
|
|
elif dataset_id == "conceptnet":
|
|
# Use specialized ConceptNet converter
|
|
conceptnet_script = Path(__file__).parent / "convert_conceptnet_to_hf.py"
|
|
|
|
# Find the downloaded CSV file
|
|
csv_files = list(download_dir.rglob("*.csv")) + list(download_dir.rglob("*.csv.gz"))
|
|
if not csv_files:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[red]✗ No CSV files found[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
csv_file = csv_files[0]
|
|
|
|
proc = subprocess.Popen(
|
|
[
|
|
sys.executable,
|
|
"-u",
|
|
str(conceptnet_script),
|
|
str(csv_file),
|
|
str(hf_dataset_dir),
|
|
],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
|
|
success = monitor_subprocess(
|
|
proc, task_id, "🔄 Converting ConceptNet", "[cyan]", start_pct, end_pct
|
|
)
|
|
|
|
if not success:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[red]✗ Conversion failed[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
|
|
# Handle regular RDF datasets
|
|
else:
|
|
# Find the downloaded file
|
|
rdf_files = (
|
|
list(download_dir.rglob("*.ttl"))
|
|
+ list(download_dir.rglob("*.nt"))
|
|
+ list(download_dir.rglob("*.rdf"))
|
|
+ list(download_dir.rglob("*.owl"))
|
|
+ list(download_dir.rglob("*.xml"))
|
|
)
|
|
|
|
# Special handling for geonames - it's XML format but stored as .txt
|
|
if dataset_id == "geonames" and dataset_info.format == "xml":
|
|
rdf_files += list(download_dir.rglob("*.txt"))
|
|
|
|
if dataset_info.format == "tsv":
|
|
rdf_files += list(download_dir.rglob("*.txt")) + list(download_dir.rglob("*.tsv"))
|
|
|
|
# Also check for CSV files for special datasets
|
|
if dataset_info.format == "csv":
|
|
rdf_files += list(download_dir.rglob("*.csv"))
|
|
|
|
if not rdf_files:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[red]✗ No RDF files found[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
# Filter out README files if there are other options
|
|
non_readme_files = [f for f in rdf_files if "readme" not in f.name.lower()]
|
|
if non_readme_files:
|
|
rdf_file = non_readme_files[0]
|
|
else:
|
|
rdf_file = rdf_files[0]
|
|
|
|
# Determine converter with progress wrapper
|
|
use_streaming = (
|
|
dataset_info.category in ["medium", "large", "xlarge"]
|
|
or "geonames" in dataset_id.lower()
|
|
)
|
|
|
|
# Check for wrapper scripts that provide progress
|
|
convert_wrapper = Path(__file__).parent / "convert_rdf_progress.py"
|
|
streaming_wrapper = Path(__file__).parent / "convert_rdf_streaming_progress.py"
|
|
|
|
if use_streaming:
|
|
if streaming_wrapper.exists():
|
|
converter_script = streaming_wrapper
|
|
else:
|
|
converter_script = (
|
|
Path(__file__).parent / "convert_rdf_to_hf_dataset_streaming_parallel.py"
|
|
)
|
|
else:
|
|
if convert_wrapper.exists():
|
|
converter_script = convert_wrapper
|
|
else:
|
|
converter_script = Path(__file__).parent / "convert_rdf_to_hf_dataset.py"
|
|
|
|
# Build command
|
|
cmd = [
|
|
sys.executable,
|
|
"-u",
|
|
str(converter_script),
|
|
str(rdf_file),
|
|
str(hf_dataset_dir),
|
|
"--format",
|
|
get_rdf_format(dataset_info),
|
|
]
|
|
|
|
# Add additional arguments for streaming converter
|
|
if use_streaming and "streaming" in converter_script.name:
|
|
# Use appropriate chunk size based on dataset size
|
|
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
|
|
|
|
cmd.extend(["--chunk-size", str(chunk_size)])
|
|
|
|
# Add metadata arguments
|
|
cmd.extend(
|
|
[
|
|
"--description",
|
|
dataset_info.description,
|
|
"--homepage",
|
|
dataset_info.url,
|
|
"--license",
|
|
dataset_info.license if dataset_info.license else "Unknown",
|
|
]
|
|
)
|
|
|
|
# Run conversion with monitoring
|
|
proc = subprocess.Popen(
|
|
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
|
|
)
|
|
|
|
success = monitor_subprocess(
|
|
proc, task_id, "🔄 Converting", "[cyan]", start_pct, end_pct
|
|
)
|
|
|
|
if not success:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[red]✗ Conversion failed[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
# Create dataset card
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
|
|
current_step += 1
|
|
|
|
# Step 3: Upload with progress tracking
|
|
if not args.skip_upload:
|
|
start_pct = current_step * step_size
|
|
end_pct = (current_step + 1) * step_size
|
|
|
|
if not args.dry_run:
|
|
try:
|
|
from datasets import load_from_disk
|
|
from huggingface_hub import HfApi
|
|
import time
|
|
|
|
# Update start of upload
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id,
|
|
description="[green]📤 Loading dataset...[/green] (0%)",
|
|
completed=start_pct,
|
|
)
|
|
|
|
# Load dataset
|
|
dataset = load_from_disk(str(hf_dataset_dir))
|
|
|
|
# Update after loading
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id,
|
|
description="[green]📤 Uploading to Hub...[/green] (20%)",
|
|
completed=start_pct + 0.2 * (end_pct - start_pct),
|
|
)
|
|
|
|
# Upload dataset
|
|
repo_id = f"CleverThis/{dataset_id}"
|
|
dataset.push_to_hub(repo_id, private=False)
|
|
|
|
# Update after main upload
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id,
|
|
description="[green]📤 Uploading README...[/green] (80%)",
|
|
completed=start_pct + 0.8 * (end_pct - start_pct),
|
|
)
|
|
|
|
# Upload README separately if it exists
|
|
readme_path = hf_dataset_dir / "README.md"
|
|
if not readme_path.exists():
|
|
# Create README if it doesn't exist
|
|
readme_path.write_text(create_dataset_card(dataset_info))
|
|
|
|
api = HfApi()
|
|
api.upload_file(
|
|
path_or_fileobj=str(readme_path),
|
|
path_in_repo="README.md",
|
|
repo_id=repo_id,
|
|
repo_type="dataset",
|
|
)
|
|
|
|
# Mark complete
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description="[green]📤 Uploaded[/green] (100%)", completed=end_pct
|
|
)
|
|
|
|
except Exception as e:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description=f"[red]✗ Upload failed: {str(e)[:30]}[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
current_step += 1
|
|
|
|
# Cleanup if requested
|
|
if args.rm and not args.dry_run:
|
|
if download_dir.exists():
|
|
shutil.rmtree(download_dir)
|
|
|
|
# Mark as complete
|
|
with progress_lock:
|
|
dataset_progress.update(task_id, description="[green]✓ Complete[/green]", completed=100)
|
|
|
|
return (dataset_id, True)
|
|
|
|
except Exception as e:
|
|
with progress_lock:
|
|
dataset_progress.update(
|
|
task_id, description=f"[red]✗ Error: {str(e)[:30]}[/red]", completed=100
|
|
)
|
|
return (dataset_id, False)
|
|
|
|
# Use ThreadPoolExecutor for I/O-bound download/upload operations
|
|
with ThreadPoolExecutor(max_workers=args.parallel) as executor:
|
|
# Submit all tasks
|
|
futures = {
|
|
executor.submit(process_with_progress, dataset_id): dataset_id for dataset_id in datasets_to_process
|
|
}
|
|
|
|
# Process completed tasks
|
|
completed = 0
|
|
for future in as_completed(futures):
|
|
dataset_id = futures[future]
|
|
try:
|
|
dataset_id, success = future.result()
|
|
results[dataset_id] = success
|
|
except Exception as e:
|
|
console.print(f"[red]Error processing {dataset_id}: {e}[/red]")
|
|
results[dataset_id] = False
|
|
|
|
completed += 1
|
|
with progress_lock:
|
|
overall_progress.update(overall_task, completed=completed)
|
|
# Update live display
|
|
live.update(generate_progress_table())
|
|
|
|
else:
|
|
# Sequential processing (original behavior)
|
|
for dataset_id in datasets_to_process:
|
|
success = process_dataset(
|
|
dataset_id=dataset_id,
|
|
base_dir=args.base_dir,
|
|
skip_download=args.skip_download,
|
|
skip_convert=args.skip_convert,
|
|
skip_upload=args.skip_upload,
|
|
dry_run=args.dry_run,
|
|
remove_after=args.rm,
|
|
)
|
|
results[dataset_id] = success
|
|
|
|
# Summary
|
|
console.print("\n[bold cyan]Processing Summary:[/bold cyan]\n")
|
|
table = Table(show_header=True)
|
|
table.add_column("Dataset", style="cyan")
|
|
table.add_column("Status", style="green")
|
|
|
|
for dataset_id, success in results.items():
|
|
if success is None:
|
|
status = "[yellow]⊘ Skipped[/yellow]"
|
|
elif success:
|
|
status = "[green]✓ Success[/green]"
|
|
else:
|
|
status = "[red]✗ Failed[/red]"
|
|
table.add_row(dataset_id, status)
|
|
|
|
console.print(table)
|
|
|
|
successful = sum(1 for v in results.values() if v is True)
|
|
skipped = sum(1 for v in results.values() if v is None)
|
|
failed = sum(1 for v in results.values() if v is False)
|
|
|
|
result_msg = f"Results: {successful}/{len(results)} successful"
|
|
if skipped:
|
|
result_msg += f", {skipped} skipped"
|
|
if failed:
|
|
result_msg += f", {failed} failed"
|
|
console.print(f"\n[bold]{result_msg}[/bold]")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|