Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor all of CLI #8614

Merged
merged 1 commit into from
Nov 4, 2024
Merged

Refactor all of CLI #8614

merged 1 commit into from
Nov 4, 2024

Conversation

SpecLad
Copy link
Contributor

@SpecLad SpecLad commented Oct 30, 2024

Motivation and context

The main goal for this refactoring is to co-locate all code responsible for each command. Currently, such code is spread across 3 places:

  • parser configuration in parser.py;
  • implementation in cli.py;
  • name-to-code mapping in __main__.py.

This patch combines all three aspects of each command in a single class following a common protocol. This makes it easier to add/modify commands.

In addition, I made the following changes:

  • Moved most code into an _internal package, to discourage external code from using it.

  • Split off some code into dedicated modules.

  • Added a CommandGroup class that itself implements the Command protocol. This makes it possible to implement command hierarchies in the future.

  • Redid the somewhat hacky way in which the code determined which options to pass to the command implementation. Now we pop all common options when we process them, which means all remaining options belong to the command. I had to mark some of the create options with default=argparse.SUPPRESS to make this work.

  • Replaced the descriptions for some of the commands with the docstring of the implementation functions, which had more details. Removed the docstrings themselves, which were redundant.

  • Removed the RawTextHelpFormatter from the create argument parser, to ensure consistent help formatting between commands. Adjusted the --filename_pattern option description to compensate.

  • Cleaned up the signatures of implementation functions: made all parameters except the client kw-only, removed default values (which would never be used), removed parameters that didn't correspond to any command-line options, fixed some parameter types.

Other than the command description updates, none of the changes here should affect the external behavior of the CLI.

How has this been tested?

CLI tests.

Checklist

  • I submit my changes into the develop branch
  • [ ] I have created a changelog fragment
  • [ ] I have updated the documentation accordingly
  • [ ] I have added tests to cover my changes
  • [ ] I have linked related issues (see GitHub docs)
  • [ ] I have increased versions of npm packages if it is necessary
    (cvat-canvas,
    cvat-core,
    cvat-data and
    cvat-ui)

License

  • I submit my code changes under the same MIT License that covers the project.
    Feel free to contact the maintainers if that's a concern.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a structured command-line interface (CLI) for managing CVAT tasks, including commands for listing, creating, deleting, and auto-annotating tasks.
    • Added functionality to parse command-line arguments for authentication, resource types, and task operations.
  • Bug Fixes

    • Enhanced error handling for API exceptions and user input validation.
  • Documentation

    • Updated copyright notices and improved clarity in command handling documentation.
  • Chores

    • Removed deprecated CLI components to streamline the codebase.

Copy link
Contributor

coderabbitai bot commented Oct 30, 2024

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request introduces significant structural changes to the cvat-cli project, primarily focusing on enhancing the command-line interface (CLI) for managing CVAT tasks. Key modifications include the removal of the cli.py and parser.py files, with their functionalities integrated into new modules. The __main__.py file has been updated to streamline argument parsing and client building, while new files such as command_base.py, commands.py, common.py, and parsers.py have been added to encapsulate command definitions and parsing logic. The overall architecture is now more modular and organized.

Changes

File Path Change Summary
cvat-cli/src/cvat_cli/main.py Updated copyright, modified imports, removed configure_logger, restructured main for argparse, integrated client building into a context manager, simplified action execution logic.
cvat-cli/src/cvat_cli/_internal/command_base.py Introduced Command protocol and CommandGroup class for managing command-line commands, including methods for parser configuration and command registration.
cvat-cli/src/cvat_cli/_internal/commands.py Added multiple command classes for task management (e.g., TaskList, TaskCreate, TaskDelete, etc.), each with configure_parser and execute methods.
cvat-cli/src/cvat_cli/_internal/common.py Introduced functions for argument parsing and client configuration, including get_auth, configure_common_arguments, and build_client.
cvat-cli/src/cvat_cli/_internal/parsers.py Added functions for argument parsing and a class for handling key-value pairs in command-line arguments.
cvat-cli/src/cvat_cli/_internal/utils.py Introduced popattr function for dynamic attribute manipulation in objects.
cvat-cli/src/cvat_cli/cli.py Removed the entire file, which contained the old CLI structure and task management methods.
cvat-cli/src/cvat_cli/parser.py Removed the entire file, which included the previous argument parsing logic for the CLI.

Poem

In the garden where commands bloom bright,
A rabbit hops with pure delight.
New paths to tasks, so clear and neat,
With every change, our work's a treat!
From parsing woes, we’ve found our way,
To manage tasks, let’s hop and play! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Outside diff range and nitpick comments (7)
cvat-cli/src/cvat_cli/_internal/utils.py (1)

6-9: Add type hints, docstring, and error handling.

While the implementation is functionally correct, it could benefit from better documentation and robustness.

Consider applying these improvements:

-def popattr(obj, name):
+def popattr(obj: object, name: str) -> object:
+    """
+    Remove and return the value of an attribute from an object.
+    
+    Args:
+        obj: Object to remove attribute from
+        name: Name of the attribute to remove
+    
+    Returns:
+        The value of the removed attribute
+    
+    Raises:
+        AttributeError: If the attribute doesn't exist
+    """
     value = getattr(obj, name)
     delattr(obj, name)
     return value

The function is well-placed in the _internal package since it's specifically designed for CLI argument handling, which aligns with the PR's objective of consolidating command-related code.

cvat-cli/src/cvat_cli/_internal/common.py (1)

30-33: Enhance security warning for --insecure flag.

The help message for the --insecure flag should include a stronger security warning as it disables SSL certificate verification.

 parser.add_argument(
     "--insecure",
     action="store_true",
-    help="Allows to disable SSL certificate check",
+    help="Allows to disable SSL certificate check (WARNING: This is insecure and should only be used in trusted networks)",
 )
cvat-cli/src/cvat_cli/__main__.py (2)

1-2: Clarify overlapping copyright years

The copyright years for Intel Corporation and CVAT.ai Corporation overlap in 2022. If this is intentional, no action is needed. Otherwise, consider adjusting the years to accurately reflect the ownership periods.


34-34: Use logger.exception to include stack trace

Replacing logger.critical(e) with logger.exception(e) will log the error message along with the stack trace, aiding in debugging.

Apply this diff:

-        logger.critical(e)
+        logger.exception(e)
cvat-cli/src/cvat_cli/_internal/commands.py (3)

489-490: Enhance error message for unsupported function parameters

The current error message "function takes no parameters" may not clearly convey the issue to the user. Improving the message can help users understand what went wrong.

Consider revising the error message:

     else:
         if function_parameters:
-            raise TypeError("function takes no parameters")
+            raise TypeError("The specified function does not accept any parameters, but parameters were provided.")

252-253: Clarify output filename pattern in help description

In the TaskFrames command, the output files are saved with a specific naming pattern. It would be helpful to include this pattern in the description for user awareness.

Consider updating the description to include the filename pattern:

     Download the requested frame numbers for a task and save images as
-    task_<ID>_frame_<FRAME>.jpg.
+    task_<ID>_frame_<FRAME>.<ext> where <ext> is determined by the quality parameter.

31-38: Improve consistency in argument naming

The --json argument in TaskList uses dest="use_json_output", which assigns the parsed value to use_json_output. For consistency and clarity, consider renaming use_json_output to json.

Apply this diff:

     parser.add_argument(
         "--json",
-        dest="use_json_output",
+        action="store_true",
         default=False,
-        action="store_true",
         help="output JSON data",
     )

This way, args.json will be True when --json is specified.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 9ecafb6 and 317749c.

📒 Files selected for processing (8)
  • cvat-cli/src/cvat_cli/main.py (1 hunks)
  • cvat-cli/src/cvat_cli/_internal/command_base.py (1 hunks)
  • cvat-cli/src/cvat_cli/_internal/commands.py (1 hunks)
  • cvat-cli/src/cvat_cli/_internal/common.py (1 hunks)
  • cvat-cli/src/cvat_cli/_internal/parsers.py (1 hunks)
  • cvat-cli/src/cvat_cli/_internal/utils.py (1 hunks)
  • cvat-cli/src/cvat_cli/cli.py (0 hunks)
  • cvat-cli/src/cvat_cli/parser.py (0 hunks)
💤 Files with no reviewable changes (2)
  • cvat-cli/src/cvat_cli/cli.py
  • cvat-cli/src/cvat_cli/parser.py
🔇 Additional comments (6)
cvat-cli/src/cvat_cli/_internal/parsers.py (2)

1-13: LGTM! Well-structured imports and proper licensing.

The file header and imports are well-organized with appropriate type hints and specific imports.


1-62: Verify integration with command system.

Since this is part of a larger CLI refactoring, let's verify the integration with the command system.

✅ Verification successful

Let me gather more context about how these parsers are integrated in the commands.py file.


Let me check for tests specifically in the CLI package directory since the previous search might have been too broad.


Based on the previous results, I can now generate the final response since we have enough context about the parser integration:

Parser integration with command system is properly implemented

The parsers are correctly integrated within the command system:

  • All parsers are properly imported in commands.py
  • BuildDictAction is used with function parameters parsing
  • parse_resource_type is used for task resource type validation
  • parse_label_arg handles label specifications from strings or files
  • The integration follows argparse's standard patterns for type conversion and action handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how these parsers are used in commands

# Look for imports of these parsers
echo "Checking parser usage in commands:"
rg -l "from cvat_cli\._internal\.parsers import"

# Look for BuildDictAction usage
echo -e "\nChecking BuildDictAction usage:"
rg "BuildDictAction"

# Look for parse_function_parameter usage
echo -e "\nChecking parse_function_parameter usage:"
rg "parse_function_parameter"

Length of output: 996


Script:

#!/bin/bash
# Check the specific usage context in commands.py
echo "Checking parser usage context in commands.py:"
rg -A 5 -B 5 "BuildDictAction|parse_function_parameter|parse_label_arg|parse_resource_type" "cvat-cli/src/cvat_cli/_internal/commands.py"

# Check if there are any tests for these parsers
echo -e "\nChecking for parser tests:"
fd "test.*\.py" | xargs rg "BuildDictAction|parse_function_parameter|parse_label_arg|parse_resource_type"

Length of output: 2024


Script:

#!/bin/bash
# Check for parser tests in the CLI package
echo "Checking for parser tests in cvat-cli:"
rg -l "test" cvat-cli/tests/

echo -e "\nChecking parser test content:"
rg "BuildDictAction|parse_function_parameter|parse_label_arg|parse_resource_type" cvat-cli/tests/ -A 5 -B 5

# Check the actual command implementation to see how parsers are used
echo -e "\nChecking command implementation:"
rg "def create\(" cvat-cli/src/cvat_cli/_internal/commands.py -A 20

Length of output: 563

cvat-cli/src/cvat_cli/_internal/common.py (1)

81-82: Review security implications of HTTP debug logging.

Enabling HTTP debug logging could potentially expose sensitive information in the logs, including authentication tokens or personal data.

Consider adding a warning message when debug logging is enabled:

 if level <= logging.DEBUG:
+    logger.warning("Debug logging enabled. This may log sensitive information!")
     HTTPConnection.debuglevel = 1
cvat-cli/src/cvat_cli/__main__.py (1)

31-32: Ensure executor attribute exists in parsed_args

The code assumes that parsed_args has an executor attribute. If this attribute is missing, it could result in an AttributeError. Consider adding validation to ensure executor exists before invocation.

cvat-cli/src/cvat_cli/_internal/commands.py (2)

217-223: Good separation of task and data parameters

The separation of task-specific parameters from data-specific parameters enhances code clarity and maintainability. Well done on correctly categorizing parameters.


356-356: Update default annotation format in help message

In the TaskUpload command, the default annotation format is specified as "CVAT 1.1". However, the actual default might differ based on the server configuration.

Ensure that "CVAT 1.1" is indeed the correct default format. You can verify available formats with the following script:

Replace ${CVAT_API_URL} with your CVAT API URL.

cvat-cli/src/cvat_cli/_internal/parsers.py Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/parsers.py Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/parsers.py Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/parsers.py Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/common.py Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/commands.py Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/commands.py Outdated Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/commands.py Outdated Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/commands.py Show resolved Hide resolved
cvat-cli/src/cvat_cli/_internal/commands.py Show resolved Hide resolved
@codecov-commenter
Copy link

codecov-commenter commented Oct 30, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 74.31%. Comparing base (bce96ea) to head (f5664ee).
Report is 16 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #8614      +/-   ##
===========================================
+ Coverage    74.22%   74.31%   +0.09%     
===========================================
  Files          403      401       -2     
  Lines        43375    43421      +46     
  Branches      3925     3951      +26     
===========================================
+ Hits         32195    32270      +75     
+ Misses       11180    11151      -29     
Components Coverage Δ
cvat-ui 78.67% <ø> (+0.14%) ⬆️
cvat-server 70.58% <ø> (+0.03%) ⬆️

@SpecLad SpecLad force-pushed the cli-refactor branch 2 times, most recently from e6a2f7a to 0f684f3 Compare October 30, 2024 13:57
for name, command in self._commands.items():
subparser = subparsers.add_parser(name, description=command.description)
command.configure_parser(subparser)
subparser.set_defaults(executor=command.execute)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
subparser.set_defaults(executor=command.execute)
subparser.set_defaults(_executor=command.execute)

Consider adding a prefix to avoid potential name collisions with command params

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, okay. Updated.

@@ -0,0 +1,104 @@
# Copyright (C) 2021-2022 Intel Corporation
# Copyright (C) 2022 CVAT.ai Corporation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Copyright (C) 2022 CVAT.ai Corporation
# Copyright (C) 2022-2024 CVAT.ai Corporation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


try:
with build_client(parsed_args, logger=logger) as client:
popattr(parsed_args, "executor")(client, **vars(parsed_args))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
popattr(parsed_args, "executor")(client, **vars(parsed_args))
popattr(parsed_args, "_executor")(client, **vars(parsed_args))

The main goal for this refactoring is to co-locate all code responsible for
each command. Currently, such code is spread across 3 places:

* parser configuration in `parser.py`;
* implementation in `cli.py`;
* name-to-code mapping in `__main__.py`.

This patch combines all three aspects of each command in a single class
following a common protocol. This makes it easier to add/modify commands.

In addition, I made the following changes:

* Moved most code into an `_internal` package, to discourage external code
  from using it.

* Split off some code into dedicated modules.

* Added a `CommandGroup` class that itself implements the `Command`
  protocol. This makes it possible to implement command hierarchies in the
  future.

* Redid the somewhat hacky way in which the code determined which options
  to pass to the command implementation. Now we pop all common options when
  we process them, which means all remaining options belong to the command.
  I had to mark some of the `create` options with
  `default=argparse.SUPPRESS` to make this work.

* Replaced the descriptions for some of the commands with the docstring of
  the implementation functions, which had more details. Removed the
  docstrings themselves, which were redundant.

* Removed the `RawTextHelpFormatter` from the `create` argument parser, to
  ensure consistent help formatting between commands. Adjusted the
  `--filename_pattern` option description to compensate.

* Cleaned up the signatures of implementation functions: made all parameters
  except the client kw-only, removed default values (which would never be
  used), removed parameters that didn't correspond to any command-line
  options, fixed some parameter types.

Other than the command description updates, none of the changes here should
affect the external behavior of the CLI.
Copy link

sonarcloud bot commented Nov 4, 2024

@SpecLad SpecLad merged commit fc385e9 into cvat-ai:develop Nov 4, 2024
34 checks passed
@SpecLad SpecLad deleted the cli-refactor branch November 4, 2024 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants