JoaquinVanschoren commited on
Commit
b81d3dd
·
1 Parent(s): 757c484

Scalability improvement: HF login, dynamic test updates, improved warnings for downloading issues

Browse files
Files changed (4) hide show
  1. README.md +4 -0
  2. app.py +149 -57
  3. requirements.txt +1 -1
  4. 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: Records validation (with timeout-safe and error-specific logic)
 
 
 
 
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 records can be generated within a reasonable time
72
- 4. If all required Responsible AI metadata fields are present
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
- return [
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
- return [
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
- # Check 4: Responsible AI metadata
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
- # Generate report
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- return [
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
- return [
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
- return [
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
- return [
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
- 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
 
 
 
 
 
 
 
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 progress spinner
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=""), # validation_results
547
- gr.update(visible=True, value=progress_html), # validation_progress
548
- gr.update(visible=False), # report_group
549
- None, # report_text
550
- None # report_md
551
  ]
552
 
553
- # Process the file and get results
554
- results, report = process_file(file)
555
 
556
- # Extract dataset name from the JSON for the report filename
557
- try:
558
- with open(file.name, 'r') as f:
559
- json_data = json.load(f)
560
- dataset_name = json_data.get('name', 'unnamed')
561
- except:
562
- dataset_name = 'unnamed'
563
 
564
- # Save report to file with new naming convention
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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), # validation_results
573
- gr.update(visible=False), # validation_progress
574
- gr.update(visible=True) if report else gr.update(visible=False), # report_group
575
- report if report else None, # report_text
576
- report_filename if report else None # report_md
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: 0 auto; display:inline-block;">
 
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
- HF_TOKEN = os.environ.get("HF_TOKEN")
6
- print("[DEBUG] HF_TOKEN is", "set" if HF_TOKEN else "missing")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- if HF_TOKEN and "huggingface.co" in url:
 
16
  os.environ["CROISSANT_BASIC_AUTH_USERNAME"] = "hf_user"
17
- os.environ["CROISSANT_BASIC_AUTH_PASSWORD"] = HF_TOKEN
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"