Skip to content

Fixes branch#143

Merged
MasumRab merged 13 commits intomainfrom
fixes-branch
Oct 14, 2025
Merged

Fixes branch#143
MasumRab merged 13 commits intomainfrom
fixes-branch

Conversation

@MasumRab
Copy link
Copy Markdown
Owner

@MasumRab MasumRab commented Oct 14, 2025

Summary by Sourcery

Resolve merge conflicts and align branch codebases by applying cosmetic refactors, adding documentation for merge planning, updating dependencies, standardizing code style, and introducing new CLI and API enhancements

New Features:

  • Add root endpoint in FastAPI to redirect '/' to Gradio UI
  • Enhance SmartGmailRetriever CLI with subcommands for listing, executing, and analytics

Enhancements:

  • Resolve merge conflicts and standardize code formatting across multiple modules
  • Clean up tests and deployment scripts, removing leftover conflict markers
  • Apply consistent string quoting, trailing commas, and import grouping for readability

Build:

  • Expand pyproject.toml with new dependencies for Gradio UI, NLP/ML, plotting, security, and Google API

Documentation:

  • Add merge_phase_plan.md to outline phased approach for branch alignment
  • Update branch_alignment_report.md with final alignment status and cherry-pick summary

Summary by CodeRabbit

  • New Features

    • Base URL now redirects to the UI for quicker access.
    • Smart retrieval CLI gains subcommands: list-strategies, execute-strategies (with limits/time budgets), and get-retrieval-analytics.
    • Default workflow setting added.
  • Documentation

    • Added comprehensive merge phase plan and branch alignment report.
  • Tests

    • Expanded coverage for virtual environment creation, recreation, and detection.
  • Refactor/Style

    • Consistent formatting, logging, and message polish across backend and UI.
    • Minor launch and development runner cleanups.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Oct 14, 2025

Reviewer's Guide

This PR standardizes code style across multiple modules, enhances the SmartGmailRetriever CLI with subcommands and analytics retrieval, adds a root redirect to the Gradio UI, updates project dependencies and startup configuration, refreshes branch alignment documentation, and resolves merge conflicts cleanly.

Class diagram for SmartGmailRetriever CLI enhancements

classDiagram
    class SmartGmailRetriever
    class main_cli
    SmartGmailRetriever <.. main_cli : uses
    class argparse_ArgumentParser {
      +add_subparsers()
      +add_parser()
    }
    main_cli ..> argparse_ArgumentParser : configures
    main_cli : list-strategies
    main_cli : execute-strategies
    main_cli : get-retrieval-analytics
Loading

Class diagram for ImportanceModel code style update

classDiagram
    class ImportanceModel {
      +important_keywords: List[str]
      +__init__()
      +analyze(text: str) dict
    }
Loading

Class diagram for AI routes code style and logic update

classDiagram
    class AIAnalysisRequest
    class AIAnalysisResponse
    class AICategorizeRequest
    class AICategorizeResponse
    class EmailResponse
    class AIValidateRequest
    class AIValidateResponse
    class AdvancedAIEngine {
      +analyze_email(subject, content, models_to_use, db)
    }
    class DatabaseManager {
      +update_email()
      +get_category_by_id()
      +get_email_by_id()
      +get_all_categories()
    }
    AIAnalysisRequest <.. AIAnalysisResponse : response
    AICategorizeRequest <.. AICategorizeResponse : response
    AIValidateRequest <.. AIValidateResponse : response
    AdvancedAIEngine <.. AIAnalysisResponse : returns
    DatabaseManager <.. EmailResponse : returns
Loading

Class diagram for Gradio app code style and UI logic update

classDiagram
    class GradioApp {
      +analyze_email_interface(subject, content)
      +get_emails_list(category_id, search)
      +get_categories()
      +update_email(email_id, updates)
      +get_emails(category, search)
      +on_select(evt)
      +analyze_batch(data_str)
      +refresh_models()
      +load_selected_model(model_name)
      +unload_selected_model(model_name)
      +run_custom_code(code)
      +get_system_health()
      +search_emails(search, category, read_status, page_size_val)
      +go_to_page(all_emails, page, page_size_val, direction)
      +change_page(all_emails, page, page_size_val)
      +select_email(evt, all_emails)
      +mark_read_unread(email_id, is_unread, ...)
      +change_category(email_id, new_category, ...)
    }
Loading

File-Level Changes

Change Details Files
Standardize code style across multiple modules
  • Applied consistent quoting and removed extra whitespace
  • Added trailing commas and reformatted multi-line arguments
  • Unified import and dict formatting conventions
backend/python_backend/gradio_app.py
backend/python_backend/ai_routes.py
launch.py
tests/test_launcher.py
run.py
deployment/deploy.py
backend/python_nlp/analysis_components/importance_model.py
backend/python_backend/database.py
backend/python_backend/main.py
backend/python_nlp/nlp_engine.py
temp_retrieve.py
python_backend/llm_config.py
backend/python_backend/model_manager.py
backend/python_backend/performance_monitor.py
Implement subcommands for SmartGmailRetriever CLI
  • Replaced single positional command with argparse subparsers
  • Added list-strategies, execute-strategies, and get-retrieval-analytics commands
  • Defined relevant options for checkpoints, strategies, API calls, and analytics period
backend/python_nlp/smart_retrieval.py
Add root HTTP endpoint to redirect '/' to Gradio UI
  • Registered GET '/' route in create_app
  • Used RedirectResponse to forward to '/ui' endpoint
src/main.py
Update project dependencies for UI, NLP, data, monitoring, and security
  • Added Gradio UI, RestrictedPython, pyngrok packages
  • Included NLP, ML, data-handling, and plotting libraries
  • Added monitoring, Google API, and security dependencies
pyproject.toml
Introduce application settings and startup service initialization
  • Added backend/data/settings.json for configuration
  • Updated startup_event to initialize database and services
backend/data/settings.json
backend/python_backend/main.py
Refresh branch alignment report documentation
  • Revised branch_alignment_report.md with final status, commit differences, and next steps
  • Added merge_phase_plan.md outlining phased merge strategy
branch_alignment_report.md
merge_phase_plan.md
Resolve merge conflicts and clean up conflict markers
  • Removed conflict markers and consolidated merged blocks
  • Ensured tests and run scripts are clean and runnable
tests/test_launcher.py
run.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link
Copy Markdown

🤖 Hi @MasumRab, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Introduces FastAPI dependency injection in AI routes, adds a root redirect to the UI, expands the smart retrieval CLI with subcommands, adds a settings JSON, significantly revises pyproject dependencies, updates tests for venv handling, and performs widespread formatting/cleanup across backend, NLP, deployment, and docs/logs.

Changes

Cohort / File(s) Summary
API routing and redirection
backend/python_backend/ai_routes.py, src/main.py
Adds FastAPI Depends for AI engine/DB in analyze_email and categorize_email; introduces "/" route redirecting to "/ui".
NLP formatting updates
backend/python_nlp/nlp_engine.py, backend/python_nlp/analysis_components/importance_model.py
Refactors dict/slice formatting and spacing; no behavioral changes.
Smart retrieval CLI rework
backend/python_nlp/smart_retrieval.py
Replaces single command with argparse subparsers: list-strategies, execute-strategies, get-retrieval-analytics, with new options.
Backend formatting and minor adjustments
backend/python_backend/database.py, backend/python_backend/gradio_app.py, backend/python_backend/main.py, backend/python_backend/model_manager.py, backend/python_backend/performance_monitor.py, deployment/deploy.py, launch.py, run.py, temp_retrieve.py
Formatting, spacing, minor logging/message consistency; no functional changes except path setup and duplicated uvicorn import in run.py.
Config and dependencies
backend/data/settings.json, pyproject.toml
Adds default settings JSON (active_workflow="default"); replaces minimal deps with broad multi-domain dependency set.
Docs and logs
branch_alignment_report.md, merge_phase_plan.md, performance_metrics_log.jsonl
Adds alignment and merge phase docs; expands log entries; conflict markers remain in metrics log.
LLM config exception handling
python_backend/llm_config.py
Narrows exception to OSError in get_git_branch; removes unused import.
Tests
tests/test_launcher.py
Removes one failure-logging assertion; adds TestVirtualEnvironment suite for venv lifecycle and startup/deps.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant F as FastAPI App
  participant R as RedirectResponse
  participant G as UI (/ui)

  C->>F: GET /
  F->>R: Create Redirect to /ui
  F-->>C: 307/302 Redirect
  C->>G: GET /ui
  G-->>C: UI content
Loading
sequenceDiagram
  autonumber
  participant Client
  participant API as FastAPI Route (/analyze_email)
  participant DI as Depends(get_ai_engine/get_db)
  participant AI as AdvancedAIEngine
  participant DB as DatabaseManager

  Client->>API: POST analyze_email(payload)
  API->>DI: Resolve ai_engine, db
  DI-->>API: ai_engine, db
  API->>AI: analyze_email(payload, db)
  AI-->>API: analysis result
  API-->>Client: JSON response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

I thump the ground—deploys tonight!
New routes hop fast, with redirects bright.
Subcommands bloom, a CLI bouquet,
While configs nibble dependencies’ hay.
Logs may tussle, tests now preen—
Carrots for code, so crisp and clean. 🥕🐇

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fixes-branch

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bfabf81 and 19d52f1.

⛔ Files ignored due to path filters (3)
  • backend/data/categories.json.gz is excluded by !**/*.gz
  • backend/data/emails.json.gz is excluded by !**/*.gz
  • backend/data/users.json.gz is excluded by !**/*.gz
📒 Files selected for processing (21)
  • backend/data/settings.json (1 hunks)
  • backend/python_backend/ai_routes.py (6 hunks)
  • backend/python_backend/database.py (2 hunks)
  • backend/python_backend/gradio_app.py (18 hunks)
  • backend/python_backend/main.py (1 hunks)
  • backend/python_backend/model_manager.py (1 hunks)
  • backend/python_backend/performance_monitor.py (1 hunks)
  • backend/python_nlp/analysis_components/importance_model.py (2 hunks)
  • backend/python_nlp/nlp_engine.py (2 hunks)
  • backend/python_nlp/smart_retrieval.py (1 hunks)
  • branch_alignment_report.md (1 hunks)
  • deployment/deploy.py (3 hunks)
  • launch.py (1 hunks)
  • merge_phase_plan.md (1 hunks)
  • performance_metrics_log.jsonl (1 hunks)
  • pyproject.toml (1 hunks)
  • python_backend/llm_config.py (1 hunks)
  • run.py (2 hunks)
  • src/main.py (2 hunks)
  • temp_retrieve.py (1 hunks)
  • tests/test_launcher.py (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

🤖 I'm sorry @MasumRab, but I was unable to process your request. Please see the logs for more details.

@MasumRab MasumRab marked this pull request as ready for review October 14, 2025 14:11
@MasumRab MasumRab merged commit f466747 into main Oct 14, 2025
5 of 8 checks passed
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

New security issues found

Comment on lines +34 to +36
response = requests.post(
f"{BASE_URL}/api/ai/analyze", json={"subject": subject, "content": content}
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security (python.requests.best-practice.use-timeout): Detected a 'requests' call without a timeout set. By default, 'requests' calls wait until the connection is closed. This means a 'requests' call without a timeout will hang the program if a response is never received. Consider setting a timeout for all 'requests'.

Suggested change
response = requests.post(
f"{BASE_URL}/api/ai/analyze", json={"subject": subject, "content": content}
)
response = requests.post(
f"{BASE_URL}/api/ai/analyze", json={"subject": subject, "content": content}
, timeout=30)

Source: opengrep

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

Blocking issues:

  • Detected a 'requests' call without a timeout set. By default, 'requests' calls wait until the connection is closed. This means a 'requests' call without a timeout will hang the program if a response is never received. Consider setting a timeout for all 'requests'. (link)

General comments:

  • This PR bundles large, unrelated changes (styling, UI features, dependency updates, branch reports) together—consider splitting it into smaller, feature-focused PRs for easier review and rollback.
  • The SmartGmailRetriever CLI defines a "get-retrieval-analytics" subcommand but has no corresponding dispatch branch for it; please implement or remove that handler.
  • The new root endpoint returns RedirectResponse but never imports it—add from fastapi.responses import RedirectResponse in main.py to prevent runtime errors.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- This PR bundles large, unrelated changes (styling, UI features, dependency updates, branch reports) together—consider splitting it into smaller, feature-focused PRs for easier review and rollback.
- The SmartGmailRetriever CLI defines a "get-retrieval-analytics" subcommand but has no corresponding dispatch branch for it; please implement or remove that handler.
- The new root endpoint returns RedirectResponse but never imports it—add `from fastapi.responses import RedirectResponse` in main.py to prevent runtime errors.

## Individual Comments

### Comment 1
<location> `tests/test_launcher.py:96` </location>
<code_context>
-=======
+

 class TestVirtualEnvironment:
     """Test virtual environment creation and management."""
</code_context>

<issue_to_address>
**suggestion (testing):** Missing tests for new CLI subcommands in SmartGmailRetriever.

Please add unit or integration tests for argument parsing, command dispatch, and expected outputs for each new subcommand.
</issue_to_address>

### Comment 2
<location> `tests/test_launcher.py:90-93` </location>
<code_context>
     assert exec_path == "/usr/bin/python-good"

-<<<<<<< HEAD
     # When the mocked execve raises an exception, the except block should log it
     # and then exit with status 1.
     assert mock_logger.error.call_count > 0
     mock_exit.assert_called_once_with(1)
-=======
+
</code_context>

<issue_to_address>
**suggestion (testing):** No tests for the new root redirect endpoint in FastAPI app.

Please add a test to confirm that a GET request to '/' returns a redirect to '/ui'.

Suggested implementation:

```python
from fastapi.testclient import TestClient

# Assuming the FastAPI app is defined in launcher.py as 'app'
from launcher import app

def test_root_redirects_to_ui():
    client = TestClient(app)
    response = client.get("/", allow_redirects=False)
    assert response.status_code in (301, 302)
    assert response.headers["location"] == "/ui"

class TestVirtualEnvironment:
    """Test virtual environment creation and management."""

```

).

Here are the code changes:

<file_operations>
<file_operation operation="edit" file_path="tests/test_launcher.py">
<<<<<<< SEARCH
class TestVirtualEnvironment:
    """Test virtual environment creation and management."""
=======
from fastapi.testclient import TestClient

# Assuming the FastAPI app is defined in launcher.py as 'app'
from launcher import app

def test_root_redirects_to_ui():
    client = TestClient(app)
    response = client.get("/", allow_redirects=False)
    assert response.status_code in (301, 302)
    assert response.headers["location"] == "/ui"

class TestVirtualEnvironment:
    """Test virtual environment creation and management."""
>>>>>>> REPLACE
</file_operation>
</file_operations>

<additional_changes>
If your FastAPI app is not defined as `app` in `launcher.py`, adjust the import statement accordingly.
If the root endpoint is not yet implemented in your FastAPI app, you will need to add it:

```python
from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()

@app.get("/")
def root():
    return RedirectResponse(url="/ui")
```
</issue_to_address>

### Comment 3
<location> `backend/python_nlp/smart_retrieval.py:206` </location>
<code_context>
-    parser.add_argument("command", choices=["list-strategies", "execute-strategies"])
+    subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands")
+
+    list_parser = subparsers.add_parser(
+        "list-strategies", help="List available retrieval strategies"
+    )
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring repeated argument definitions into shared parent parsers or helpers to reduce duplication.

You can DRY-up the repeated flags by extracting them into a shared parent parser or a small helper. For example:

```python
# above your subparsers…
shared_checkpoint = argparse.ArgumentParser(add_help=False)
shared_checkpoint.add_argument(
    "--checkpoint-db-path",
    type=str,
    default="sync_checkpoints.db",
    help="Path to the checkpoint database",
)

execute_options = [
    ("--strategies", {"dest": "strategies", "nargs": "+", "help": ""}),
    ("--max-api-calls", {"type": int, "default": 100, "help": ""}),
    ("--time-budget-minutes", {"type": int, "default": 30, "help": ""}),
]

# create subparsers
subparsers = parser.add_subparsers(dest="command", required=True)

# list-strategies inherits checkpoint
list_p = subparsers.add_parser(
    "list-strategies",
    parents=[shared_checkpoint],
    help="List available retrieval strategies",
)

# execute-strategies — loop to add its flags
exec_p = subparsers.add_parser(
    "execute-strategies",
    help="Execute retrieval strategies",
)
for opt, kwargs in execute_options:
    exec_p.add_argument(opt, **kwargs)

# analytics also reuses checkpoint
analytics_p = subparsers.add_parser(
    "get-retrieval-analytics",
    parents=[shared_checkpoint],
    help="Get retrieval analytics",
)
analytics_p.add_argument("--days", type=int, default=30, help="")
```

Benefits:  
- Removes the copy-pasted `--checkpoint-db-path` block.  
- Keeps each subparser’s intent clear.  
- Still uses only stdlib argparse.
</issue_to_address>

### Comment 4
<location> `backend/python_backend/gradio_app.py:34-36` </location>
<code_context>
        response = requests.post(
            f"{BASE_URL}/api/ai/analyze", json={"subject": subject, "content": content}
        )
</code_context>

<issue_to_address>
**security (python.requests.best-practice.use-timeout):** Detected a 'requests' call without a timeout set. By default, 'requests' calls wait until the connection is closed. This means a 'requests' call without a timeout will hang the program if a response is never received. Consider setting a timeout for all 'requests'.

```suggestion
        response = requests.post(
            f"{BASE_URL}/api/ai/analyze", json={"subject": subject, "content": content}
        , timeout=30)
```

*Source: opengrep*
</issue_to_address>

### Comment 5
<location> `backend/python_backend/gradio_app.py:68` </location>
<code_context>
                        "Read" if not email.get("isUnread", True) else "Unread",

</code_context>

<issue_to_address>
**suggestion (code-quality):** Swap if/else branches of if expression to remove negation ([`swap-if-expression`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/swap-if-expression))

```suggestion
                        "Unread" if email.get("isUnread", True) else "Read",
```

<br/><details><summary>Explanation</summary>Negated conditions are more difficult to read than positive ones, so it is best
to avoid them where we can. By swapping the `if` and `else` conditions around we
can invert the condition and make it positive.
</details>
</issue_to_address>

### Comment 6
<location> `backend/python_backend/ai_routes.py:37` </location>
<code_context>
@router.post("/api/ai/analyze", response_model=AIAnalysisResponse)
async def analyze_email(
    request: AIAnalysisRequest,
    ai_engine: AdvancedAIEngine = Depends(get_ai_engine),
    db: DatabaseManager = Depends(get_db),
):
    """
    Analyzes email content and returns AI-driven insights.
    """
    try:
        default_models = {"sentiment": "sentiment-default", "topic": "topic-default"}
        analysis_result = await ai_engine.analyze_email(
            subject=request.subject, content=request.content, models_to_use=default_models, db=db
        )
        return analysis_result.to_dict()
    except Exception as e:
        logger.error(f"Error in AI analysis endpoint: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail="Failed to analyze email with AI.")

</code_context>

<issue_to_address>
**suggestion (code-quality):** Explicitly raise from a previous error ([`raise-from-previous-error`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/raise-from-previous-error/))

```suggestion
        raise HTTPException(
            status_code=500, detail="Failed to analyze email with AI."
        ) from e
```
</issue_to_address>

### Comment 7
<location> `backend/python_backend/ai_routes.py:54` </location>
<code_context>
@router.post("/api/ai/categorize", response_model=AICategorizeResponse)
async def categorize_email(
    request: AICategorizeRequest,
    db: DatabaseManager = Depends(get_db),
    ai_engine: AdvancedAIEngine = Depends(get_ai_engine),
):
    """
    Categorizes an email, either automatically using AI or manually.
    """
    email = await db.get_email_by_id(request.emailId)
    if not email:
        raise HTTPException(status_code=404, detail="Email not found")

    if request.autoAnalyze:
        try:
            default_models = {"sentiment": "sentiment-default", "topic": "topic-default"}
            analysis_result = await ai_engine.analyze_email(
                subject=email["subject"],
                content=email["content"],
                models_to_use=default_models,
                db=db,
            )

            if analysis_result and analysis_result.category_id:
                updated_email_dict = await db.update_email(
                    request.emailId,
                    {
                        "categoryId": analysis_result.category_id,
                        "confidence": int(analysis_result.confidence * 100),
                        "labels": analysis_result.suggested_labels,
                    },
                )
                category = await db.get_category_by_id(analysis_result.category_id)
                return AICategorizeResponse(
                    success=True,
                    email=EmailResponse(**updated_email_dict),
                    analysis=AIAnalysisResponse(**analysis_result.to_dict()),
                    categoryAssigned=category["name"],
                )
            else:
                return AICategorizeResponse(
                    success=False,
                    message="AI analysis did not result in a category.",
                    analysis=(
                        AIAnalysisResponse(**analysis_result.to_dict()) if analysis_result else None
                    ),
                )
        except Exception as e:
            logger.error(f"Error in AI categorization: {e}", exc_info=True)
            raise HTTPException(status_code=500, detail="Failed to categorize email with AI.")
    else:
        if request.categoryId is None:
            raise HTTPException(
                status_code=400, detail="categoryId is required for manual categorization"
            )

        update_data = {"categoryId": request.categoryId}
        if request.confidence is not None:
            update_data["confidence"] = request.confidence

        updated_email_dict = await db.update_email(request.emailId, update_data)
        category = await db.get_category_by_id(request.categoryId)
        return AICategorizeResponse(
            success=True,
            email=EmailResponse(**updated_email_dict),
            categoryAssigned=category["name"],
        )

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Swap if/else branches ([`swap-if-else-branches`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-if-else-branches/))
- Remove unnecessary else after guard condition ([`remove-unnecessary-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-else/))
- Explicitly raise from a previous error ([`raise-from-previous-error`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/raise-from-previous-error/))
</issue_to_address>

### Comment 8
<location> `backend/python_backend/gradio_app.py:58-71` </location>
<code_context>
def get_emails_list(category_id=None, search=None):
    """Fetch emails from API with optional filters"""
    try:
        params = {}
        if category_id is not None:
            params["category_id"] = category_id
        if search:
            params["search"] = search
        response = requests.get("http://127.0.0.1:8000/api/emails", params=params)
        if response.status_code == 200:
            emails = response.json()
            # Convert to list of lists for Dataframe
            data = []
            for email in emails:
                data.append(
                    [
                        email["id"],
                        email["time"][:19],  # Truncate datetime
                        email["sender"],
                        email["subject"],
                        email.get("category", ""),
                        "Read" if not email.get("isUnread", True) else "Unread",
                    ]
                )
            return data
        return []
    except Exception:
        return []

</code_context>

<issue_to_address>
**suggestion (code-quality):** We've found these issues:

- Convert for loop into list comprehension ([`list-comprehension`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/list-comprehension/))
- Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-immediately-returned-variable/))

```suggestion
            return [
                [
                    email["id"],
                    email["time"][:19],  # Truncate datetime
                    email["sender"],
                    email["subject"],
                    email.get("category", ""),
                    "Read" if not email.get("isUnread", True) else "Unread",
                ]
                for email in emails
            ]
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 90 to 93
# When the mocked execve raises an exception, the except block should log it
# and then exit with status 1.
assert mock_logger.error.call_count > 0
mock_exit.assert_called_once_with(1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): No tests for the new root redirect endpoint in FastAPI app.

Please add a test to confirm that a GET request to '/' returns a redirect to '/ui'.

Suggested implementation:

from fastapi.testclient import TestClient

# Assuming the FastAPI app is defined in launcher.py as 'app'
from launcher import app

def test_root_redirects_to_ui():
    client = TestClient(app)
    response = client.get("/", allow_redirects=False)
    assert response.status_code in (301, 302)
    assert response.headers["location"] == "/ui"

class TestVirtualEnvironment:
    """Test virtual environment creation and management."""

).

Here are the code changes:

<file_operations>
<file_operation operation="edit" file_path="tests/test_launcher.py">
<<<<<<< SEARCH
class TestVirtualEnvironment:
"""Test virtual environment creation and management."""

from fastapi.testclient import TestClient

Assuming the FastAPI app is defined in launcher.py as 'app'

from launcher import app

def test_root_redirects_to_ui():
client = TestClient(app)
response = client.get("/", allow_redirects=False)
assert response.status_code in (301, 302)
assert response.headers["location"] == "/ui"

class TestVirtualEnvironment:
"""Test virtual environment creation and management."""

REPLACE
</file_operation>
</file_operations>

<additional_changes>
If your FastAPI app is not defined as app in launcher.py, adjust the import statement accordingly.
If the root endpoint is not yet implemented in your FastAPI app, you will need to add it:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()

@app.get("/")
def root():
    return RedirectResponse(url="/ui")

Comment on lines +34 to +36
response = requests.post(
f"{BASE_URL}/api/ai/analyze", json={"subject": subject, "content": content}
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security (python.requests.best-practice.use-timeout): Detected a 'requests' call without a timeout set. By default, 'requests' calls wait until the connection is closed. This means a 'requests' call without a timeout will hang the program if a response is never received. Consider setting a timeout for all 'requests'.

Suggested change
response = requests.post(
f"{BASE_URL}/api/ai/analyze", json={"subject": subject, "content": content}
)
response = requests.post(
f"{BASE_URL}/api/ai/analyze", json={"subject": subject, "content": content}
, timeout=30)

Source: opengrep

email["sender"],
email["subject"],
email.get("category", ""),
"Read" if not email.get("isUnread", True) else "Unread",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Swap if/else branches of if expression to remove negation (swap-if-expression)

Suggested change
"Read" if not email.get("isUnread", True) else "Unread",
"Unread" if email.get("isUnread", True) else "Read",


ExplanationNegated conditions are more difficult to read than positive ones, so it is best
to avoid them where we can. By swapping the if and else conditions around we
can invert the condition and make it positive.

return analysis_result.to_dict()
except Exception as e:
logger.error(f"Error in AI analysis endpoint: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Failed to analyze email with AI.")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Explicitly raise from a previous error (raise-from-previous-error)

Suggested change
raise HTTPException(status_code=500, detail="Failed to analyze email with AI.")
raise HTTPException(
status_code=500, detail="Failed to analyze email with AI."
) from e

@@ -49,15 +52,12 @@ async def categorize_email(

if request.autoAnalyze:
try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:

Comment on lines 58 to 71
# Convert to list of lists for Dataframe
data = []
for email in emails:
data.append([
email['id'],
email['time'][:19], # Truncate datetime
email['sender'],
email['subject'],
email.get('category', ''),
'Read' if not email.get('isUnread', True) else 'Unread'
])
data.append(
[
email["id"],
email["time"][:19], # Truncate datetime
email["sender"],
email["subject"],
email.get("category", ""),
"Read" if not email.get("isUnread", True) else "Unread",
]
)
return data
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): We've found these issues:

Suggested change
# Convert to list of lists for Dataframe
data = []
for email in emails:
data.append([
email['id'],
email['time'][:19], # Truncate datetime
email['sender'],
email['subject'],
email.get('category', ''),
'Read' if not email.get('isUnread', True) else 'Unread'
])
data.append(
[
email["id"],
email["time"][:19], # Truncate datetime
email["sender"],
email["subject"],
email.get("category", ""),
"Read" if not email.get("isUnread", True) else "Unread",
]
)
return data
return [
[
email["id"],
email["time"][:19], # Truncate datetime
email["sender"],
email["subject"],
email.get("category", ""),
"Read" if not email.get("isUnread", True) else "Unread",
]
for email in emails
]

@coderabbitai coderabbitai bot mentioned this pull request Mar 29, 2026
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.

1 participant