Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.6.0"
description = "A command-line tool for browsing Reddit without requiring an API key"
readme = "README.md"
requires-python = ">=3.14"
dependencies = ["typer", "httpx", "pydantic"]
dependencies = ["typer", "httpx", "pydantic", "openpyxl>=3.1.0"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Checking openpyxl dependency and typing stubs presence..."
rg -n --no-heading 'openpyxl|types-openpyxl' pyproject.toml reddit_cli/xlsx_export.py

Repository: AliiiBenn/reddit-cli

Length of output: 1227


🏁 Script executed:

cat -n pyproject.toml

Repository: AliiiBenn/reddit-cli

Length of output: 894


🏁 Script executed:

fd -e "mypy.ini" -o ".mypy.ini" -o "setup.cfg" | head -20

Repository: AliiiBenn/reddit-cli

Length of output: 201


🏁 Script executed:

rg -n "mypy|disallow_untyped_calls|disallow_untyped_defs|ignore_missing_imports" pyproject.toml

Repository: AliiiBenn/reddit-cli

Length of output: 46


🌐 Web query:

types-openpyxl package availability PyPI typing stubs

💡 Result:

The types-openpyxl package, providing typing stubs for openpyxl, is available on PyPI. The latest version as of 2026-03-22 is 3.1.5.20260322, which targets openpyxl==3.1.5. It is part of the official Typeshed project (github.com/python/typeshed) and supports type checkers like mypy and pyright. Install via pip install types-openpyxl. An older, third-party alternative called openpyxl-stubs (last updated 2023) also exists but is less recommended due to lack of recent maintenance.

Citations:


🏁 Script executed:

fd -type f -name "*.yml" -o -name "*.yaml" | grep -E "(github|gitlab|ci)" | head -20

Repository: AliiiBenn/reddit-cli

Length of output: 234


🏁 Script executed:

find . -maxdepth 3 -type f \( -name "*.yml" -o -name "*.yaml" -o -name ".mypy.ini" -o -name "setup.cfg" -o -name "pyproject.toml" \) | xargs rg -l "mypy" 2>/dev/null

Repository: AliiiBenn/reddit-cli

Length of output: 124


🏁 Script executed:

ls -la .github/workflows/ 2>/dev/null || echo "No .github/workflows directory found"

Repository: AliiiBenn/reddit-cli

Length of output: 410


🏁 Script executed:

rg "disallow.*untyped|ignore.*missing.*imports" --type yaml --type toml

Repository: AliiiBenn/reddit-cli

Length of output: 46


🏁 Script executed:

cat -n .github/workflows/mypy.yml

Repository: AliiiBenn/reddit-cli

Length of output: 733


🏁 Script executed:

find . -maxdepth 2 -name "setup.cfg" -o -name ".mypy.ini"

Repository: AliiiBenn/reddit-cli

Length of output: 46


🏁 Script executed:

rg "tool\.mypy\]|disallow_untyped" --multiline

Repository: AliiiBenn/reddit-cli

Length of output: 46


🏁 Script executed:

rg "import_untyped|disallow.*untyped" .

Repository: AliiiBenn/reddit-cli

Length of output: 46


Add types-openpyxl to dev dependencies for better type checking

The suggested patch is recommended to improve type coverage for the openpyxl dependency. While mypy is currently running without strict type-checking flags, adding types-openpyxl from Typeshed will enable better IDE support, autocomplete, and future-proof the project against stricter type-checking configurations.

Suggested patch
 [project.optional-dependencies]
-dev = ["pytest", "pytest-asyncio", "respx", "httpx"]
+dev = ["pytest", "pytest-asyncio", "respx", "httpx", "types-openpyxl"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` at line 7, Add the types-openpyxl package to the development
dependencies so IDEs and type checkers get stubs for openpyxl; update the
pyproject.toml dev-dependencies section to include "types-openpyxl" (no runtime
import changes needed) so files that import openpyxl (referenced in the
dependencies list containing "openpyxl>=3.1.0") gain type hints and improved
mypy/IDE support.


[project.optional-dependencies]
dev = ["pytest", "pytest-asyncio", "respx", "httpx"]
Expand Down
17 changes: 14 additions & 3 deletions reddit_cli/commands/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
post_csv_header,
)
from reddit_cli.reddit import RedditClient, PostsClient
from reddit_cli.xlsx_export import posts_to_xlsx


# Valid values for CLI validation
VALID_SORT_VALUES = ["hot", "new", "top", "rising", "controversial", "gilded"]
VALID_PERIOD_VALUES = ["day", "week", "month", "year", "all"]
VALID_FORMAT_VALUES = ["display", "sql", "csv"]
VALID_FORMAT_VALUES = ["display", "sql", "csv", "xlsx"]


def _validate_sort_period(sort: str, period: str | None, limit: int) -> None:
Expand Down Expand Up @@ -47,12 +48,22 @@ def _write_output(

Args:
posts: List of Post objects
format_type: Output format (display, sql, csv)
format_type: Output format (display, sql, csv, xlsx)
output_file: File path or None for stdout
"""
if format_type == "display":
return

if format_type == "xlsx":
if not output_file:
typer.echo("Error: --output is required for xlsx format", err=True)
raise typer.Exit(code=2)
xlsx_data = posts_to_xlsx(posts)
with open(output_file, "wb") as f:
f.write(xlsx_data)
typer.echo(f"Exported {len(posts)} posts to {output_file}")
return

lines: list[str] = []
if format_type == "csv":
lines.append(post_csv_header())
Expand Down Expand Up @@ -151,7 +162,7 @@ def browse(
period: Time period for top/controversial (day, week, month, year, all)
after: Pagination cursor (get posts after this ID)
before: Pagination cursor (get posts before this ID)
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand Down
19 changes: 15 additions & 4 deletions reddit_cli/commands/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
comment_csv_header,
)
from reddit_cli.reddit import RedditClient, CommentsClient, Comment
from reddit_cli.xlsx_export import comments_to_xlsx


VALID_FORMAT_VALUES = ["display", "sql", "csv"]
VALID_FORMAT_VALUES = ["display", "sql", "csv", "xlsx"]


def _flatten_comments(comments: list[Comment]) -> list[Comment]:
Expand Down Expand Up @@ -40,14 +41,24 @@ def _write_comments_output(

Args:
comments: List of Comment objects
format_type: Output format (display, sql, csv)
format_type: Output format (display, sql, csv, xlsx)
output_file: File path or None for stdout
"""
flat_comments = _flatten_comments(comments)

if format_type == "display":
return

if format_type == "xlsx":
if not output_file:
typer.echo("Error: --output is required for xlsx format", err=True)
raise typer.Exit(code=2)
xlsx_data = comments_to_xlsx(comments)
with open(output_file, "wb") as f:
f.write(xlsx_data)
typer.echo(f"Exported {len(flat_comments)} comments to {output_file}")
return

lines: list[str] = []
if format_type == "csv":
lines.append(comment_csv_header())
Expand Down Expand Up @@ -107,7 +118,7 @@ def comments(
post_id: Post ID (with or without t3_ prefix)
sort: Sort type (confidence, top, new, old, controversial, qa)
depth: Maximum comment depth
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand Down Expand Up @@ -138,7 +149,7 @@ def comment(
post_id: Post ID (with or without t3_ prefix)
comment_id: Comment ID (with or without t1_ prefix)
replies: Include replies
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand Down
21 changes: 16 additions & 5 deletions reddit_cli/commands/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
post_csv_header,
)
from reddit_cli.reddit import RedditClient, PostsClient
from reddit_cli.xlsx_export import posts_to_xlsx


# Valid values for CLI validation
VALID_SORT_VALUES = ["hot", "new", "top", "rising", "controversial", "gilded"]
VALID_PERIOD_VALUES = ["day", "week", "month", "year", "all"]
VALID_FORMAT_VALUES = ["display", "sql", "csv"]
VALID_FORMAT_VALUES = ["display", "sql", "csv", "xlsx"]


def _validate_sort_period(sort: str, period: str | None, limit: int) -> None:
Expand Down Expand Up @@ -47,12 +48,22 @@ def _write_posts_output(

Args:
posts: List of Post objects
format_type: Output format (display, sql, csv)
format_type: Output format (display, sql, csv, xlsx)
output_file: File path or None for stdout
"""
if format_type == "display":
return

if format_type == "xlsx":
if not output_file:
typer.echo("Error: --output is required for xlsx format", err=True)
raise typer.Exit(code=2)
xlsx_data = posts_to_xlsx(posts)
with open(output_file, "wb") as f:
f.write(xlsx_data)
typer.echo(f"Exported {len(posts)} posts to {output_file}")
return

lines: list[str] = []
if format_type == "csv":
lines.append(post_csv_header())
Expand Down Expand Up @@ -126,7 +137,7 @@ def frontpage(
period: Time period for top/controversial (day, week, month, year, all)
after: Pagination cursor
before: Pagination cursor
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand Down Expand Up @@ -163,7 +174,7 @@ def home(
period: Time period for top/controversial (day, week, month, year, all)
after: Pagination cursor
before: Pagination cursor
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand Down Expand Up @@ -198,7 +209,7 @@ def best(
period: Time period (day, week, month, year, all)
after: Pagination cursor
before: Pagination cursor
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand Down
13 changes: 11 additions & 2 deletions reddit_cli/commands/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from reddit_cli.errors import handle_api_error, handle_validation_error
from reddit_cli.export import post_to_sql_insert, post_to_csv_row, post_csv_header
from reddit_cli.reddit import RedditClient, PostsClient
from reddit_cli.xlsx_export import posts_to_xlsx


VALID_FORMAT_VALUES = ["display", "sql", "csv"]
VALID_FORMAT_VALUES = ["display", "sql", "csv", "xlsx"]


async def _post_async(post_id: str):
Expand Down Expand Up @@ -35,7 +36,7 @@ def post(
post_id: Post ID (with or without t3_ prefix)
view: Show post details
info: Show detailed post info
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand All @@ -55,6 +56,14 @@ def post(
typer.echo()
if post_obj.selftext:
typer.echo(post_obj.selftext.encode(sys.stdout.encoding, errors="replace").decode(sys.stdout.encoding))
elif format == "xlsx":
if not output:
typer.echo("Error: --output is required for xlsx format", err=True)
raise typer.Exit(code=2)
xlsx_data = posts_to_xlsx([post_obj])
with open(output, "wb") as f:
f.write(xlsx_data)
typer.echo(f"Exported 1 post to {output}")
else:
lines = []
if format == "csv":
Expand Down
17 changes: 14 additions & 3 deletions reddit_cli/commands/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
post_csv_header,
)
from reddit_cli.reddit import RedditClient, PostsClient
from reddit_cli.xlsx_export import posts_to_xlsx


# Valid values for CLI validation
VALID_SEARCH_SORT_VALUES = ["relevance", "hot", "top", "new", "comments"]
VALID_PERIOD_VALUES = ["hour", "day", "week", "month", "year", "all"]
VALID_FORMAT_VALUES = ["display", "sql", "csv"]
VALID_FORMAT_VALUES = ["display", "sql", "csv", "xlsx"]


def _validate_search_params(sort: str, period: str | None, limit: int) -> None:
Expand Down Expand Up @@ -47,12 +48,22 @@ def _write_posts_output(

Args:
posts: List of Post objects
format_type: Output format (display, sql, csv)
format_type: Output format (display, sql, csv, xlsx)
output_file: File path or None for stdout
"""
if format_type == "display":
return

if format_type == "xlsx":
if not output_file:
typer.echo("Error: --output is required for xlsx format", err=True)
raise typer.Exit(code=2)
xlsx_data = posts_to_xlsx(posts)
with open(output_file, "wb") as f:
f.write(xlsx_data)
typer.echo(f"Exported {len(posts)} posts to {output_file}")
return

lines: list[str] = []
if format_type == "csv":
lines.append(post_csv_header())
Expand Down Expand Up @@ -123,7 +134,7 @@ def search(
sort: Sort type (relevance, hot, top, new, comments)
limit: Number of results
period: Time period (day, week, month, year, all)
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand Down
25 changes: 18 additions & 7 deletions reddit_cli/commands/subreddit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
subreddit_csv_header,
)
from reddit_cli.reddit import RedditClient, SubredditsClient
from reddit_cli.xlsx_export import subreddits_to_xlsx


# Valid values for CLI validation
VALID_SUBREDDIT_SORT_VALUES = ["gilded", "subscribers", "active"]
VALID_FORMAT_VALUES = ["display", "sql", "csv"]
VALID_FORMAT_VALUES = ["display", "sql", "csv", "xlsx"]


def _validate_list_params(sort: str, limit: int) -> None:
Expand All @@ -35,6 +36,16 @@ def _write_subreddits_output(
if format_type == "display":
return

if format_type == "xlsx":
if not output_file:
typer.echo("Error: --output is required for xlsx format", err=True)
raise typer.Exit(code=2)
xlsx_data = subreddits_to_xlsx(subreddits)
with open(output_file, "wb") as f:
f.write(xlsx_data)
typer.echo(f"Exported {len(subreddits)} subreddits to {output_file}")
return

lines: list[str] = []
if format_type == "csv":
lines.append(subreddit_csv_header())
Expand Down Expand Up @@ -85,7 +96,7 @@ def subreddit(
Args:
name: Subreddit name (with or without r/ prefix)
rules: Show subreddit rules
format: Output format (display, sql, csv)
format: Output format (display, sql, csv, xlsx)
output: Output file path
"""
try:
Expand Down Expand Up @@ -137,7 +148,7 @@ async def _list_subreddits_async(
def subreddits_popular(
sort: str = typer.Option("subscribers", "--sort", help="Sort type (subscribers, active, gilded)"),
limit: int = typer.Option(25, "--limit", help="Number of results"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv)"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv, xlsx)"),
output: str | None = typer.Option(None, "--output", help="Output file path"),
) -> None:
"""List popular subreddits."""
Expand Down Expand Up @@ -191,7 +202,7 @@ async def _search_async(
def subreddits_search(
query: str = typer.Argument(..., help="Search query"),
limit: int = typer.Option(25, "--limit", help="Number of results"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv)"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv, xlsx)"),
output: str | None = typer.Option(None, "--output", help="Output file path"),
) -> None:
"""Search subreddits by keyword."""
Expand Down Expand Up @@ -227,7 +238,7 @@ async def _new_async(
@subreddits_app.command(name="new")
def subreddits_new(
limit: int = typer.Option(25, "--limit", help="Number of results"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv)"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv, xlsx)"),
output: str | None = typer.Option(None, "--output", help="Output file path"),
) -> None:
"""List newly created subreddits."""
Expand Down Expand Up @@ -263,7 +274,7 @@ async def _gold_async(
@subreddits_app.command(name="gold")
def subreddits_gold(
limit: int = typer.Option(25, "--limit", help="Number of results"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv)"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv, xlsx)"),
output: str | None = typer.Option(None, "--output", help="Output file path"),
) -> None:
"""List Reddit Gold subreddits."""
Expand Down Expand Up @@ -299,7 +310,7 @@ async def _default_async(
@subreddits_app.command(name="default")
def subreddits_default(
limit: int = typer.Option(25, "--limit", help="Number of results"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv)"),
format: str = typer.Option("display", "--format", help="Output format (display, sql, csv, xlsx)"),
output: str | None = typer.Option(None, "--output", help="Output file path"),
) -> None:
"""List default subreddits."""
Expand Down
3 changes: 3 additions & 0 deletions reddit_cli/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ def handle_api_error(e: Exception) -> None:
1: General error (network issues, API errors)
2: Usage error (invalid arguments)
"""
# Re-raise typer.Exit exceptions without modification
if isinstance(e, typer.Exit):
raise e
if isinstance(e, httpx.TimeoutException):
typer.echo("Error: Connection timed out. Please check your internet connection and try again.", err=True)
raise typer.Exit(code=EXIT_GENERAL_ERROR)
Expand Down
Loading
Loading