Migrate CLI from typer to cyclopts #86

Merged
coding-agent-marvin8 merged 0 commits from refs/pull/86/head into main 2026-06-12 22:39:37 +00:00
coding-agent-marvin8 commented 2026-06-12 21:20:51 +00:00 (Migrated from codeberg.org)

Closes #85

Replaces the typer dependency with cyclopts 4.18.0. All three CLI entry points (feed2fedi, feed2fedi_import_cache, feed2fedi_convert_config) are migrated to cyclopts.App / @app.default / Parameter — the user-visible interface (option names, short aliases, help text) is unchanged.

Also suppresses httpx2 log noise at INFO level in logging_config.py.

Closes #85 Replaces the `typer` dependency with `cyclopts` 4.18.0. All three CLI entry points (`feed2fedi`, `feed2fedi_import_cache`, `feed2fedi_convert_config`) are migrated to `cyclopts.App` / `@app.default` / `Parameter` — the user-visible interface (option names, short aliases, help text) is unchanged. Also suppresses `httpx2` log noise at INFO level in `logging_config.py`.
coding-agent-marvin8 commented 2026-06-12 21:39:35 +00:00 (Migrated from codeberg.org)

app.py line 143 (review 1) · app.py line 143 (review 2) — WuMing

The return value of main() is ignored. Previously the code checked if not result: typer.Abort(), causing an abnormal exit when feed processing failed. Now the process exits with code 0 regardless.

🔴 This is a pre-existing bug, not introduced by this PR. In the original code both typer.Abort() and typer.Exit() were instantiated but never raisedtyper.Abort() creates the exception object but does not throw it, so the process always exited with code 0 regardless of result. The new code is more honest about that behaviour. Properly fixing the exit code (and the inverted condition — if not result aborted when there was no error, which is backwards) is a separate concern that belongs in its own issue and PR rather than being bundled into a like-for-like framework swap.

[`app.py` line 143 (review 1)](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367332) · [`app.py` line 143 (review 2)](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367392) — WuMing > The return value of main() is ignored. Previously the code checked `if not result: typer.Abort()`, causing an abnormal exit when feed processing failed. Now the process exits with code 0 regardless. 🔴 This is a **pre-existing bug**, not introduced by this PR. In the original code both `typer.Abort()` and `typer.Exit()` were *instantiated* but never *raised* — `typer.Abort()` creates the exception object but does not throw it, so the process always exited with code 0 regardless of `result`. The new code is more honest about that behaviour. Properly fixing the exit code (and the inverted condition — `if not result` aborted when there was *no* error, which is backwards) is a separate concern that belongs in its own issue and PR rather than being bundled into a like-for-like framework swap.
coding-agent-marvin8 commented 2026-06-12 21:39:43 +00:00 (Migrated from codeberg.org)

app.py config_file · app.py config_file · app.py logging_config · app.py logging_config · app.py url_file · app.py url_file · utils.py config_file · utils.py config_json — WuMing

Removed typer file validations (file_okay, dir_okay, writable, resolve_path, exists, readable). This can cause late and obscure errors if the user supplies an invalid path.

🔴 Intentional scope reduction. The typer/click file_okay, dir_okay, readable, writable, resolve_path, and exists constraints are typer-specific and have no direct cyclopts equivalent — cyclopts.validators.Path only covers exists. This PR is a like-for-like framework swap; adding manual validation to every path parameter goes beyond that scope. A follow-up issue can track that work if it's considered worthwhile.

[`app.py` config_file](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367335) · [`app.py` config_file](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367395) · [`app.py` logging_config](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367338) · [`app.py` logging_config](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367401) · [`app.py` url_file](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367341) · [`app.py` url_file](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367404) · [`utils.py` config_file](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367344) · [`utils.py` config_json](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367347) — WuMing > Removed typer file validations (file_okay, dir_okay, writable, resolve_path, exists, readable). This can cause late and obscure errors if the user supplies an invalid path. 🔴 **Intentional scope reduction.** The typer/click `file_okay`, `dir_okay`, `readable`, `writable`, `resolve_path`, and `exists` constraints are typer-specific and have no direct cyclopts equivalent — `cyclopts.validators.Path` only covers `exists`. This PR is a like-for-like framework swap; adding manual validation to every path parameter goes beyond that scope. A follow-up issue can track that work if it's considered worthwhile.
coding-agent-marvin8 commented 2026-06-12 21:39:51 +00:00 (Migrated from codeberg.org)

logging_config.py line 14 (review 1) · logging_config.py line 14 (review 2) — WuMing

Added 'httpx2' to the list of quiet loggers. If the project does not use the 'httpx2' library, this entry is unnecessary. If 'httpx2' is a typo for 'httpx', it may be harmless but could be confusing.

httpx2 is intentional and correct — it is the logger name used by the HTTP/2 transport layer inside httpx[http2] (which this project uses; see httpx[http2]~=0.28.1 in pyproject.toml). When httpx opens an HTTP/2 connection via the h2 backend it emits connection-level messages under the httpx2 logger, separate from the main httpx logger. Without this entry those low-level frames appear as INFO noise. Not a typo.

[`logging_config.py` line 14 (review 1)](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367350) · [`logging_config.py` line 14 (review 2)](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367407) — WuMing > Added 'httpx2' to the list of quiet loggers. If the project does not use the 'httpx2' library, this entry is unnecessary. If 'httpx2' is a typo for 'httpx', it may be harmless but could be confusing. ✅ `httpx2` is **intentional and correct** — it is the logger name used by the HTTP/2 transport layer inside `httpx[http2]` (which this project uses; see `httpx[http2]~=0.28.1` in `pyproject.toml`). When httpx opens an HTTP/2 connection via the `h2` backend it emits connection-level messages under the `httpx2` logger, separate from the main `httpx` logger. Without this entry those low-level frames appear as INFO noise. Not a typo.
coding-agent-marvin8 commented 2026-06-12 21:39:59 +00:00 (Migrated from codeberg.org)

app.py line 128 — WuMing

Removed input validations for --limit parameter (previously had none). Not critical, but loss of consistency.

🔴 The original typer.Option for --limit had no file or path constraints — only a help string. Nothing was removed; the behaviour is identical. This appears to be a spurious finding.

[`app.py` line 128](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367398) — WuMing > Removed input validations for --limit parameter (previously had none). Not critical, but loss of consistency. 🔴 The original `typer.Option` for `--limit` had **no** file or path constraints — only a `help` string. Nothing was removed; the behaviour is identical. This appears to be a spurious finding.
coding-agent-marvin8 commented 2026-06-12 21:40:07 +00:00 (Migrated from codeberg.org)

tests/unit/test_cli_apps.py line 1 — WuMing

Tests only verify that --help exits 0 and contains a keyword. No tests exist for actual command execution, error scenarios, or non-zero exit codes.

🔴 The --help tests verify that each App() is correctly wired — options registered, app callable, standard help flag functional. Testing actual command execution via the shims would invoke asyncio.run(main(...)), which requires a real config file and fediverse credentials — that is integration testing, not unit testing. The business logic of convert_config_json is already unit-tested directly in test_convert_config.py, and main() / import_urls() are covered by other tests in the suite. The new tests add the appropriate level of coverage for the code added in this PR.

[`tests/unit/test_cli_apps.py` line 1](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367353) — WuMing > Tests only verify that --help exits 0 and contains a keyword. No tests exist for actual command execution, error scenarios, or non-zero exit codes. 🔴 The `--help` tests verify that each `App()` is correctly wired — options registered, app callable, standard help flag functional. Testing *actual command execution* via the shims would invoke `asyncio.run(main(...))`, which requires a real config file and fediverse credentials — that is integration testing, not unit testing. The business logic of `convert_config_json` is already unit-tested directly in `test_convert_config.py`, and `main()` / `import_urls()` are covered by other tests in the suite. The new tests add the appropriate level of coverage for the code added in this PR.
coding-agent-marvin8 commented 2026-06-12 21:40:15 +00:00 (Migrated from codeberg.org)

Release-Notes.md line 13 · Release-Notes.md line 19 — WuMing

The entry 'Suppressed httpx2 log noise at INFO level' is generic — adding context like 'similar to httpx and httpcore' would improve clarity.

Minor grammar: 'suppressed at INFO level' could be clearer as 'suppressed at the INFO level' or 'set to WARNING level'.

🔴 The prose block immediately below the bullet already provides the context: "The httpx2 logger is now suppressed at INFO level alongside httpx and httpcore". The one-line bullet is intentionally terse — the prose is where the detail lives. On the grammar point, "at INFO level" (without the article) is standard Python logging phrasing consistent with the Python docs and the rest of the codebase, so no change is made.

[`Release-Notes.md` line 13](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367356) · [`Release-Notes.md` line 19](https://codeberg.org/marvinsmastodontools/feed2fedi/pulls/86#issuecomment-17367359) — WuMing > The entry 'Suppressed httpx2 log noise at INFO level' is generic — adding context like 'similar to httpx and httpcore' would improve clarity. > Minor grammar: 'suppressed at INFO level' could be clearer as 'suppressed at the INFO level' or 'set to WARNING level'. 🔴 The prose block immediately below the bullet already provides the context: *"The `httpx2` logger is now suppressed at INFO level alongside `httpx` and `httpcore`"*. The one-line bullet is intentionally terse — the prose is where the detail lives. On the grammar point, "at INFO level" (without the article) is standard Python logging phrasing consistent with the Python docs and the rest of the codebase, so no change is made.
Sign in to join this conversation.
No description provided.