|
1 | | -"""Command Line Interface for `openml` to configure its settings.""" |
| 1 | +"""Command Line Interface for `openml` to configure settings and browse models.""" |
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
|
9 | 9 | from typing import Callable |
10 | 10 | from urllib.parse import urlparse |
11 | 11 |
|
| 12 | +import openml |
12 | 13 | from openml import config |
13 | 14 |
|
14 | 15 |
|
@@ -327,12 +328,144 @@ def not_supported_yet(_: str) -> None: |
327 | 328 | set_field_function(args.value) |
328 | 329 |
|
329 | 330 |
|
| 331 | +def models_list(args: argparse.Namespace) -> None: |
| 332 | + """List flows (models) from OpenML with optional filtering.""" |
| 333 | + try: |
| 334 | + flows_df = openml.flows.list_flows( |
| 335 | + offset=args.offset, |
| 336 | + size=args.size, |
| 337 | + tag=args.tag, |
| 338 | + uploader=args.uploader, |
| 339 | + ) |
| 340 | + |
| 341 | + if flows_df.empty: |
| 342 | + print("No flows found matching the criteria.") |
| 343 | + return |
| 344 | + |
| 345 | + # Display flows in a readable format |
| 346 | + if args.format == "table": |
| 347 | + # Print as a formatted table |
| 348 | + print(f"\nFound {len(flows_df)} flow(s):\n") |
| 349 | + print(flows_df.to_string(index=False)) |
| 350 | + else: |
| 351 | + # Print in a more compact format |
| 352 | + print(f"\nFound {len(flows_df)} flow(s):\n") |
| 353 | + for _, row in flows_df.iterrows(): |
| 354 | + print(f"ID: {row['id']:>6} | {row['name']:<40} | Version: {row['version']}") |
| 355 | + if args.verbose: |
| 356 | + print(f" Full Name: {row['full_name']}") |
| 357 | + print(f" External Version: {row['external_version']}") |
| 358 | + print(f" Uploader: {row['uploader']}") |
| 359 | + print() |
| 360 | + |
| 361 | + except Exception as e: # noqa: BLE001 |
| 362 | + print(f"Error listing flows: {e}", file=sys.stderr) |
| 363 | + sys.exit(1) |
| 364 | + |
| 365 | + |
| 366 | +def models_info(args: argparse.Namespace) -> None: |
| 367 | + """Display detailed information about a specific flow (model).""" |
| 368 | + try: |
| 369 | + flow_id = int(args.flow_id) |
| 370 | + flow = openml.flows.get_flow(flow_id) |
| 371 | + |
| 372 | + # Display flow information using its __repr__ method |
| 373 | + print(flow) |
| 374 | + |
| 375 | + # Additional information |
| 376 | + if flow.parameters: |
| 377 | + print("\nParameters:") |
| 378 | + for param_name, param_value in flow.parameters.items(): |
| 379 | + meta_info = flow.parameters_meta_info.get(param_name, {}) |
| 380 | + param_desc = meta_info.get("description", "No description") |
| 381 | + param_type = meta_info.get("data_type", "unknown") |
| 382 | + print(f" {param_name}: {param_value} ({param_type})") |
| 383 | + if param_desc != "No description": |
| 384 | + print(f" Description: {param_desc}") |
| 385 | + |
| 386 | + if flow.components: |
| 387 | + print(f"\nComponents: {len(flow.components)}") |
| 388 | + for comp_name, comp_flow in flow.components.items(): |
| 389 | + comp_id = comp_flow.flow_id if comp_flow.flow_id else "Not uploaded" |
| 390 | + print(f" {comp_name}: Flow ID {comp_id}") |
| 391 | + |
| 392 | + if flow.tags: |
| 393 | + print(f"\nTags: {', '.join(flow.tags)}") |
| 394 | + |
| 395 | + except ValueError: |
| 396 | + print( |
| 397 | + f"Error: '{args.flow_id}' is not a valid flow ID. Please provide a number.", |
| 398 | + file=sys.stderr, |
| 399 | + ) |
| 400 | + sys.exit(1) |
| 401 | + except Exception as e: # noqa: BLE001 |
| 402 | + print(f"Error retrieving flow information: {e}", file=sys.stderr) |
| 403 | + sys.exit(1) |
| 404 | + |
| 405 | + |
| 406 | +def models_search(args: argparse.Namespace) -> None: |
| 407 | + """Search for flows (models) by name.""" |
| 408 | + try: |
| 409 | + # Get all flows (or a reasonable subset) |
| 410 | + flows_df = openml.flows.list_flows( |
| 411 | + offset=0, |
| 412 | + size=args.max_results if args.max_results else 1000, |
| 413 | + tag=args.tag, |
| 414 | + ) |
| 415 | + |
| 416 | + if flows_df.empty: |
| 417 | + print("No flows found.") |
| 418 | + return |
| 419 | + |
| 420 | + # Filter by search query (case-insensitive) |
| 421 | + query_lower = args.query.lower() |
| 422 | + matching_flows = flows_df[ |
| 423 | + flows_df["name"].str.lower().str.contains(query_lower, na=False) |
| 424 | + | flows_df["full_name"].str.lower().str.contains(query_lower, na=False) |
| 425 | + ] |
| 426 | + |
| 427 | + if matching_flows.empty: |
| 428 | + print(f"No flows found matching '{args.query}'.") |
| 429 | + return |
| 430 | + |
| 431 | + print(f"\nFound {len(matching_flows)} flow(s) matching '{args.query}':\n") |
| 432 | + for _, row in matching_flows.iterrows(): |
| 433 | + print(f"ID: {row['id']:>6} | {row['name']:<40} | Version: {row['version']}") |
| 434 | + if args.verbose: |
| 435 | + print(f" Full Name: {row['full_name']}") |
| 436 | + print(f" External Version: {row['external_version']}") |
| 437 | + print(f" Uploader: {row['uploader']}") |
| 438 | + print() |
| 439 | + |
| 440 | + except Exception as e: # noqa: BLE001 |
| 441 | + print(f"Error searching flows: {e}", file=sys.stderr) |
| 442 | + sys.exit(1) |
| 443 | + |
| 444 | + |
| 445 | +def models(args: argparse.Namespace) -> None: |
| 446 | + """Handle models subcommands.""" |
| 447 | + subcommands = { |
| 448 | + "list": models_list, |
| 449 | + "info": models_info, |
| 450 | + "search": models_search, |
| 451 | + } |
| 452 | + |
| 453 | + if args.models_subcommand in subcommands: |
| 454 | + subcommands[args.models_subcommand](args) |
| 455 | + else: |
| 456 | + print(f"Unknown models subcommand: {args.models_subcommand}") |
| 457 | + sys.exit(1) |
| 458 | + |
| 459 | + |
330 | 460 | def main() -> None: |
331 | | - subroutines = {"configure": configure} |
| 461 | + subroutines = {"configure": configure, "models": models} |
332 | 462 |
|
333 | | - parser = argparse.ArgumentParser() |
334 | | - subparsers = parser.add_subparsers(dest="subroutine") |
| 463 | + parser = argparse.ArgumentParser( |
| 464 | + description="OpenML Python CLI - Access OpenML datasets, tasks, flows, and more.", |
| 465 | + ) |
| 466 | + subparsers = parser.add_subparsers(dest="subroutine", help="Available commands") |
335 | 467 |
|
| 468 | + # Configure command |
336 | 469 | parser_configure = subparsers.add_parser( |
337 | 470 | "configure", |
338 | 471 | description="Set or read variables in your configuration file. For more help also see " |
@@ -360,8 +493,105 @@ def main() -> None: |
360 | 493 | help="The value to set the FIELD to.", |
361 | 494 | ) |
362 | 495 |
|
| 496 | + # Models command |
| 497 | + parser_models = subparsers.add_parser( |
| 498 | + "models", |
| 499 | + description="Browse and search OpenML flows (models).", |
| 500 | + ) |
| 501 | + |
| 502 | + models_subparsers = parser_models.add_subparsers( |
| 503 | + dest="models_subcommand", |
| 504 | + help="Available models subcommands", |
| 505 | + ) |
| 506 | + |
| 507 | + # Models list command |
| 508 | + parser_models_list = models_subparsers.add_parser( |
| 509 | + "list", |
| 510 | + description="List flows (models) from OpenML.", |
| 511 | + ) |
| 512 | + parser_models_list.add_argument( |
| 513 | + "--offset", |
| 514 | + type=int, |
| 515 | + default=None, |
| 516 | + help="Number of flows to skip (for pagination).", |
| 517 | + ) |
| 518 | + parser_models_list.add_argument( |
| 519 | + "--size", |
| 520 | + type=int, |
| 521 | + default=None, |
| 522 | + help="Maximum number of flows to return.", |
| 523 | + ) |
| 524 | + parser_models_list.add_argument( |
| 525 | + "--tag", |
| 526 | + type=str, |
| 527 | + default=None, |
| 528 | + help="Filter flows by tag.", |
| 529 | + ) |
| 530 | + parser_models_list.add_argument( |
| 531 | + "--uploader", |
| 532 | + type=str, |
| 533 | + default=None, |
| 534 | + help="Filter flows by uploader ID.", |
| 535 | + ) |
| 536 | + parser_models_list.add_argument( |
| 537 | + "--format", |
| 538 | + type=str, |
| 539 | + choices=["table", "compact"], |
| 540 | + default="compact", |
| 541 | + help="Output format (default: compact).", |
| 542 | + ) |
| 543 | + parser_models_list.add_argument( |
| 544 | + "--verbose", |
| 545 | + "-v", |
| 546 | + action="store_true", |
| 547 | + help="Show detailed information for each flow.", |
| 548 | + ) |
| 549 | + |
| 550 | + # Models info command |
| 551 | + parser_models_info = models_subparsers.add_parser( |
| 552 | + "info", |
| 553 | + description="Display detailed information about a specific flow (model).", |
| 554 | + ) |
| 555 | + parser_models_info.add_argument( |
| 556 | + "flow_id", |
| 557 | + type=str, |
| 558 | + help="The ID of the flow to display.", |
| 559 | + ) |
| 560 | + |
| 561 | + # Models search command |
| 562 | + parser_models_search = models_subparsers.add_parser( |
| 563 | + "search", |
| 564 | + description="Search for flows (models) by name.", |
| 565 | + ) |
| 566 | + parser_models_search.add_argument( |
| 567 | + "query", |
| 568 | + type=str, |
| 569 | + help="Search query (searches in flow names).", |
| 570 | + ) |
| 571 | + parser_models_search.add_argument( |
| 572 | + "--max-results", |
| 573 | + type=int, |
| 574 | + default=None, |
| 575 | + help="Maximum number of results to search through (default: 1000).", |
| 576 | + ) |
| 577 | + parser_models_search.add_argument( |
| 578 | + "--tag", |
| 579 | + type=str, |
| 580 | + default=None, |
| 581 | + help="Filter flows by tag before searching.", |
| 582 | + ) |
| 583 | + parser_models_search.add_argument( |
| 584 | + "--verbose", |
| 585 | + "-v", |
| 586 | + action="store_true", |
| 587 | + help="Show detailed information for each flow.", |
| 588 | + ) |
| 589 | + |
363 | 590 | args = parser.parse_args() |
364 | | - subroutines.get(args.subroutine, lambda _: parser.print_help())(args) |
| 591 | + if args.subroutine: |
| 592 | + subroutines.get(args.subroutine, lambda _: parser.print_help())(args) |
| 593 | + else: |
| 594 | + parser.print_help() |
365 | 595 |
|
366 | 596 |
|
367 | 597 | if __name__ == "__main__": |
|
0 commit comments