Skip to content

Conversation

@cibere
Copy link
Owner

@cibere cibere commented Feb 6, 2025

Summary

Adds search groups to easily create a nested command structure

Changelog

Breaking Changes

New Features

Bug Fixes

Checklist

  • If code changes were made then they have been tested.
    • I have updated the documentation to reflect the changes.

Summary by CodeRabbit

  • New Features
    • Introduced advanced search grouping that enables hierarchical and flexible management of search operations.
    • Added a new SearchGroup class for structured command group creation.
  • Refactor
    • Streamlined utility setup for clearer and more consistent handling of function initialization.
  • Documentation
    • Added documentation for the SearchGroup class, including its attributes and members.

@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 2025

📝 Walkthrough

Walkthrough

The pull request introduces modifications across four files within the flogin package. An import statement is added in __init__.py to expose new search group functionality. A new SearchGroup class is defined in search_group.py to handle searches using prefixes, subhandlers, and callbacks. Additionally, the func_with_self class in utils.py is updated to include an owner parameter in its constructor, enhancing clarity in managing ownership. Documentation for the SearchGroup class is also added in api.rst.

Changes

File(s) Change Summary
flogin/init.py Added the import statement: "from .search_group import *" to expose new search group features.
flogin/search_group.py Added a new SearchGroup class extending SearchHandler. Implements initialization, condition checking, result creation, callback processing, subgroup creation, and retrieval of the search group's hierarchy.
flogin/utils.py Updated the func_with_self class signature to include a keyword-only "owner" parameter in the __init__ method, with a revised parameter layout for improved readability.
docs/api.rst Added documentation for the SearchGroup class, including an attributetable directive and an autoclass directive, specifying exclusions for callback and condition members.

Possibly related PRs

  • update decorators (again) #41: The changes in the main PR, which involve adding an import statement for the SearchGroup class, are related to the retrieved PR as it introduces the SearchGroup class itself in the flogin/search_group.py file.
  • Improve docs for beginners #37: The changes in the main PR, which involve adding an import statement for the SearchGroup class, are related to the retrieved PR as it documents the SearchGroup class and its members, indicating a direct connection at the code level.

Suggested labels

documentation

Poem

I'm a bunny coding through the night,
Hopping on changes that shine so bright,
New searches and owners, oh what delight!
With each code line I skip and bound,
My whiskers twitch to every update found 🐇.

Tip

🌐 Web search-backed reviews and chat
  • We have enabled web search-based reviews and chat for all users. This feature allows CodeRabbit to access the latest documentation and information on the web.
  • You can disable this feature by setting web_search: false in the knowledge_base settings.
  • Please share any feedback in the Discord discussion.
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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.

@cibere cibere self-assigned this Feb 6, 2025
@cibere cibere added the enhancement New feature or request label Feb 6, 2025
@cibere cibere linked an issue Feb 6, 2025 that may be closed by this pull request
Copy link

@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: 1

🧹 Nitpick comments (4)
flogin/search_group.py (2)

33-39: Add a class-level docstring for clarity.
Although the class is well-structured and typed, consider adding a top-level docstring to describe its purpose, usage, and main responsibilities. This will improve readability and maintainability.


200-238: Check for potential conflicts when creating subgroups.
The subgroup method allows prefix nesting. Consider validating that a newly created subgroup prefix does not collide with existing handlers or subgroups in self._subhandlers. This helps avoid accidental shadowing or confusing behavior.

flogin/__init__.py (1)

11-11: Avoid wildcard imports to prevent namespace clutter.
Importing using '*' can inadvertently pollute the namespace. Explicit imports are generally recommended to maintain clarity and avoid name collisions.

flogin/utils.py (1)

301-309: Enhance documentation for the new 'owner' parameter.
The addition of the keyword-only parameter owner improves clarity around instance ownership. Consider extending or adding docstrings to explain acceptable types, usage patterns, and implications if the owner is missing or not set.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bda3a99 and 27a05af.

📒 Files selected for processing (3)
  • flogin/__init__.py (1 hunks)
  • flogin/search_group.py (1 hunks)
  • flogin/utils.py (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Lint
flogin/search_group.py

[low] 102-102: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


[low] 103-103: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


[low] 108-108: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

🔇 Additional comments (2)
flogin/search_group.py (2)

40-48: Consider adding tests for prefix initialization errors.
The conditional logic raising a ValueError is appropriate. However, adding unit tests that verify behavior for missing prefix arguments ensures stronger coverage of edge cases and clarifies contract expectations.


66-71: Validate query.text before calling startswith.
Although the code handles IndexError, an empty or None query.text could still pose edge cases. Consider validating query.text isn't empty or None before using string operations to avoid hidden exceptions or subtle bugs.

@cibere
Copy link
Owner Author

cibere commented Feb 6, 2025

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 2025

✅ Actions performed

Full review triggered.

Copy link

@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: 2

🧹 Nitpick comments (2)
flogin/search_group.py (1)

159-161: Consider adding error handling for empty subgroups.

The root_handler method assumes subgroups exist but doesn't handle the case when _subgroups is empty.

Consider adding a check:

async def root_handler(self, query: Query) -> list[Result]:
+   if not self._subgroups:
+       return []
    return [self.create_result(key, query) for key in self._subgroups]
flogin/__init__.py (1)

11-11: Consider updating __all__ to expose new SearchGroup class.

While the wildcard import from search_group is added, consider explicitly listing SearchGroup in __all__ for better control over the public API.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bda3a99 and 6162f4b.

📒 Files selected for processing (4)
  • docs/api.rst (1 hunks)
  • flogin/__init__.py (1 hunks)
  • flogin/search_group.py (1 hunks)
  • flogin/utils.py (1 hunks)
🧰 Additional context used
📓 Learnings (1)
flogin/search_group.py (1)
Learnt from: cibere
PR: cibere/flogin#71
File: flogin/search_group.py:102-108
Timestamp: 2025-02-06T22:49:41.181Z
Learning: In flogin, assert statements in callback functions (e.g., `assert self.plugin`, `assert self.plugin.last_query`) are used for Pyright type narrowing and not for runtime validation, as these values are guaranteed to be present when flogin calls the callbacks.
🪛 GitHub Actions: build
flogin/search_group.py

[error] 5-5: ImportError: cannot import name 'PluginT' from 'flogin._types' (unknown location). Did you mean: 'plugin'?

🪛 GitHub Actions: Lint
flogin/search_group.py

[low] 142-142: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


[low] 143-143: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


[low] 148-148: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

🔇 Additional comments (4)
flogin/search_group.py (2)

23-78: Well-documented class with clear examples!

The docstring provides comprehensive examples of using the SearchGroup class for creating nested command structures. The examples demonstrate both basic subcommand usage and nested subgroup creation.


142-143: Assert statements are correctly used for type narrowing.

Based on the retrieved learning from @cibere, these assert statements are intentionally used for Pyright type narrowing and not for runtime validation, as these values are guaranteed to be present when flogin calls the callbacks.

Also applies to: 148-148

🧰 Tools
🪛 GitHub Actions: Lint

[low] 142-142: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


[low] 143-143: Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

flogin/utils.py (1)

301-309: Good improvement to owner initialization!

The changes make the owner initialization more flexible by allowing it to be set during instantiation while maintaining backward compatibility with the MISSING sentinel.

docs/api.rst (1)

106-111: Documentation properly structured and placed!

The SearchGroup documentation is correctly placed in the Search Handlers section and properly uses attributetable and autoclass directives.

Comment on lines +163 to +177
parts = query.text.split(self.sep)
query._data["search"] = query.text.removeprefix(self.prefix).removeprefix(
self.sep
)

try:
handler = self._subgroups[parts[1]]
except (KeyError, IndexError):
handler = self.root_handler

if isinstance(handler, SearchGroup):
handler.plugin = self.plugin
handler = handler.callback

return handler(query)
Copy link

@coderabbitai coderabbitai bot Feb 6, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for invalid query format.

The callback method splits the query text but doesn't validate the format. This could lead to unexpected behavior with malformed queries.

Consider adding validation:

def callback(self, query: Query) -> SearchHandlerCallbackReturns:
+   if not query.text:
+       return self.root_handler(query)
+
    parts = query.text.split(self.sep)
    query._data["search"] = query.text.removeprefix(self.prefix).removeprefix(
        self.sep
    )

    try:
        handler = self._subgroups[parts[1]]
    except (KeyError, IndexError):
        handler = self.root_handler

    if isinstance(handler, SearchGroup):
        handler.plugin = self.plugin
        handler = handler.callback

    return handler(query)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
parts = query.text.split(self.sep)
query._data["search"] = query.text.removeprefix(self.prefix).removeprefix(
self.sep
)
try:
handler = self._subgroups[parts[1]]
except (KeyError, IndexError):
handler = self.root_handler
if isinstance(handler, SearchGroup):
handler.plugin = self.plugin
handler = handler.callback
return handler(query)
def callback(self, query: Query) -> SearchHandlerCallbackReturns:
if not query.text:
return self.root_handler(query)
parts = query.text.split(self.sep)
query._data["search"] = query.text.removeprefix(self.prefix).removeprefix(self.sep)
try:
handler = self._subgroups[parts[1]]
except (KeyError, IndexError):
handler = self.root_handler
if isinstance(handler, SearchGroup):
handler.plugin = self.plugin
handler = handler.callback
return handler(query)

Copy link
Owner Author

Choose a reason for hiding this comment

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

can you explain this more? I don't understand why this change would help

Copy link

Choose a reason for hiding this comment

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

@cibere Let me explain why this change would be helpful:

Currently, when an empty query is received, the code still tries to:

  1. Split the empty string ("") into parts
  2. Remove prefixes from an empty string
  3. Try to access parts[1] which fails because parts is just [""]
  4. Finally falls back to root_handler due to the IndexError

The suggested change simply checks for empty query upfront and returns root_handler immediately. While both approaches end up calling root_handler, the suggested way:

  • Is more explicit about handling empty queries
  • Avoids unnecessary string operations
  • Makes the code's intent clearer

Would you like me to show some example scenarios to demonstrate this?

Copy link
Owner Author

Choose a reason for hiding this comment

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

yes please

Copy link

Choose a reason for hiding this comment

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

@cibere Let me demonstrate with two scenarios:

Scenario 1: Current Behavior (Empty Query)

query.text = ""

# Current flow:
parts = "".split(" ")  # parts = [""]
query._data["search"] = "".removeprefix(self.prefix).removeprefix(self.sep)  # Unnecessary operations
try:
    handler = self._subgroups[parts[1]]  # IndexError because parts only has one item
except (KeyError, IndexError):
    handler = self.root_handler  # Finally reaches root_handler

Scenario 2: Proposed Behavior (Empty Query)

query.text = ""

# Proposed flow:
if not query.text:
    return self.root_handler(query)  # Immediately returns root_handler

The end result is the same (calling root_handler), but the proposed way:

  1. Skips unnecessary string operations
  2. Avoids throwing and catching exceptions
  3. Makes it immediately clear that empty queries show the root menu

Would this help explain the reasoning better?

Comment on lines +5 to +9
from ._types import (
PluginT,
SearchHandlerCallback,
SearchHandlerCallbackReturns,
)
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

⚠️ Potential issue

🧩 Analysis chain

Fix import error for PluginT.

The pipeline is failing with ImportError: cannot import name 'PluginT' from 'flogin._types'. This needs to be fixed for the code to work.


🏁 Script executed:

#!/bin/bash
# Check if PluginT is defined in _types.py
rg -A 5 'PluginT' | grep -v 'search_group.py'

Length of output: 17092


I'll take a closer look at the contents of flogin/_types.py to understand the conditional definition of PluginT and why it might not be available when imported by search_group.py. Let me inspect the file.


🏁 Script executed:

#!/bin/bash
# Output the first 100 lines of flogin/_types.py to inspect definitions for PluginT.
sed -n '1,100p' flogin/_types.py

Length of output: 773


Investigated conditional definition of PluginT in flogin/_types.py.

The file clearly defines PluginT in both branches of the TYPE_CHECKING condition—when type checking (using the advanced definition with bounds and covariance) and at runtime (a plain TypeVar). This indicates that PluginT is indeed available from flogin/_types.py. The pipeline’s ImportError appears to be caused by a circular dependency issue during type checking—specifically, when TYPE_CHECKING is True, _types.py attempts to import Plugin from flogin/plugin.py, and a circular import may be preventing PluginT from being exported properly.

To resolve this, please consider one of the following fixes:

  • Move the definition of PluginT outside of the TYPE_CHECKING block so that it’s unconditionally available. For example, always define PluginT with the advanced TypeVar using importlib “if TYPE_CHECKING:” only for imports needed solely for type annotations. This avoids a circular dependency when the type checker is active.

  • Alternatively, if you need to keep the conditional definition, restructure your code (and possibly the import order in flogin/plugin.py) to break the circular dependency.

Please verify which approach best fits the overall design of our codebase.

🧰 Tools
🪛 GitHub Actions: build

[error] 5-5: ImportError: cannot import name 'PluginT' from 'flogin._types' (unknown location). Did you mean: 'plugin'?

@cibere cibere closed this Feb 7, 2025
@cibere cibere deleted the search-handler-groups branch February 7, 2025 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

group system

1 participant