reflex-hosting-cli 0.1.70__tar.gz → 0.1.71a2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/CHANGELOG.md +31 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/PKG-INFO +1 -1
- reflex_hosting_cli-0.1.71a2/src/reflex_cli/constants/base.py +51 -0
- reflex_hosting_cli-0.1.71a2/src/reflex_cli/constants/log_level.py +115 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/utils/__init__.py +5 -1
- reflex_hosting_cli-0.1.71a2/src/reflex_cli/utils/cli_options.py +81 -0
- reflex_hosting_cli-0.1.71a2/src/reflex_cli/utils/console.py +184 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/utils/dependency.py +11 -8
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/utils/exceptions.py +4 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/utils/hosting.py +656 -128
- reflex_hosting_cli-0.1.71a2/src/reflex_cli/utils/log.py +131 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/apps.py +67 -56
- reflex_hosting_cli-0.1.71a2/src/reflex_cli/v2/auth.py +261 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/cli.py +136 -64
- reflex_hosting_cli-0.1.71a2/src/reflex_cli/v2/deploy.py +216 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/deployments.py +14 -3
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/gcp.py +34 -33
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/project.py +26 -23
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/providers.py +18 -17
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/scan.py +11 -8
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/secrets.py +20 -16
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/vmtypes_regions.py +6 -3
- reflex_hosting_cli-0.1.70/src/reflex_cli/constants/base.py +0 -58
- reflex_hosting_cli-0.1.70/src/reflex_cli/utils/console.py +0 -230
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/.gitignore +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/README.md +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/news/.gitkeep +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/pyproject.toml +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/__init__.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/cli.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/constants/__init__.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/constants/compiler.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/constants/hosting.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/core/__init__.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/core/config.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/deployments.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/__init__.py +0 -0
- {reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/v2/utils.py +0 -0
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
## v0.1.71a2 (2026-08-28)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- Fix for older reflex versions
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## v0.1.71a1 (2026-08-27)
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
|
|
12
|
+
- `REFLEX_ACCESS_TOKEN` now takes precedence over the token stored by `reflex login`. Previously the stored token won and the environment variable was consulted only when no token was stored, so exporting it to run a script against a different account had no effect on a machine that had ever logged in — silently, and with no way to tell which credential was in use. Exporting the variable is an explicit choice for that invocation; the config file is ambient state left behind by an earlier login. This changes behavior only when both are present and differ. `reflex cloud whoami` reports which source is in use. ([#6918](https://github.com/reflex-dev/reflex/issues/6918))
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- Added `reflex cloud whoami` and `reflex cloud token`. `whoami` reports the account, org, tier and token source that the CLI is authenticating as, resolving the token against the control plane without ever starting a browser login and without printing the token — it shows a non-reversible fingerprint instead, so two machines can be compared without anyone sharing a secret. `reflex cloud token` takes exactly one of `--print`, `--set TOKEN` or `--clear`: `--print` writes the raw token to stdout for capture (`export REFLEX_ACCESS_TOKEN=$(reflex cloud token --print)`), `--set` validates the token with the control plane before storing it and leaves the previous one in place if it is rejected, and `--clear` removes the stored token, noting when `REFLEX_ACCESS_TOKEN` remains set and will take over. ([#6918](https://github.com/reflex-dev/reflex/issues/6918))
|
|
17
|
+
- On the deploy that first lands an app on GCP, `--hostname` now doubles as the app's Cloud Run service name, so the service in the customer's console reads like the app's URL instead of `app-<uuid>`. A hostname the service-name grammar refuses (leading digit, over 49 characters, or the reserved `app-<uuid>` shape) is skipped with a note and the server generates a name from the app name; later GCP deploys never send one, since the name is pinned to the live service. ([#6937](https://github.com/reflex-dev/reflex/issues/6937))
|
|
18
|
+
- `reflex deploy` uploads a build's two archives straight to storage, concurrently and with a progress bar for each, instead of relaying them through the control plane. ([#6938](https://github.com/reflex-dev/reflex/issues/6938))
|
|
19
|
+
- `reflex cloud deploy` now reports why a deploy failed instead of exiting on a status string: the recorded reason, whether the failure was in your app or on Reflex's side, and the end of the build log when that is what explains it. ([#6948](https://github.com/reflex-dev/reflex/issues/6948))
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
- The hosting config file (`hosting_v1.json`) is now written atomically. `save_token_to_config` and `delete_token_from_config` opened it with mode `"w"`, truncating it before writing, so a failed write — a full disk, an I/O error, an interrupted process — left an empty file and destroyed the stored access token and selected project. Neither helper reports write failures to the caller (`save_token_to_config` logs a warning, `delete_token_from_config` only a debug message), so this was easy to miss. Both now serialize to a temporary file alongside the target and move it into place, leaving the existing credentials untouched when a write fails. A config that exists but cannot be read is no longer treated as empty either, so `delete_token_from_config` leaves a malformed file alone instead of replacing it; `save_token_to_config` still starts fresh from one, so a corrupt config cannot block re-authenticating. This also covers `reflex login` and `reflex logout`, which share these helpers. ([#6918](https://github.com/reflex-dev/reflex/issues/6918))
|
|
24
|
+
|
|
25
|
+
### Miscellaneous
|
|
26
|
+
|
|
27
|
+
- The hosting CLI's logging goes through standard python `logging`. On reflex 0.9 and up it shares the `reflex-base` console and `LogLevel`; on earlier reflex, where `reflex-base` is not installed, the CLI renders the same output itself. Debug output renders purple (was blue), errors go to stderr (was stdout), and success messages are hidden at `--loglevel warning`. ([#6866](https://github.com/reflex-dev/reflex/issues/6866))
|
|
28
|
+
- `reflex cloud token --set` accepts the token on stdin — pass `-`, or omit the value entirely — so live credentials need not appear in shell history or the process list. When stdin is a terminal it prompts without echoing. `reflex cloud whoami` writes its output directly rather than through the shared console, which applies rich markup and wraps to the terminal width: identifiers now print in full instead of being truncated to fit, and `--json` stays on one line so it can be piped. ([#6918](https://github.com/reflex-dev/reflex/issues/6918))
|
|
29
|
+
- The `reflex deploy` command implementation now lives here, in `reflex_cli.v2.deploy`. The package no longer imports the `reflex` framework at module scope, so it stays importable on its own. ([#6924](https://github.com/reflex-dev/reflex/issues/6924))
|
|
30
|
+
|
|
31
|
+
|
|
1
32
|
## v0.1.70 (2026-08-18)
|
|
2
33
|
|
|
3
34
|
### Deprecations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: reflex-hosting-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.71a2
|
|
4
4
|
Summary: Reflex Hosting CLI
|
|
5
5
|
Author-email: Nikhil Rao <nikhil@reflex.dev>, Alek Petuskey <alek@reflex.dev>
|
|
6
6
|
Maintainer-email: Simon Young <simon@reflex.dev>, Khaleel Al-Adhami <khaleel@reflex.dev>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Base file for constants that don't fit any other categories."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from platformdirs import PlatformDirs
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
# The two enums are interchangeable, so the shared one is what gets
|
|
12
|
+
# type-checked; reflex_cli.constants.log_level is checked on its own.
|
|
13
|
+
from reflex_base.constants.base import LogLevel as LogLevel
|
|
14
|
+
else:
|
|
15
|
+
# reflex-base only exists from reflex 0.9 on, and the hosting CLI supports
|
|
16
|
+
# older reflex too, so the fork is the baseline and the shared enum is
|
|
17
|
+
# adopted when it is there. Older reflex-base releases predate part of the
|
|
18
|
+
# enum's API, and adopting one of those leaves the CLI calling a method
|
|
19
|
+
# that does not exist, so those keep the fork as well.
|
|
20
|
+
from reflex_cli.constants.log_level import LogLevel as LogLevel
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from reflex_base.constants.base import LogLevel as _BaseLogLevel
|
|
24
|
+
except ImportError:
|
|
25
|
+
pass
|
|
26
|
+
else:
|
|
27
|
+
if hasattr(_BaseLogLevel, "to_logging_level"):
|
|
28
|
+
LogLevel = _BaseLogLevel
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Reflex(SimpleNamespace):
|
|
32
|
+
"""Base constants concerning Reflex. This is duplicate of the same class in reflex main."""
|
|
33
|
+
|
|
34
|
+
# The name of the Reflex package.
|
|
35
|
+
MODULE_NAME = "reflex"
|
|
36
|
+
|
|
37
|
+
# Files and directories used to init a new project.
|
|
38
|
+
# The directory to store reflex dependencies.
|
|
39
|
+
DIR = (
|
|
40
|
+
# on windows, we use C:/Users/<username>/AppData/Local/reflex.
|
|
41
|
+
# on macOS, we use ~/Library/Application Support/reflex.
|
|
42
|
+
# on linux, we use ~/.local/share/reflex.
|
|
43
|
+
PlatformDirs(MODULE_NAME, False).user_data_dir
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Dirs(SimpleNamespace):
|
|
48
|
+
"""Various directories/paths used by the CLI."""
|
|
49
|
+
|
|
50
|
+
# The cloud.yaml file.
|
|
51
|
+
CLOUD_YAML = "cloud.yml"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""The hosting CLI's own LogLevel, used when reflex-base is not installed.
|
|
2
|
+
|
|
3
|
+
reflex-base only exists from reflex 0.9 on, but the hosting CLI supports older
|
|
4
|
+
reflex too. :mod:`reflex_cli.constants.base` prefers the reflex-base enum when
|
|
5
|
+
it is importable and falls back to this one otherwise; the two are
|
|
6
|
+
interchangeable, with the same members and the same string values.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LogLevel(str, Enum):
|
|
16
|
+
"""The log levels."""
|
|
17
|
+
|
|
18
|
+
DEBUG = "debug"
|
|
19
|
+
DEFAULT = "default"
|
|
20
|
+
INFO = "info"
|
|
21
|
+
WARNING = "warning"
|
|
22
|
+
ERROR = "error"
|
|
23
|
+
CRITICAL = "critical"
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_string(cls, level: str | None) -> LogLevel | None:
|
|
27
|
+
"""Convert a string to a log level.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
level: The log level as a string.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The log level, or None if the string names no level.
|
|
34
|
+
"""
|
|
35
|
+
if not level:
|
|
36
|
+
return None
|
|
37
|
+
try:
|
|
38
|
+
return cls[level.upper()]
|
|
39
|
+
except KeyError:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
def to_logging_level(self) -> int:
|
|
43
|
+
"""Map this level to a stdlib logging level number.
|
|
44
|
+
|
|
45
|
+
DEFAULT acts as a threshold equivalent to INFO.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The stdlib logging level.
|
|
49
|
+
"""
|
|
50
|
+
return _LOGGING_LEVELS[self]
|
|
51
|
+
|
|
52
|
+
def subprocess_level(self) -> LogLevel:
|
|
53
|
+
"""Return the log level to hand to a subprocess.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
This level, or WARNING when it is DEFAULT.
|
|
57
|
+
"""
|
|
58
|
+
return self if self != LogLevel.DEFAULT else LogLevel.WARNING
|
|
59
|
+
|
|
60
|
+
# The str mixin supplies alphabetical comparisons, so all four operators
|
|
61
|
+
# must be overridden to compare by verbosity rank instead.
|
|
62
|
+
def __lt__(self, other: LogLevel) -> bool:
|
|
63
|
+
"""Compare log levels.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
other: The other log level.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if the log level is less verbose than the other log level.
|
|
70
|
+
"""
|
|
71
|
+
return _LOG_LEVEL_RANK[self] < _LOG_LEVEL_RANK[other]
|
|
72
|
+
|
|
73
|
+
def __le__(self, other: LogLevel) -> bool:
|
|
74
|
+
"""Compare log levels.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
other: The other log level.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
True if the log level is less than or equal to the other log level.
|
|
81
|
+
"""
|
|
82
|
+
return _LOG_LEVEL_RANK[self] <= _LOG_LEVEL_RANK[other]
|
|
83
|
+
|
|
84
|
+
def __gt__(self, other: LogLevel) -> bool:
|
|
85
|
+
"""Compare log levels.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
other: The other log level.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if the log level is more verbose-restrictive than the other.
|
|
92
|
+
"""
|
|
93
|
+
return _LOG_LEVEL_RANK[self] > _LOG_LEVEL_RANK[other]
|
|
94
|
+
|
|
95
|
+
def __ge__(self, other: LogLevel) -> bool:
|
|
96
|
+
"""Compare log levels.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
other: The other log level.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
True if the log level is greater than or equal to the other.
|
|
103
|
+
"""
|
|
104
|
+
return _LOG_LEVEL_RANK[self] >= _LOG_LEVEL_RANK[other]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_LOG_LEVEL_RANK = {level: rank for rank, level in enumerate(LogLevel)}
|
|
108
|
+
_LOGGING_LEVELS = {
|
|
109
|
+
LogLevel.DEBUG: logging.DEBUG,
|
|
110
|
+
LogLevel.DEFAULT: logging.INFO,
|
|
111
|
+
LogLevel.INFO: logging.INFO,
|
|
112
|
+
LogLevel.WARNING: logging.WARNING,
|
|
113
|
+
LogLevel.ERROR: logging.ERROR,
|
|
114
|
+
LogLevel.CRITICAL: logging.CRITICAL,
|
|
115
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"""Reflex utilities."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
|
|
3
5
|
import click
|
|
4
6
|
|
|
5
7
|
from reflex_cli.constants.hosting import ReflexHostingCli
|
|
6
8
|
|
|
7
9
|
from . import console
|
|
8
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
def disabled_v1_hosting(*args, **kwargs):
|
|
11
15
|
"""Print error and exit when using v1 hosting commands.
|
|
@@ -18,7 +22,7 @@ def disabled_v1_hosting(*args, **kwargs):
|
|
|
18
22
|
Exit: Always.
|
|
19
23
|
|
|
20
24
|
"""
|
|
21
|
-
|
|
25
|
+
logger.error(
|
|
22
26
|
"The alpha hosting service has been decommissioned as of Dec 5, 2024. "
|
|
23
27
|
f"Please upgrade to reflex>={ReflexHostingCli.MINIMUM_REFLEX_VERSION} to use Reflex Cloud hosting."
|
|
24
28
|
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Shared click options for the hosting CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from reflex_cli.constants.base import LogLevel
|
|
10
|
+
from reflex_cli.utils import console
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def set_loglevel(ctx: click.Context, self: click.Parameter, value: str | None):
|
|
17
|
+
"""Set the log level.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
ctx: The click context.
|
|
21
|
+
self: The click command.
|
|
22
|
+
value: The log level to set.
|
|
23
|
+
"""
|
|
24
|
+
if value is not None:
|
|
25
|
+
console.set_log_level(value)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
loglevel_option = click.option(
|
|
29
|
+
"--loglevel",
|
|
30
|
+
"--log-level",
|
|
31
|
+
"loglevel",
|
|
32
|
+
type=click.Choice(
|
|
33
|
+
[loglevel.value for loglevel in LogLevel],
|
|
34
|
+
case_sensitive=False,
|
|
35
|
+
),
|
|
36
|
+
is_eager=True,
|
|
37
|
+
callback=set_loglevel,
|
|
38
|
+
expose_value=False,
|
|
39
|
+
help="The log level to use.",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def set_log_json(ctx: click.Context, self: click.Parameter, value: bool):
|
|
44
|
+
"""Enable machine-readable JSON log output.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
ctx: The click context.
|
|
48
|
+
self: The click command.
|
|
49
|
+
value: Whether --json was passed.
|
|
50
|
+
"""
|
|
51
|
+
if value:
|
|
52
|
+
try:
|
|
53
|
+
from reflex_base.utils.log import set_json_mode
|
|
54
|
+
except ImportError:
|
|
55
|
+
# JSON records are a reflex-base feature; without it there is no
|
|
56
|
+
# pipeline to switch, matching reflex_cli.utils.log.is_json_mode.
|
|
57
|
+
return
|
|
58
|
+
set_json_mode(True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
json_option = click.option(
|
|
62
|
+
"--json",
|
|
63
|
+
"log_json",
|
|
64
|
+
is_flag=True,
|
|
65
|
+
is_eager=True,
|
|
66
|
+
callback=set_log_json,
|
|
67
|
+
expose_value=False,
|
|
68
|
+
help="Output logs as machine-readable JSON records.",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def log_options(func: Callable) -> Callable:
|
|
73
|
+
"""Apply the shared logging CLI options (--loglevel, --json).
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
func: The click command callback.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The decorated callback.
|
|
80
|
+
"""
|
|
81
|
+
return loglevel_option(json_option(func))
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Interactive console helpers, shared with reflex-base when it is installed.
|
|
2
|
+
|
|
3
|
+
Level-gated messages go through :mod:`logging` (see :mod:`reflex_cli.utils.log`);
|
|
4
|
+
what remains here are the rich features that are output rather than logging:
|
|
5
|
+
prompts, tables, spinners and plain prints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
from typing import overload
|
|
12
|
+
|
|
13
|
+
from reflex_cli.constants.base import LogLevel
|
|
14
|
+
from reflex_cli.utils.log import HAS_REFLEX_BASE, is_json_mode
|
|
15
|
+
from reflex_cli.utils.log import set_log_level as _set_log_level
|
|
16
|
+
|
|
17
|
+
if HAS_REFLEX_BASE:
|
|
18
|
+
from reflex_base.utils.console import ask as ask
|
|
19
|
+
from reflex_base.utils.console import print as print
|
|
20
|
+
from reflex_base.utils.console import print_table as print_table
|
|
21
|
+
from reflex_base.utils.console import progress as progress
|
|
22
|
+
from reflex_base.utils.console import rule as rule
|
|
23
|
+
from reflex_base.utils.console import status as status
|
|
24
|
+
else:
|
|
25
|
+
from rich.console import Console, OverflowMethod
|
|
26
|
+
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
|
27
|
+
from rich.prompt import Prompt
|
|
28
|
+
from rich.table import Table
|
|
29
|
+
|
|
30
|
+
_console = Console(highlight=False)
|
|
31
|
+
|
|
32
|
+
def print(msg: str, **kwargs):
|
|
33
|
+
"""Print a message.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
msg: The message to print.
|
|
37
|
+
kwargs: Keyword arguments to pass to the print function.
|
|
38
|
+
"""
|
|
39
|
+
_console.print(msg, **kwargs)
|
|
40
|
+
|
|
41
|
+
def print_table(
|
|
42
|
+
tabular_data: list[list[str]],
|
|
43
|
+
headers: Sequence[str] = (),
|
|
44
|
+
overflow: OverflowMethod = "ellipsis",
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Print a table to the console.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
tabular_data: The data to print in tabular format.
|
|
50
|
+
headers: The headers for the table.
|
|
51
|
+
overflow: What to do with a cell too wide for its column. The
|
|
52
|
+
default cuts it short; pass "fold" for values a user has to
|
|
53
|
+
read in full, such as an email or an identifier.
|
|
54
|
+
"""
|
|
55
|
+
table = Table()
|
|
56
|
+
|
|
57
|
+
for column in headers:
|
|
58
|
+
table.add_column(column, overflow=overflow)
|
|
59
|
+
|
|
60
|
+
for row in tabular_data:
|
|
61
|
+
table.add_row(*row)
|
|
62
|
+
|
|
63
|
+
_console.print(table)
|
|
64
|
+
|
|
65
|
+
def rule(title: str, **kwargs):
|
|
66
|
+
"""Print a horizontal rule with a title.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
title: The title of the rule.
|
|
70
|
+
kwargs: Keyword arguments to pass to the print function.
|
|
71
|
+
"""
|
|
72
|
+
_console.rule(title, **kwargs)
|
|
73
|
+
|
|
74
|
+
@overload
|
|
75
|
+
def ask(
|
|
76
|
+
question: str,
|
|
77
|
+
choices: list[str] | None = None,
|
|
78
|
+
*,
|
|
79
|
+
show_choices: bool = True,
|
|
80
|
+
) -> str: ...
|
|
81
|
+
|
|
82
|
+
@overload
|
|
83
|
+
def ask(
|
|
84
|
+
question: str,
|
|
85
|
+
choices: list[str] | None = None,
|
|
86
|
+
default: str = ...,
|
|
87
|
+
show_choices: bool = True,
|
|
88
|
+
) -> str: ...
|
|
89
|
+
|
|
90
|
+
def ask(
|
|
91
|
+
question: str,
|
|
92
|
+
choices: list[str] | None = None,
|
|
93
|
+
default: str | None = None,
|
|
94
|
+
show_choices: bool = True,
|
|
95
|
+
) -> str | None:
|
|
96
|
+
"""Ask the user a question, optionally with a list of choices.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
question: The question to ask the user.
|
|
100
|
+
choices: A list of choices to select from.
|
|
101
|
+
default: The default option selected.
|
|
102
|
+
show_choices: Whether to show the choices.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A string with the user input.
|
|
106
|
+
"""
|
|
107
|
+
return Prompt.ask(
|
|
108
|
+
question, choices=choices, default=default, show_choices=show_choices
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def progress():
|
|
112
|
+
"""Create a new progress bar.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
A new progress bar.
|
|
116
|
+
"""
|
|
117
|
+
return Progress(
|
|
118
|
+
*Progress.get_default_columns()[:-1],
|
|
119
|
+
MofNCompleteColumn(),
|
|
120
|
+
TimeElapsedColumn(),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def status(*args, **kwargs):
|
|
124
|
+
"""Create a status with a spinner.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
*args: Args to pass to the status.
|
|
128
|
+
**kwargs: Kwargs to pass to the status.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
A new status.
|
|
132
|
+
"""
|
|
133
|
+
return _console.status(*args, **kwargs)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def set_log_level(log_level: LogLevel | str | None):
|
|
137
|
+
"""Set the log level, accepting legacy string values and foreign enums.
|
|
138
|
+
|
|
139
|
+
Older reflex releases call the CLI's entrypoints directly rather than
|
|
140
|
+
through click, handing over their own ``reflex.constants.LogLevel``. That is
|
|
141
|
+
a different class from this package's -- and, before the enum grew
|
|
142
|
+
``to_logging_level``, one the CLI cannot use -- so it is resolved by value,
|
|
143
|
+
the way a legacy plain string is. An unrecognized value falls back to INFO.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
log_level: The log level to set, or None to leave it unchanged.
|
|
147
|
+
"""
|
|
148
|
+
if log_level is not None and not isinstance(log_level, LogLevel):
|
|
149
|
+
# A foreign LogLevel carries the level in ``value``; a plain string is
|
|
150
|
+
# already the value. Either way it resolves by name.
|
|
151
|
+
log_level = (
|
|
152
|
+
LogLevel.from_string(getattr(log_level, "value", log_level))
|
|
153
|
+
or LogLevel.INFO
|
|
154
|
+
)
|
|
155
|
+
_set_log_level(log_level)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def transfer_progress():
|
|
159
|
+
"""Create a progress bar measured in bytes rather than in steps.
|
|
160
|
+
|
|
161
|
+
Lives here rather than beside ``progress`` in reflex-base because only the
|
|
162
|
+
deploy upload wants it, and the CLI has to render it whether or not
|
|
163
|
+
reflex-base is installed.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
A new progress bar, sized and paced for a file transfer.
|
|
167
|
+
"""
|
|
168
|
+
from rich.progress import (
|
|
169
|
+
BarColumn,
|
|
170
|
+
DownloadColumn,
|
|
171
|
+
Progress,
|
|
172
|
+
TextColumn,
|
|
173
|
+
TimeElapsedColumn,
|
|
174
|
+
TransferSpeedColumn,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return Progress(
|
|
178
|
+
TextColumn("[progress.description]{task.description}"),
|
|
179
|
+
BarColumn(),
|
|
180
|
+
DownloadColumn(),
|
|
181
|
+
TransferSpeedColumn(),
|
|
182
|
+
TimeElapsedColumn(),
|
|
183
|
+
disable=is_json_mode(),
|
|
184
|
+
)
|
{reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/utils/dependency.py
RENAMED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import importlib.metadata
|
|
6
6
|
import io
|
|
7
|
+
import logging
|
|
7
8
|
import re
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
@@ -13,6 +14,8 @@ from urllib.parse import urlparse
|
|
|
13
14
|
from reflex_cli import constants
|
|
14
15
|
from reflex_cli.utils import console
|
|
15
16
|
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
def detect_encoding(filename: Path) -> str | None:
|
|
18
21
|
"""Detect the encoding of the given file.
|
|
@@ -70,7 +73,7 @@ def check_requirements():
|
|
|
70
73
|
SystemExit: If no requirements.txt is found.
|
|
71
74
|
"""
|
|
72
75
|
if not does_requirements_file_or_pyproject_exist():
|
|
73
|
-
|
|
76
|
+
logger.warning("No requirements.txt or pyproject.toml found.")
|
|
74
77
|
return
|
|
75
78
|
|
|
76
79
|
if not Path(constants.RequirementsTxt.FILE).exists():
|
|
@@ -94,8 +97,8 @@ def check_requirements():
|
|
|
94
97
|
check=True,
|
|
95
98
|
)
|
|
96
99
|
except subprocess.CalledProcessError as cpe:
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
logger.debug(f"Unable to run pip freeze in subprocess: {cpe}")
|
|
101
|
+
logger.warning(
|
|
99
102
|
"Unable to detect installed packages in your environment using pip freeze."
|
|
100
103
|
" Please make sure your requirements.txt is up to date."
|
|
101
104
|
)
|
|
@@ -112,8 +115,8 @@ def check_requirements():
|
|
|
112
115
|
if Path(constants.RequirementsTxt.FILE).exists():
|
|
113
116
|
with Path(constants.RequirementsTxt.FILE).open(encoding=encoding) as f:
|
|
114
117
|
current_requirements_lines = set(f)
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
logger.debug("Current requirements.txt:")
|
|
119
|
+
logger.debug("".join(current_requirements_lines))
|
|
117
120
|
|
|
118
121
|
diff = list(new_requirements_lines - current_requirements_lines)
|
|
119
122
|
|
|
@@ -121,11 +124,11 @@ def check_requirements():
|
|
|
121
124
|
return
|
|
122
125
|
|
|
123
126
|
if not current_requirements_lines:
|
|
124
|
-
|
|
127
|
+
logger.warning("It seems like there's no requirements.txt in your project.")
|
|
125
128
|
raise SystemExit("No requirements.txt found.")
|
|
126
129
|
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
logger.warning("Detected difference in requirements.txt and python env.")
|
|
131
|
+
logger.warning("The requirements.txt may need to be updated.")
|
|
129
132
|
console.ask("Do you wish to proceed? (ctl+c to cancel)")
|
|
130
133
|
return
|
|
131
134
|
|
{reflex_hosting_cli-0.1.70 → reflex_hosting_cli-0.1.71a2}/src/reflex_cli/utils/exceptions.py
RENAMED
|
@@ -43,6 +43,10 @@ class ResponseError(ReflexHostingCliError):
|
|
|
43
43
|
"""Raised when a response is not as expected."""
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
class ArchiveUploadError(ReflexHostingCliError):
|
|
47
|
+
"""Raised when a build's archives cannot be uploaded to storage."""
|
|
48
|
+
|
|
49
|
+
|
|
46
50
|
class ConfigError(ReflexHostingCliError):
|
|
47
51
|
"""Raised when there is an error with the config."""
|
|
48
52
|
|