Spaces:
Sleeping
Sleeping
Commit ·
b81d3dd
1
Parent(s): 757c484
Scalability improvement: HF login, dynamic test updates, improved warnings for downloading issues
Browse files- README.md +4 -0
- app.py +149 -57
- requirements.txt +1 -1
- validation.py +41 -5
README.md
CHANGED
|
@@ -8,6 +8,10 @@ sdk_version: 5.20.0
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: apache-2.0
|
| 11 |
+
hf_oauth: true
|
| 12 |
+
hf_oauth_scopes:
|
| 13 |
+
- read-repos
|
| 14 |
+
- gated-repos
|
| 15 |
---
|
| 16 |
|
| 17 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
CHANGED
|
@@ -5,7 +5,7 @@ import gradio_client.utils as _gc_utils
|
|
| 5 |
import json
|
| 6 |
import time
|
| 7 |
import traceback
|
| 8 |
-
from validation import validate_json, validate_croissant, validate_records, validate_rai, generate_validation_report
|
| 9 |
|
| 10 |
# Patch gradio_client.utils to handle boolean JSON schemas.
|
| 11 |
# gradio_client 1.7.2 crashes when a component schema has `additionalProperties: true/false`.
|
|
@@ -41,15 +41,15 @@ def process_file(file):
|
|
| 41 |
if not croissant_valid:
|
| 42 |
return results, None
|
| 43 |
|
| 44 |
-
# Check 3:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
records_valid, records_message, records_status = validate_records(json_data)
|
| 46 |
records_message = records_message.replace("\n✓\n", "\n")
|
| 47 |
results.append(("Records Generation Test", records_valid, records_message, records_status))
|
| 48 |
|
| 49 |
-
# Check 4: Responsible AI metadata
|
| 50 |
-
rai_valid, rai_message = validate_rai(json_data)
|
| 51 |
-
results.append(("Responsible AI Metadata", rai_valid, rai_message, "pass" if rai_valid else "error"))
|
| 52 |
-
|
| 53 |
# Generate final report
|
| 54 |
report = generate_validation_report(filename, json_data, results)
|
| 55 |
|
|
@@ -68,10 +68,43 @@ def create_ui():
|
|
| 68 |
The validator will check:
|
| 69 |
1. If the file is valid JSON
|
| 70 |
2. If it passes Croissant schema validation
|
| 71 |
-
3. If
|
| 72 |
-
4. If
|
| 73 |
""")
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
# Track the active tab for conditional UI updates
|
| 76 |
active_tab = gr.State("upload") # Default to upload tab
|
| 77 |
|
|
@@ -384,75 +417,86 @@ def create_ui():
|
|
| 384 |
None # Clear report file
|
| 385 |
]
|
| 386 |
|
| 387 |
-
def fetch_from_url(url):
|
| 388 |
if not url:
|
| 389 |
-
|
| 390 |
"""<div class="progress-status">Please enter a URL</div>""",
|
| 391 |
gr.update(value=""),
|
| 392 |
gr.update(visible=False),
|
| 393 |
None,
|
| 394 |
None
|
| 395 |
]
|
|
|
|
| 396 |
try:
|
| 397 |
# Fetch JSON from URL
|
| 398 |
response = requests.get(url, timeout=10)
|
| 399 |
response.raise_for_status()
|
| 400 |
json_data = response.json()
|
| 401 |
-
|
| 402 |
-
# Process validation
|
| 403 |
-
progress_html = """<div class="progress-status">✅ JSON fetched successfully from URL</div>"""
|
| 404 |
-
|
| 405 |
-
# Validate the fetched JSON
|
| 406 |
results = []
|
| 407 |
results.append(("JSON Format Validation", True, "The URL returned valid JSON."))
|
| 408 |
-
|
| 409 |
croissant_valid, croissant_message, croissant_status = validate_croissant(json_data)
|
| 410 |
results.append(("Croissant Schema Validation", croissant_valid, croissant_message, croissant_status))
|
| 411 |
-
|
| 412 |
if not croissant_valid:
|
| 413 |
-
|
| 414 |
"""<div class="progress-status">✅ JSON fetched successfully from URL</div>""",
|
| 415 |
build_results_html(results),
|
| 416 |
gr.update(visible=False),
|
| 417 |
None,
|
| 418 |
None
|
| 419 |
]
|
| 420 |
-
|
| 421 |
-
records_valid, records_message, records_status = validate_records(json_data)
|
| 422 |
-
results.append(("Records Generation Test (Optional)", records_valid, records_message, records_status))
|
| 423 |
|
| 424 |
-
#
|
| 425 |
rai_valid, rai_message = validate_rai(json_data)
|
| 426 |
results.append(("Responsible AI Metadata", rai_valid, rai_message, "pass" if rai_valid else "error"))
|
| 427 |
|
| 428 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
report = generate_validation_report(url.split("/")[-1], json_data, results)
|
| 430 |
report_filename = f"report_croissant-validation_{json_data.get('name', 'unnamed')}.md"
|
| 431 |
-
|
| 432 |
if report:
|
| 433 |
with open(report_filename, "w") as f:
|
| 434 |
f.write(report)
|
| 435 |
|
| 436 |
-
|
| 437 |
"""<div class="progress-status">✅ JSON fetched successfully from URL</div>""",
|
| 438 |
build_results_html(results),
|
| 439 |
gr.update(visible=True),
|
| 440 |
report,
|
| 441 |
report_filename
|
| 442 |
]
|
| 443 |
-
|
| 444 |
except requests.exceptions.RequestException as e:
|
| 445 |
error_message = f"Error fetching URL: {str(e)}"
|
| 446 |
-
|
| 447 |
f"""<div class="progress-status">{error_message}</div>""",
|
| 448 |
gr.update(value=""),
|
| 449 |
gr.update(visible=False),
|
| 450 |
None,
|
| 451 |
None
|
| 452 |
-
]
|
| 453 |
except json.JSONDecodeError as e:
|
| 454 |
error_message = f"URL did not return valid JSON: {str(e)}"
|
| 455 |
-
|
| 456 |
f"""<div class="progress-status">{error_message}</div>""",
|
| 457 |
gr.update(value=""),
|
| 458 |
gr.update(visible=False),
|
|
@@ -461,7 +505,7 @@ def create_ui():
|
|
| 461 |
]
|
| 462 |
except Exception as e:
|
| 463 |
error_message = f"Unexpected error: {str(e)}"
|
| 464 |
-
|
| 465 |
f"""<div class="progress-status">{error_message}</div>""",
|
| 466 |
gr.update(value=""),
|
| 467 |
gr.update(visible=False),
|
|
@@ -487,7 +531,14 @@ def create_ui():
|
|
| 487 |
status_class = "status-warning"
|
| 488 |
status_icon = "?"
|
| 489 |
if "Records" in test_name:
|
| 490 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
else:
|
| 492 |
message_with_emoji = "⚠️ " + message
|
| 493 |
else: # error
|
|
@@ -524,7 +575,7 @@ def create_ui():
|
|
| 524 |
html += '</div>'
|
| 525 |
return gr.update(value=html, visible=True)
|
| 526 |
|
| 527 |
-
def on_validate(file):
|
| 528 |
if file is None:
|
| 529 |
yield [
|
| 530 |
gr.update(value=""), # validation_results
|
|
@@ -535,7 +586,7 @@ def create_ui():
|
|
| 535 |
]
|
| 536 |
return
|
| 537 |
|
| 538 |
-
# Show
|
| 539 |
progress_html = """
|
| 540 |
<div class="validation-progress">
|
| 541 |
<div class="loading-spinner"></div>
|
|
@@ -543,37 +594,77 @@ def create_ui():
|
|
| 543 |
</div>
|
| 544 |
"""
|
| 545 |
yield [
|
| 546 |
-
gr.update(value=""),
|
| 547 |
-
gr.update(visible=True, value=progress_html),
|
| 548 |
-
gr.update(visible=False),
|
| 549 |
-
None,
|
| 550 |
-
None
|
| 551 |
]
|
| 552 |
|
| 553 |
-
|
| 554 |
-
|
| 555 |
|
| 556 |
-
#
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
|
| 564 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
report_filename = f"report_croissant-validation_{dataset_name}.md"
|
| 566 |
if report:
|
| 567 |
with open(report_filename, "w") as f:
|
| 568 |
f.write(report)
|
| 569 |
|
| 570 |
-
# Return final state
|
| 571 |
yield [
|
| 572 |
-
build_results_html(results),
|
| 573 |
-
gr.update(visible=False),
|
| 574 |
-
gr.update(visible=True) if report else gr.update(visible=False),
|
| 575 |
-
report if report else None,
|
| 576 |
-
report_filename if report else None
|
| 577 |
]
|
| 578 |
|
| 579 |
# Connect UI events to functions with updated outputs
|
|
@@ -619,7 +710,7 @@ def create_ui():
|
|
| 619 |
# Footer
|
| 620 |
gr.HTML("""
|
| 621 |
<div style="text-align: center; margin-top: 20px;">
|
| 622 |
-
<p>Learn more about 🥐<a href="https://github.com/mlcommons/croissant" target="_blank">Croissant</a>.</p>
|
| 623 |
</div>
|
| 624 |
""")
|
| 625 |
|
|
@@ -627,7 +718,8 @@ def create_ui():
|
|
| 627 |
<div class="progress-status" style="text-align: left; color: #d35400;">
|
| 628 |
⚠️ It is possible that this validator is currently being used by a lot of people at the same time, which may trigger rate limiting by the platform hosting your data.
|
| 629 |
The app will then try again and may get into a very long loop. If it takes too long to run, we recommend using any of the following options:
|
| 630 |
-
<ul style="text-align:left; margin:
|
|
|
|
| 631 |
<li>🔁 Click the button with the three dots (⋯) above and select "Duplicate this Space" to run this app in your own Hugging Face space.</li>
|
| 632 |
<li>💻 Click the button with the three dots (⋯) above and select "Run Locally" and then "Clone (git)" to get instructions to run the checker locally. You can also use docker option (you don't need the tokens).</li>
|
| 633 |
<li>🥐 Run the Croissant validation code yourself (<a href="https://github.com/mlcommons/croissant" target="_blank">GitHub</a>), e.g. with <a href="https://github.com/mlcommons/croissant/tree/7a632f34438e9c8e3812c6a0049898560259c6d4/python/mlcroissant/mlcroissant/scripts" target="_blank">these scripts</a> (validate and load).</li>
|
|
|
|
| 5 |
import json
|
| 6 |
import time
|
| 7 |
import traceback
|
| 8 |
+
from validation import validate_json, validate_croissant, validate_records, validate_rai, generate_validation_report, set_active_token, clear_active_token
|
| 9 |
|
| 10 |
# Patch gradio_client.utils to handle boolean JSON schemas.
|
| 11 |
# gradio_client 1.7.2 crashes when a component schema has `additionalProperties: true/false`.
|
|
|
|
| 41 |
if not croissant_valid:
|
| 42 |
return results, None
|
| 43 |
|
| 44 |
+
# Check 3: Responsible AI metadata
|
| 45 |
+
rai_valid, rai_message = validate_rai(json_data)
|
| 46 |
+
results.append(("Responsible AI Metadata", rai_valid, rai_message, "pass" if rai_valid else "error"))
|
| 47 |
+
|
| 48 |
+
# Check 4: Records validation (with timeout-safe and error-specific logic)
|
| 49 |
records_valid, records_message, records_status = validate_records(json_data)
|
| 50 |
records_message = records_message.replace("\n✓\n", "\n")
|
| 51 |
results.append(("Records Generation Test", records_valid, records_message, records_status))
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
# Generate final report
|
| 54 |
report = generate_validation_report(filename, json_data, results)
|
| 55 |
|
|
|
|
| 68 |
The validator will check:
|
| 69 |
1. If the file is valid JSON
|
| 70 |
2. If it passes Croissant schema validation
|
| 71 |
+
3. If all required Responsible AI metadata fields are present
|
| 72 |
+
4. If records can be generated within a reasonable time
|
| 73 |
""")
|
| 74 |
+
|
| 75 |
+
gr.HTML("""
|
| 76 |
+
<style>
|
| 77 |
+
#login-card {
|
| 78 |
+
display: flex !important;
|
| 79 |
+
flex-direction: column !important;
|
| 80 |
+
align-items: center !important;
|
| 81 |
+
gap: 10px !important;
|
| 82 |
+
padding: 16px 24px !important;
|
| 83 |
+
border: 1px solid rgba(128,128,128,0.35) !important;
|
| 84 |
+
border-radius: 10px !important;
|
| 85 |
+
background-color: rgba(0,0,0,0.03) !important;
|
| 86 |
+
margin-bottom: 16px !important;
|
| 87 |
+
}
|
| 88 |
+
#login-card > * {
|
| 89 |
+
background: none !important;
|
| 90 |
+
border: none !important;
|
| 91 |
+
box-shadow: none !important;
|
| 92 |
+
}
|
| 93 |
+
#login-card .login-button {
|
| 94 |
+
background-color: white !important;
|
| 95 |
+
color: black !important;
|
| 96 |
+
border: 1px solid #ccc !important;
|
| 97 |
+
}
|
| 98 |
+
</style>
|
| 99 |
+
""")
|
| 100 |
+
if os.environ.get("SPACE_ID"):
|
| 101 |
+
with gr.Column(elem_id="login-card"):
|
| 102 |
+
gr.HTML("<p style='text-align:center; margin:0;'>"
|
| 103 |
+
"If your dataset is hosted on Hugging Face, log in first. "
|
| 104 |
+
"This avoids rate limiting issues and allows you to fetch gated datasets. "
|
| 105 |
+
"If your dataset is not on Hugging Face, you can still use the validator without logging in.</p>")
|
| 106 |
+
gr.LoginButton(elem_classes=["login-button"])
|
| 107 |
+
|
| 108 |
# Track the active tab for conditional UI updates
|
| 109 |
active_tab = gr.State("upload") # Default to upload tab
|
| 110 |
|
|
|
|
| 417 |
None # Clear report file
|
| 418 |
]
|
| 419 |
|
| 420 |
+
def fetch_from_url(url, oauth_token: gr.OAuthToken | None = None):
|
| 421 |
if not url:
|
| 422 |
+
yield [
|
| 423 |
"""<div class="progress-status">Please enter a URL</div>""",
|
| 424 |
gr.update(value=""),
|
| 425 |
gr.update(visible=False),
|
| 426 |
None,
|
| 427 |
None
|
| 428 |
]
|
| 429 |
+
return
|
| 430 |
try:
|
| 431 |
# Fetch JSON from URL
|
| 432 |
response = requests.get(url, timeout=10)
|
| 433 |
response.raise_for_status()
|
| 434 |
json_data = response.json()
|
| 435 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
results = []
|
| 437 |
results.append(("JSON Format Validation", True, "The URL returned valid JSON."))
|
| 438 |
+
|
| 439 |
croissant_valid, croissant_message, croissant_status = validate_croissant(json_data)
|
| 440 |
results.append(("Croissant Schema Validation", croissant_valid, croissant_message, croissant_status))
|
| 441 |
+
|
| 442 |
if not croissant_valid:
|
| 443 |
+
yield [
|
| 444 |
"""<div class="progress-status">✅ JSON fetched successfully from URL</div>""",
|
| 445 |
build_results_html(results),
|
| 446 |
gr.update(visible=False),
|
| 447 |
None,
|
| 448 |
None
|
| 449 |
]
|
| 450 |
+
return
|
|
|
|
|
|
|
| 451 |
|
| 452 |
+
# Responsible AI metadata (fast)
|
| 453 |
rai_valid, rai_message = validate_rai(json_data)
|
| 454 |
results.append(("Responsible AI Metadata", rai_valid, rai_message, "pass" if rai_valid else "error"))
|
| 455 |
|
| 456 |
+
# Show partial results while records test runs
|
| 457 |
+
records_spinner = """<div class="validation-progress"><div class="loading-spinner"></div><span>Running records generation test...</span></div>"""
|
| 458 |
+
yield [
|
| 459 |
+
"""<div class="progress-status">✅ JSON fetched successfully from URL</div>""",
|
| 460 |
+
gr.update(value=build_results_html(results)["value"] + records_spinner, visible=True),
|
| 461 |
+
gr.update(visible=False),
|
| 462 |
+
None,
|
| 463 |
+
None
|
| 464 |
+
]
|
| 465 |
+
|
| 466 |
+
# Records validation (slow)
|
| 467 |
+
set_active_token(oauth_token.token if oauth_token else None)
|
| 468 |
+
try:
|
| 469 |
+
records_valid, records_message, records_status = validate_records(json_data)
|
| 470 |
+
finally:
|
| 471 |
+
clear_active_token()
|
| 472 |
+
results.append(("Records Generation Test (Optional)", records_valid, records_message, records_status))
|
| 473 |
+
|
| 474 |
report = generate_validation_report(url.split("/")[-1], json_data, results)
|
| 475 |
report_filename = f"report_croissant-validation_{json_data.get('name', 'unnamed')}.md"
|
|
|
|
| 476 |
if report:
|
| 477 |
with open(report_filename, "w") as f:
|
| 478 |
f.write(report)
|
| 479 |
|
| 480 |
+
yield [
|
| 481 |
"""<div class="progress-status">✅ JSON fetched successfully from URL</div>""",
|
| 482 |
build_results_html(results),
|
| 483 |
gr.update(visible=True),
|
| 484 |
report,
|
| 485 |
report_filename
|
| 486 |
]
|
| 487 |
+
|
| 488 |
except requests.exceptions.RequestException as e:
|
| 489 |
error_message = f"Error fetching URL: {str(e)}"
|
| 490 |
+
yield [
|
| 491 |
f"""<div class="progress-status">{error_message}</div>""",
|
| 492 |
gr.update(value=""),
|
| 493 |
gr.update(visible=False),
|
| 494 |
None,
|
| 495 |
None
|
| 496 |
+
]
|
| 497 |
except json.JSONDecodeError as e:
|
| 498 |
error_message = f"URL did not return valid JSON: {str(e)}"
|
| 499 |
+
yield [
|
| 500 |
f"""<div class="progress-status">{error_message}</div>""",
|
| 501 |
gr.update(value=""),
|
| 502 |
gr.update(visible=False),
|
|
|
|
| 505 |
]
|
| 506 |
except Exception as e:
|
| 507 |
error_message = f"Unexpected error: {str(e)}"
|
| 508 |
+
yield [
|
| 509 |
f"""<div class="progress-status">{error_message}</div>""",
|
| 510 |
gr.update(value=""),
|
| 511 |
gr.update(visible=False),
|
|
|
|
| 531 |
status_class = "status-warning"
|
| 532 |
status_icon = "?"
|
| 533 |
if "Records" in test_name:
|
| 534 |
+
if "429" in message or "Too Many Requests" in message or "rate limit" in message.lower():
|
| 535 |
+
message_with_emoji = (
|
| 536 |
+
"⚠️ Rate limit hit while trying to generate records. "
|
| 537 |
+
"Log in with your Hugging Face account (button above) to use your own token and avoid this.\n\n"
|
| 538 |
+
+ message
|
| 539 |
+
)
|
| 540 |
+
else:
|
| 541 |
+
message_with_emoji = "⚠️ Could not automatically generate records. This is oftentimes not an issue (e.g. datasets could be too large or too complex), and it's not required to pass this test to submit to NeurIPS.\n\n" + message
|
| 542 |
else:
|
| 543 |
message_with_emoji = "⚠️ " + message
|
| 544 |
else: # error
|
|
|
|
| 575 |
html += '</div>'
|
| 576 |
return gr.update(value=html, visible=True)
|
| 577 |
|
| 578 |
+
def on_validate(file, oauth_token: gr.OAuthToken | None = None):
|
| 579 |
if file is None:
|
| 580 |
yield [
|
| 581 |
gr.update(value=""), # validation_results
|
|
|
|
| 586 |
]
|
| 587 |
return
|
| 588 |
|
| 589 |
+
# Show initial spinner
|
| 590 |
progress_html = """
|
| 591 |
<div class="validation-progress">
|
| 592 |
<div class="loading-spinner"></div>
|
|
|
|
| 594 |
</div>
|
| 595 |
"""
|
| 596 |
yield [
|
| 597 |
+
gr.update(value=""),
|
| 598 |
+
gr.update(visible=True, value=progress_html),
|
| 599 |
+
gr.update(visible=False),
|
| 600 |
+
None,
|
| 601 |
+
None
|
| 602 |
]
|
| 603 |
|
| 604 |
+
results = []
|
| 605 |
+
filename = file.name.split("/")[-1]
|
| 606 |
|
| 607 |
+
# Check 1: JSON
|
| 608 |
+
json_valid, json_message, json_data = validate_json(file.name)
|
| 609 |
+
json_message = json_message.replace("\n✓\n", "\n")
|
| 610 |
+
results.append(("JSON Format Validation", json_valid, json_message, "pass" if json_valid else "error"))
|
| 611 |
+
if not json_valid:
|
| 612 |
+
yield [build_results_html(results), gr.update(visible=False), gr.update(visible=False), None, None]
|
| 613 |
+
return
|
| 614 |
|
| 615 |
+
# Check 2: Croissant schema
|
| 616 |
+
croissant_valid, croissant_message, croissant_status = validate_croissant(json_data)
|
| 617 |
+
croissant_message = croissant_message.replace("\n✓\n", "\n")
|
| 618 |
+
results.append(("Croissant Schema Validation", croissant_valid, croissant_message, croissant_status))
|
| 619 |
+
if not croissant_valid:
|
| 620 |
+
yield [build_results_html(results), gr.update(visible=False), gr.update(visible=False), None, None]
|
| 621 |
+
return
|
| 622 |
+
|
| 623 |
+
# Check 3: Responsible AI metadata (fast)
|
| 624 |
+
rai_valid, rai_message = validate_rai(json_data)
|
| 625 |
+
results.append(("Responsible AI Metadata", rai_valid, rai_message, "pass" if rai_valid else "error"))
|
| 626 |
+
|
| 627 |
+
# Show partial results + spinner for records test
|
| 628 |
+
records_spinner = """
|
| 629 |
+
<div class="validation-progress">
|
| 630 |
+
<div class="loading-spinner"></div>
|
| 631 |
+
<div>
|
| 632 |
+
<div>Running records generation test...</div>
|
| 633 |
+
<div>This test tries to load a sample of the data and may be slow for large datasets.</div>
|
| 634 |
+
</div>
|
| 635 |
+
</div>
|
| 636 |
+
"""
|
| 637 |
+
yield [
|
| 638 |
+
build_results_html(results),
|
| 639 |
+
gr.update(visible=True, value=records_spinner),
|
| 640 |
+
gr.update(visible=False),
|
| 641 |
+
None,
|
| 642 |
+
None
|
| 643 |
+
]
|
| 644 |
+
|
| 645 |
+
# Check 4: Records (slow)
|
| 646 |
+
set_active_token(oauth_token.token if oauth_token else None)
|
| 647 |
+
try:
|
| 648 |
+
records_valid, records_message, records_status = validate_records(json_data)
|
| 649 |
+
finally:
|
| 650 |
+
clear_active_token()
|
| 651 |
+
records_message = records_message.replace("\n✓\n", "\n")
|
| 652 |
+
results.append(("Records Generation Test", records_valid, records_message, records_status))
|
| 653 |
+
|
| 654 |
+
# Generate report
|
| 655 |
+
report = generate_validation_report(filename, json_data, results)
|
| 656 |
+
dataset_name = json_data.get('name', 'unnamed') if isinstance(json_data, dict) else 'unnamed'
|
| 657 |
report_filename = f"report_croissant-validation_{dataset_name}.md"
|
| 658 |
if report:
|
| 659 |
with open(report_filename, "w") as f:
|
| 660 |
f.write(report)
|
| 661 |
|
|
|
|
| 662 |
yield [
|
| 663 |
+
build_results_html(results),
|
| 664 |
+
gr.update(visible=False),
|
| 665 |
+
gr.update(visible=True) if report else gr.update(visible=False),
|
| 666 |
+
report if report else None,
|
| 667 |
+
report_filename if report else None
|
| 668 |
]
|
| 669 |
|
| 670 |
# Connect UI events to functions with updated outputs
|
|
|
|
| 710 |
# Footer
|
| 711 |
gr.HTML("""
|
| 712 |
<div style="text-align: center; margin-top: 20px;">
|
| 713 |
+
<p>Learn more about 🥐 <a href="https://github.com/mlcommons/croissant" target="_blank">Croissant</a>.</p>
|
| 714 |
</div>
|
| 715 |
""")
|
| 716 |
|
|
|
|
| 718 |
<div class="progress-status" style="text-align: left; color: #d35400;">
|
| 719 |
⚠️ It is possible that this validator is currently being used by a lot of people at the same time, which may trigger rate limiting by the platform hosting your data.
|
| 720 |
The app will then try again and may get into a very long loop. If it takes too long to run, we recommend using any of the following options:
|
| 721 |
+
<ul style="text-align:left; margin: 12px auto 0; display:inline-block;">
|
| 722 |
+
<li>🤗 If your dataset is on Hugging Face, please log in first to avoid rate limiting issues.</li>
|
| 723 |
<li>🔁 Click the button with the three dots (⋯) above and select "Duplicate this Space" to run this app in your own Hugging Face space.</li>
|
| 724 |
<li>💻 Click the button with the three dots (⋯) above and select "Run Locally" and then "Clone (git)" to get instructions to run the checker locally. You can also use docker option (you don't need the tokens).</li>
|
| 725 |
<li>🥐 Run the Croissant validation code yourself (<a href="https://github.com/mlcommons/croissant" target="_blank">GitHub</a>), e.g. with <a href="https://github.com/mlcommons/croissant/tree/7a632f34438e9c8e3812c6a0049898560259c6d4/python/mlcroissant/mlcroissant/scripts" target="_blank">these scripts</a> (validate and load).</li>
|
requirements.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
mlcroissant>=1.0.17
|
| 2 |
pydantic>=2.10.6
|
| 3 |
-
gradio>=3.50.2
|
| 4 |
func_timeout
|
| 5 |
requests
|
| 6 |
huggingface-hub>=0.30.1
|
|
|
|
| 1 |
mlcroissant>=1.0.17
|
| 2 |
pydantic>=2.10.6
|
| 3 |
+
gradio[oauth]>=3.50.2
|
| 4 |
func_timeout
|
| 5 |
requests
|
| 6 |
huggingface-hub>=0.30.1
|
validation.py
CHANGED
|
@@ -1,9 +1,45 @@
|
|
| 1 |
import mlcroissant._src.operation_graph.operations.download as dl_mod
|
|
|
|
| 2 |
import requests
|
| 3 |
import os
|
| 4 |
|
| 5 |
-
|
| 6 |
-
print("[DEBUG] HF_TOKEN is", "set" if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
# Only send HF credentials when downloading from huggingface.co.
|
| 9 |
# The default get_basic_auth_from_env() applies auth to ALL URLs, which
|
|
@@ -12,9 +48,10 @@ _orig_download_from_http = dl_mod.Download._download_from_http
|
|
| 12 |
|
| 13 |
def _hf_aware_download(self, filepath):
|
| 14 |
url = self.node.content_url or ""
|
| 15 |
-
|
|
|
|
| 16 |
os.environ["CROISSANT_BASIC_AUTH_USERNAME"] = "hf_user"
|
| 17 |
-
os.environ["CROISSANT_BASIC_AUTH_PASSWORD"] =
|
| 18 |
else:
|
| 19 |
os.environ.pop("CROISSANT_BASIC_AUTH_USERNAME", None)
|
| 20 |
os.environ.pop("CROISSANT_BASIC_AUTH_PASSWORD", None)
|
|
@@ -134,7 +171,6 @@ RAI_FIELDS = [
|
|
| 134 |
"rai:dataUseCases",
|
| 135 |
"rai:dataSocialImpact",
|
| 136 |
"rai:hasSyntheticData",
|
| 137 |
-
"prov:wasGeneratedBy",
|
| 138 |
]
|
| 139 |
|
| 140 |
RAI_GUIDELINES_URL = "https://neurips.cc/Conferences/2026/EvaluationsDatasetsHosting"
|
|
|
|
| 1 |
import mlcroissant._src.operation_graph.operations.download as dl_mod
|
| 2 |
+
import requests as _requests_mod
|
| 3 |
import requests
|
| 4 |
import os
|
| 5 |
|
| 6 |
+
_SERVER_HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 7 |
+
print("[DEBUG] HF_TOKEN is", "set" if _SERVER_HF_TOKEN else "missing")
|
| 8 |
+
|
| 9 |
+
# _active_token holds the HF token to use for the current validation request.
|
| 10 |
+
# It defaults to the server-level HF_TOKEN but can be overridden per-request
|
| 11 |
+
# via set_active_token() so that logged-in users' own tokens are used instead.
|
| 12 |
+
_active_token: dict = {"token": _SERVER_HF_TOKEN}
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def set_active_token(token: str | None) -> None:
|
| 16 |
+
"""Set the HF token to use for the current validation request."""
|
| 17 |
+
_active_token["token"] = token if token else _SERVER_HF_TOKEN
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def clear_active_token() -> None:
|
| 21 |
+
"""Reset the HF token back to the server-level default."""
|
| 22 |
+
_active_token["token"] = _SERVER_HF_TOKEN
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Patch requests.Session.send to fail immediately on HTTP 429 instead of
|
| 26 |
+
# letting mlcroissant / fsspec / huggingface_hub retry silently for minutes.
|
| 27 |
+
_orig_session_send = _requests_mod.Session.send
|
| 28 |
+
|
| 29 |
+
def _rate_limit_aware_send(self, request, **kwargs):
|
| 30 |
+
response = _orig_session_send(self, request, **kwargs)
|
| 31 |
+
if response.status_code == 429:
|
| 32 |
+
retry_after = response.headers.get("Retry-After", "unknown")
|
| 33 |
+
raise _requests_mod.exceptions.HTTPError(
|
| 34 |
+
f"HTTP 429 Too Many Requests for {request.url}. "
|
| 35 |
+
f"Retry-After: {retry_after}s. "
|
| 36 |
+
"You are being rate limited. Log in with your Hugging Face account to avoid this.",
|
| 37 |
+
response=response,
|
| 38 |
+
)
|
| 39 |
+
return response
|
| 40 |
+
|
| 41 |
+
_requests_mod.Session.send = _rate_limit_aware_send
|
| 42 |
+
|
| 43 |
|
| 44 |
# Only send HF credentials when downloading from huggingface.co.
|
| 45 |
# The default get_basic_auth_from_env() applies auth to ALL URLs, which
|
|
|
|
| 48 |
|
| 49 |
def _hf_aware_download(self, filepath):
|
| 50 |
url = self.node.content_url or ""
|
| 51 |
+
token = _active_token["token"]
|
| 52 |
+
if token and "huggingface.co" in url:
|
| 53 |
os.environ["CROISSANT_BASIC_AUTH_USERNAME"] = "hf_user"
|
| 54 |
+
os.environ["CROISSANT_BASIC_AUTH_PASSWORD"] = token
|
| 55 |
else:
|
| 56 |
os.environ.pop("CROISSANT_BASIC_AUTH_USERNAME", None)
|
| 57 |
os.environ.pop("CROISSANT_BASIC_AUTH_PASSWORD", None)
|
|
|
|
| 171 |
"rai:dataUseCases",
|
| 172 |
"rai:dataSocialImpact",
|
| 173 |
"rai:hasSyntheticData",
|
|
|
|
| 174 |
]
|
| 175 |
|
| 176 |
RAI_GUIDELINES_URL = "https://neurips.cc/Conferences/2026/EvaluationsDatasetsHosting"
|