Skip to content

fix(hosting-cli): make reflex-base an optional dependency - #6939

Open
adhami3310 wants to merge 5 commits into
mainfrom
claude/reflex-hosting-cli-deps-31c397
Open

fix(hosting-cli): make reflex-base an optional dependency#6939
adhami3310 wants to merge 5 commits into
mainfrom
claude/reflex-hosting-cli-deps-31c397

Conversation

@adhami3310

@adhami3310 adhami3310 commented Aug 24, 2026

Copy link
Copy Markdown
Member

Fixes ENG-11615.

The problem

Since #6866, reflex-hosting-cli declares reflex-base >= 0.9.8.post19.dev0 while constants/hosting.py still advertises reflex support down to 0.6.6.post1 (recommending 0.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 (post19 predates reflex_base.utils.log existing at all), but that is moot now.

The fix

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 — 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:

reflex >= 0.9 older reflex
LogLevel reflex_base.constants.base new reflex_cli.constants.log_level
SUCCESS, set_log_level reflex_base.utils.log new reflex_cli.utils.log
print/print_table/ask/status/rule/progress reflex_base.utils.console reflex_cli.utils.console

On 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 LogLevel lives in its own module so pyright checks it independently rather than inside an except ImportError branch.

MINIMUM_REFLEX_VERSION / RECOMMENDED_REFLEX_VERSION are left at 0.6.6.post1 / 0.7.6 — they are truthful again.

API change

reflex_cli.utils.console drops the PoorProgress and is_debug re-exports (added by #6866, never used) and the debug/info/success/log/warn/error/timing/deprecate re-exports, rather than duplicating them into the fallback. The CLI has logged through logging since #6866 and reflex-base deprecated the legacy helpers in #6867. This is an unreleased surface, so no deprecation cycle applies.

Verification

  • ruff check / ruff format clean; pyright reflex packages/reflex-hosting-cli tests reports 0 errors
  • 7672 unit tests pass, 74.87% coverage
  • check_min_deps.py 18/18 PASS; --check-dev-pins now reports zero dev pins repo-wide
  • 24 new tests in tests/units/reflex_cli/utils/test_log.py install a meta-path finder that makes reflex_base unimportable, then assert every CLI module still imports and the fallback sink renders, gates and routes correctly. Confirmed the guard bites: reintroducing from reflex_base.utils import log in scan.py fails test_cli_imports_without_reflex_base for both scan and deployments.

Reviewer notes

  • check_min_deps.py does 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.
  • The fallback duplicates ~60 lines of reflex-base's RichConsoleHandler. If those styles drift in reflex-base, the fallback diverges silently — only the LogLevel enum is cross-checked against reflex-base, not the styles.
  • constants/base.py imports reflex-base's LogLevel under TYPE_CHECKING, so type-checking the CLI without reflex-base installed shows an unresolved import. This is deliberate: it keeps reflex/reflex.py passing config.loglevel straight through instead of reviving the _convert_reflex_loglevel_to_reflex_cli_loglevel mapper ENG-10963 refactor(log): hosting CLI shares the reflex-base console and logging (4/5) #6866 deleted.
  • The fallback attaches no handler until set_log_level is called; before that, records fall to logging.lastResort. Every CLI entry point calls it first, so this only affects direct library use.
  • The issue's separate open question — whether check_min_deps.py should validate dev-pinned floors against workspace commit distance — is not addressed here.

Review in cubic

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.
@adhami3310
adhami3310 requested a review from a team as a code owner August 24, 2026 23:16
@linear-code

linear-code Bot commented Aug 24, 2026

Copy link
Copy Markdown

ENG-11615

@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR removes the hosting CLI’s mandatory reflex-base dependency while preserving shared behavior when that package is available.

  • Adds local fallback implementations for log levels, logging, and interactive console helpers.
  • Redirects hosting CLI modules through the compatibility layer.
  • Adds regression coverage for supported Reflex versions and operation without reflex-base.
  • Updates dependency metadata, lock data, and release notes.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

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

@codspeed-hq

codspeed-hq Bot commented Aug 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 27 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/reflex-hosting-cli-deps-31c397 (6cd5694) with main (f35b493)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 18 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-hosting-cli/src/reflex_cli/constants/log_level.py
Comment thread tests/units/reflex_cli/utils/test_log.py Outdated
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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 1 file (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread tests/units/reflex_cli/test_min_reflex_support.py Outdated
…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.
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