fix(hosting-cli): make reflex-base an optional dependency - #6939
fix(hosting-cli): make reflex-base an optional dependency#6939adhami3310 wants to merge 5 commits into
Conversation
reflex-hosting-cli declared `reflex-base >= 0.9.8.post19.dev0` while advertising support for reflex down to 0.6.6.post1. reflex-base does not exist below 0.9.0, so that floor could not be satisfied on any reflex the CLI claims to support, and on reflex 0.8.9 (which declares no reflex-base dependency) pip would silently install a second, mismatched framework base alongside it with no conflict to detect. The dependency was never needed for the shared logging pipeline: reflex_base.utils.log lists "reflex_cli" in PACKAGE_LOGGER_NAMES, so it parents the CLI's loggers from its own side. Only three things were genuinely imported — LogLevel, log.SUCCESS, and the rich console helpers — and each is now resolved from reflex-base when it is importable and from a local fork otherwise. On reflex 0.9 every name resolves to the identical reflex-base object, so behavior there is unchanged. The forked LogLevel moves to reflex_cli.constants.log_level so it is type-checked on its own, and reflex_cli.utils.log holds the fallback SUCCESS level and rich handler (same styles, stderr routing and level gating as reflex-base). reflex_cli.utils.console drops the PoorProgress, is_debug and debug/info/success/log/warn/error/timing/deprecate re-exports rather than duplicate them into the fallback: the CLI has logged through `logging` since #6866, and reflex-base deprecated the legacy helpers in #6867.
Greptile SummaryThe PR removes the hosting CLI’s mandatory
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/reflex-hosting-cli/pyproject.toml | Removes the incompatible mandatory reflex-base dependency and workspace source. |
| packages/reflex-hosting-cli/src/reflex_cli/constants/base.py | Selects the shared LogLevel when available and otherwise uses the local compatible enum. |
| packages/reflex-hosting-cli/src/reflex_cli/constants/log_level.py | Adds the fallback log-level enum needed on Reflex releases predating reflex-base. |
| packages/reflex-hosting-cli/src/reflex_cli/utils/log.py | Adds the optional shared logging integration and local Rich-based fallback sink. |
| packages/reflex-hosting-cli/src/reflex_cli/utils/console.py | Delegates interactive helpers to reflex-base when installed and supplies local equivalents otherwise. |
| tests/units/reflex_cli/test_min_reflex_support.py | Adds guards preventing dependencies that contradict the advertised minimum Reflex version. |
| tests/units/reflex_cli/utils/test_log.py | Exercises imports and logging behavior with reflex-base deliberately unavailable. |
Reviews (5): Last reviewed commit: "Merge main into the reflex-base optional..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
All reported issues were addressed across 18 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The runtime half was already covered: test_cli_imports_without_reflex_base fails if a CLI module imports a workspace package older reflex does not ship. Nothing covered the packaging half — re-adding `reflex-base` to the declared dependencies without importing it at module scope broke no test. Three interlocking guards, derived from the checkout rather than hard-coded so a package added under packages/ later is covered automatically: - no declared dependency on a workspace package while MINIMUM_REFLEX_VERSION predates the workspace split (reflex 0.9.0, where reflex-base first shipped) - no [tool.uv.sources] workspace entry, which resolves locally and so hides an unsatisfiable dependency inside the monorepo - MINIMUM_REFLEX_VERSION <= RECOMMENDED_REFLEX_VERSION, so the CLI cannot gate on a version it then tells the user to upgrade away from The first guard skips itself if the floor is raised past the split, at which point a workspace dependency is legitimate — and the third then catches the half-migration of raising the minimum without the recommendation.
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…e leaking Review feedback on #6939. - The forked LogLevel was missing subprocess_level(), which the shared enum defines. Nothing calls it on the CLI's enum today (only reflex calls it, on its own config.loglevel), but parity is the fork's whole contract: a method present on reflex 0.9 and absent on older reflex is precisely the bug class this package guards against. Added it, and added a test asserting the fork covers reflex-base's entire public LogLevel API so the next gap fails loudly rather than waiting to be noticed. - test_set_log_level_accepts_strings exercised the reflex-base path outside the isolation context. That path is process-wide -- it sets REFLEX_LOGLEVEL so subprocesses inherit the level, and moves a module global -- so the test left REFLEX_LOGLEVEL=info behind for the rest of the session. Sandboxed the env var with monkeypatch and restore the previous level in a finally. - codespell: "re-use" -> "reuse".
…ce member Review feedback on #6939. The guard treated every distribution under packages/ as unavailable at the advertised reflex floor, which is too broad: reflex-release and reflex-docgen are ordinary PyPI distributions that install fine against any reflex, so depending on one would have failed the test for no reason. The set that actually matters is the framework runtime -- the workspace packages reflex itself depends on. Their presence and version are decided by whichever reflex the user installed, which is what makes a CLI dependency on one either unsatisfiable or a silent second copy. That set is now derived by intersecting the root package's dependencies with the workspace members (minus the hosting CLI itself), so it keeps tracking the checkout. The discoverability test now pins both directions: reflex-base and reflex-components-core are in the banned set, reflex-release and reflex-docgen are not.
#6938 landed the presigned archive upload, which touches the same console module this branch restructures. Conflict resolution: - console.py: kept this branch's guarded import block and carried transfer_progress() forward. It gated its progress bar on reflex_base.utils.log.is_json_mode() directly, which no longer exists as a hard import, so it goes through reflex_cli.utils.log.is_json_mode() instead -- forwarded to reflex-base when installed, False otherwise, since JSON output is a reflex-base pipeline feature with nothing to stay quiet for without it. - hosting.py: the upload retry path called console.warn(), one of the legacy helpers this branch stopped re-exporting (deprecated in reflex-base by #6867). Now logger.warning(), matching every other message in the file. - transfer_progress's docstring justified living in this package by not wanting to raise the reflex-base floor. There is no floor now, so it says what is actually true: the CLI has to render it with or without reflex-base. Verified the merged upload path renders on real reflex 0.8.9 with no reflex-base installed, and added test_fallback_progress_bars to keep both progress bars covered on that path.
Fixes ENG-11615.
The problem
Since #6866,
reflex-hosting-clideclaresreflex-base >= 0.9.8.post19.dev0whileconstants/hosting.pystill advertises reflex support down to0.6.6.post1(recommending0.7.6). reflex-base does not exist below 0.9.0 — it was carved out in #6281 and reflex only began depending on it at 0.9.x. So the declared floor cannot be satisfied on any reflex the CLI claims to support.It fails quietly rather than loudly: reflex 0.8.9 declares no reflex-base dependency, so pip installs reflex 0.8.9 and reflex-base 0.9.8 side by side with no conflict to detect. The runtime gate passes and the user carries a second, mismatched framework base.
Nothing is broken for users today — this only exists on
main. Published 0.1.70 was cut ~20 hours before #6866 landed, so 0.1.71 would be the first release to ship it. The declared pin was also understated by 25 commits (post19predatesreflex_base.utils.logexisting at all), but that is moot now.The fix
The dependency was never needed for the shared logging pipeline.
reflex_base.utils.loglists"reflex_cli"inPACKAGE_LOGGER_NAMES, so it parents the CLI's loggers from its own side — the CLI joins the pipeline without importing anything. Only three things were genuinely imported, and each is now resolved from reflex-base when importable and from a local fork otherwise:LogLevelreflex_base.constants.basereflex_cli.constants.log_levelSUCCESS,set_log_levelreflex_base.utils.logreflex_cli.utils.logprint/print_table/ask/status/rule/progressreflex_base.utils.consolereflex_cli.utils.consoleOn reflex 0.9 every name resolves to the identical reflex-base object, so behavior there is unchanged. #6866's refactor — standard
logging, purple debug, errors to stderr, success hidden at--loglevel warning— is kept in full; the fallback handler reproduces the same styles, stderr routing and level gating.The forked
LogLevellives in its own module so pyright checks it independently rather than inside anexcept ImportErrorbranch.MINIMUM_REFLEX_VERSION/RECOMMENDED_REFLEX_VERSIONare left at0.6.6.post1/0.7.6— they are truthful again.API change
reflex_cli.utils.consoledrops thePoorProgressandis_debugre-exports (added by #6866, never used) and thedebug/info/success/log/warn/error/timing/deprecatere-exports, rather than duplicating them into the fallback. The CLI has logged throughloggingsince #6866 and reflex-base deprecated the legacy helpers in #6867. This is an unreleased surface, so no deprecation cycle applies.Verification
ruff check/ruff formatclean;pyright reflex packages/reflex-hosting-cli testsreports 0 errorscheck_min_deps.py18/18 PASS;--check-dev-pinsnow reports zero dev pins repo-widetests/units/reflex_cli/utils/test_log.pyinstall a meta-path finder that makesreflex_baseunimportable, then assert every CLI module still imports and the fallback sink renders, gates and routes correctly. Confirmed the guard bites: reintroducingfrom reflex_base.utils import loginscan.pyfailstest_cli_imports_without_reflex_basefor bothscananddeployments.Reviewer notes
check_min_deps.pydoes not cover the fallback. It fails only on errors new at minimum resolution, and reflex-base is absent from both its environments, so the unresolved import cancels out. The new unit tests are the only guard on that path.RichConsoleHandler. If those styles drift in reflex-base, the fallback diverges silently — only theLogLevelenum is cross-checked against reflex-base, not the styles.constants/base.pyimports reflex-base'sLogLevelunderTYPE_CHECKING, so type-checking the CLI without reflex-base installed shows an unresolved import. This is deliberate: it keepsreflex/reflex.pypassingconfig.loglevelstraight through instead of reviving the_convert_reflex_loglevel_to_reflex_cli_loglevelmapper ENG-10963 refactor(log): hosting CLI shares the reflex-base console and logging (4/5) #6866 deleted.set_log_levelis called; before that, records fall tologging.lastResort. Every CLI entry point calls it first, so this only affects direct library use.check_min_deps.pyshould validate dev-pinned floors against workspace commit distance — is not addressed here.