reflex-hosting-cli 0.1.68__tar.gz → 0.1.69__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.68 → reflex_hosting_cli-0.1.69}/CHANGELOG.md +11 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/PKG-INFO +1 -1
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/hosting.py +56 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/cli.py +90 -24
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/.gitignore +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/README.md +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/news/.gitkeep +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/pyproject.toml +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/__init__.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/cli.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/__init__.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/base.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/compiler.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/hosting.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/core/__init__.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/core/config.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/deployments.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/__init__.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/console.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/dependency.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/exceptions.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/__init__.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/apps.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/deployments.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/gcp.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/project.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/providers.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/scan.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/secrets.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/utils.py +0 -0
- {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/vmtypes_regions.py +0 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## v0.1.69 (2026-08-13)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- `deploy()` accepts `min_instances` and `max_instances`, which are applied to the app before the deployment is submitted so it picks the new autoscaling bounds up. Only the bounds explicitly passed are sent, so apps keep their platform defaults otherwise. ([#6884](https://github.com/reflex-dev/reflex/issues/6884))
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- `--vmtype` is no longer dropped when deploying to Google Cloud. The server maps VM types onto Cloud Run CPU/memory limits, so the flag is now passed through; only `--region` is still ignored for that target, since the region comes from the connected GCP account. ([#6884](https://github.com/reflex-dev/reflex/issues/6884))
|
|
10
|
+
|
|
11
|
+
|
|
1
12
|
## v0.1.68 (2026-08-03)
|
|
2
13
|
|
|
3
14
|
### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reflex-hosting-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.69
|
|
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>
|
|
@@ -1063,6 +1063,62 @@ def set_app_provider(app_id: str, provider: str, client: AuthenticatedClient) ->
|
|
|
1063
1063
|
return response.json().get("provider", provider)
|
|
1064
1064
|
|
|
1065
1065
|
|
|
1066
|
+
def set_instance_bounds(
|
|
1067
|
+
app_id: str,
|
|
1068
|
+
client: AuthenticatedClient,
|
|
1069
|
+
min_instances: int | None = None,
|
|
1070
|
+
max_instances: int | None = None,
|
|
1071
|
+
) -> str | None:
|
|
1072
|
+
"""Set the autoscaling instance bounds on an app.
|
|
1073
|
+
|
|
1074
|
+
Only the bounds explicitly passed are sent; an omitted bound is left at
|
|
1075
|
+
whatever the app already has (the platform default, unless previously
|
|
1076
|
+
overridden). The bounds are picked up by the next deployment, so this must
|
|
1077
|
+
be called before submitting one for it to take effect.
|
|
1078
|
+
|
|
1079
|
+
Args:
|
|
1080
|
+
app_id: The id of the application.
|
|
1081
|
+
client: The authenticated client.
|
|
1082
|
+
min_instances: The minimum number of instances to keep running.
|
|
1083
|
+
max_instances: The maximum number of instances to scale out to.
|
|
1084
|
+
|
|
1085
|
+
Returns:
|
|
1086
|
+
None on success, or a ``"set instance bounds failed: ..."`` string on
|
|
1087
|
+
error (validation, unsupported platform, or a scale already running).
|
|
1088
|
+
|
|
1089
|
+
Raises:
|
|
1090
|
+
NotAuthenticatedError: If the token is not valid.
|
|
1091
|
+
|
|
1092
|
+
"""
|
|
1093
|
+
import httpx
|
|
1094
|
+
|
|
1095
|
+
if not isinstance(client, AuthenticatedClient):
|
|
1096
|
+
raise NotAuthenticatedError("not authenticated")
|
|
1097
|
+
bounds: dict[str, int] = {}
|
|
1098
|
+
if min_instances is not None:
|
|
1099
|
+
bounds["min_instances"] = min_instances
|
|
1100
|
+
if max_instances is not None:
|
|
1101
|
+
bounds["max_instances"] = max_instances
|
|
1102
|
+
response = httpx.post(
|
|
1103
|
+
urljoin(
|
|
1104
|
+
constants.Hosting.HOSTING_SERVICE,
|
|
1105
|
+
f"/api/v1/apps/{app_id}/instance_bounds",
|
|
1106
|
+
),
|
|
1107
|
+
json=bounds,
|
|
1108
|
+
headers=authorization_header(client.token),
|
|
1109
|
+
timeout=constants.Hosting.TIMEOUT,
|
|
1110
|
+
)
|
|
1111
|
+
try:
|
|
1112
|
+
response.raise_for_status()
|
|
1113
|
+
except httpx.HTTPStatusError as ex:
|
|
1114
|
+
try:
|
|
1115
|
+
ex_details = ex.response.json().get("detail")
|
|
1116
|
+
except (ValueError, AttributeError):
|
|
1117
|
+
ex_details = ex.response.text
|
|
1118
|
+
return f"set instance bounds failed: {ex_details}"
|
|
1119
|
+
return None
|
|
1120
|
+
|
|
1121
|
+
|
|
1066
1122
|
def rollback_deployment(
|
|
1067
1123
|
app_id: str, deployment_id: str, client: AuthenticatedClient
|
|
1068
1124
|
) -> str | None:
|
|
@@ -203,6 +203,35 @@ def _restore_provider_on_failure(
|
|
|
203
203
|
raise
|
|
204
204
|
|
|
205
205
|
|
|
206
|
+
@contextlib.contextmanager
|
|
207
|
+
def _warn_if_bounds_outlive_deploy(app_name: str, applied: bool) -> Iterator[None]:
|
|
208
|
+
"""Report instance bounds that stuck when the deploy they were for failed.
|
|
209
|
+
|
|
210
|
+
Bounds are app state, not deployment state: there is no deployment-scoped
|
|
211
|
+
rollback to undo them, and the CLI has no way to read what they were before
|
|
212
|
+
to restore them. So when the submit they were applied for does not produce a
|
|
213
|
+
deployment -- rejected, raised, or interrupted -- say so, rather than let the
|
|
214
|
+
app's next deployment pick them up unannounced.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
app_name: The name of the app whose bounds were changed.
|
|
218
|
+
applied: Whether bounds were actually applied; a no-op when False.
|
|
219
|
+
|
|
220
|
+
Yields:
|
|
221
|
+
None.
|
|
222
|
+
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
yield
|
|
226
|
+
except BaseException:
|
|
227
|
+
if applied:
|
|
228
|
+
console.warn(
|
|
229
|
+
f"The new instance bounds were applied to '{app_name}' and will "
|
|
230
|
+
"be used by its next deployment, even though this deploy failed."
|
|
231
|
+
)
|
|
232
|
+
raise
|
|
233
|
+
|
|
234
|
+
|
|
206
235
|
def deploy(
|
|
207
236
|
export_fn: Callable[[str, str, str, bool, bool, bool, bool], None]
|
|
208
237
|
| Callable[[str, str, str, bool, bool, bool], None],
|
|
@@ -223,6 +252,10 @@ def deploy(
|
|
|
223
252
|
app_id: str | None = None,
|
|
224
253
|
provider: str | None = None,
|
|
225
254
|
deployment_description: str | None = None,
|
|
255
|
+
# Appended rather than grouped next to `vmtype` so existing positional
|
|
256
|
+
# callers keep binding to the same parameters.
|
|
257
|
+
min_instances: int | None = None,
|
|
258
|
+
max_instances: int | None = None,
|
|
226
259
|
**kwargs,
|
|
227
260
|
):
|
|
228
261
|
"""Deploy the app to the Reflex hosting service.
|
|
@@ -249,6 +282,10 @@ def deploy(
|
|
|
249
282
|
interactive mode.
|
|
250
283
|
deployment_description: An optional changelog note recorded on this
|
|
251
284
|
deployment and shown in ``reflex cloud apps history``.
|
|
285
|
+
min_instances: The minimum number of instances to keep running. Left at
|
|
286
|
+
the app's current value when omitted.
|
|
287
|
+
max_instances: The maximum number of instances to scale out to. Left at
|
|
288
|
+
the app's current value when omitted.
|
|
252
289
|
**kwargs: Additional keyword arguments.
|
|
253
290
|
|
|
254
291
|
Raises:
|
|
@@ -498,16 +535,15 @@ def deploy(
|
|
|
498
535
|
else None
|
|
499
536
|
)
|
|
500
537
|
if effective_provider == hosting.PROVIDER_GCP:
|
|
501
|
-
# GCP Cloud Run
|
|
502
|
-
#
|
|
503
|
-
#
|
|
504
|
-
if regions
|
|
538
|
+
# GCP Cloud Run takes its region from the org's connected GCP account,
|
|
539
|
+
# so a requested region is dropped. VM types are honored: the server
|
|
540
|
+
# maps them onto Cloud Run CPU/memory limits.
|
|
541
|
+
if regions:
|
|
505
542
|
console.info(
|
|
506
|
-
"Ignoring --region
|
|
507
|
-
"(region
|
|
543
|
+
"Ignoring --region for the Google Cloud target "
|
|
544
|
+
"(the region comes from the connected GCP account)."
|
|
508
545
|
)
|
|
509
546
|
regions = None
|
|
510
|
-
vmtype = None
|
|
511
547
|
|
|
512
548
|
with _restore_provider_on_failure(app, switched_from, authenticated_client):
|
|
513
549
|
urls = hosting.get_hostname(
|
|
@@ -628,23 +664,53 @@ def deploy(
|
|
|
628
664
|
shutil.rmtree(temporary_dir_path)
|
|
629
665
|
raise click.exceptions.Exit(1) from ex
|
|
630
666
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
667
|
+
# Instance bounds are app state that the deployment reads when it is
|
|
668
|
+
# created, so they have to land before the submit below. Keep the two
|
|
669
|
+
# calls adjacent: a bound applied earlier would outlive an export that
|
|
670
|
+
# then fails, silently changing what the app's *next* deployment scales
|
|
671
|
+
# to (and, for a non-zero minimum, what it costs to run).
|
|
672
|
+
bounds_applied = min_instances is not None or max_instances is not None
|
|
673
|
+
if bounds_applied:
|
|
674
|
+
try:
|
|
675
|
+
bounds_error = hosting.set_instance_bounds(
|
|
676
|
+
app_id=app["id"],
|
|
677
|
+
min_instances=min_instances,
|
|
678
|
+
max_instances=max_instances,
|
|
679
|
+
client=authenticated_client,
|
|
680
|
+
)
|
|
681
|
+
except BaseException:
|
|
682
|
+
# A dropped connection says nothing about whether the server
|
|
683
|
+
# applied the write, and the bounds are billable state, so hedge
|
|
684
|
+
# rather than report either outcome as fact.
|
|
685
|
+
console.warn(
|
|
686
|
+
f"Lost contact while setting the instance bounds of "
|
|
687
|
+
f"'{app['name']}'; they may or may not have been applied. "
|
|
688
|
+
"Check the app in the Reflex Cloud dashboard before relying "
|
|
689
|
+
"on its scaling."
|
|
690
|
+
)
|
|
691
|
+
raise
|
|
692
|
+
if bounds_error:
|
|
693
|
+
console.error(bounds_error)
|
|
694
|
+
raise click.exceptions.Exit(1)
|
|
695
|
+
|
|
696
|
+
with _warn_if_bounds_outlive_deploy(app["name"], bounds_applied):
|
|
697
|
+
result = hosting.create_deployment(
|
|
698
|
+
app_id=app.get("id"),
|
|
699
|
+
app_name=app_name,
|
|
700
|
+
project_id=project_id,
|
|
701
|
+
regions=regions,
|
|
702
|
+
zip_dir=Path(temporary_dir_path),
|
|
703
|
+
hostname=extract_domain(host_url) if hostname else None,
|
|
704
|
+
vmtype=vmtype,
|
|
705
|
+
secrets=processed_envs,
|
|
706
|
+
client=authenticated_client,
|
|
707
|
+
packages=packages,
|
|
708
|
+
strategy=strategy,
|
|
709
|
+
description=deployment_description,
|
|
710
|
+
)
|
|
711
|
+
if "failed" in result:
|
|
712
|
+
console.error(result)
|
|
713
|
+
raise click.exceptions.Exit(1)
|
|
648
714
|
hosting_ui_url = f"{constants.Hosting.HOSTING_SERVICE_UI}/project/{app['project_id']}/app/{app['id']}/"
|
|
649
715
|
console.print(
|
|
650
716
|
f"deployment progress can now be viewed on the website: {hosting_ui_url}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/compiler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/vmtypes_regions.py
RENAMED
|
File without changes
|