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.
Files changed (31) hide show
  1. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/CHANGELOG.md +11 -0
  2. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/PKG-INFO +1 -1
  3. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/hosting.py +56 -0
  4. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/cli.py +90 -24
  5. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/.gitignore +0 -0
  6. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/README.md +0 -0
  7. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/news/.gitkeep +0 -0
  8. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/pyproject.toml +0 -0
  9. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/__init__.py +0 -0
  10. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/cli.py +0 -0
  11. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/__init__.py +0 -0
  12. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/base.py +0 -0
  13. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/compiler.py +0 -0
  14. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/constants/hosting.py +0 -0
  15. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/core/__init__.py +0 -0
  16. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/core/config.py +0 -0
  17. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/deployments.py +0 -0
  18. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/__init__.py +0 -0
  19. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/console.py +0 -0
  20. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/dependency.py +0 -0
  21. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/utils/exceptions.py +0 -0
  22. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/__init__.py +0 -0
  23. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/apps.py +0 -0
  24. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/deployments.py +0 -0
  25. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/gcp.py +0 -0
  26. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/project.py +0 -0
  27. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/providers.py +0 -0
  28. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/scan.py +0 -0
  29. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/secrets.py +0 -0
  30. {reflex_hosting_cli-0.1.68 → reflex_hosting_cli-0.1.69}/src/reflex_cli/v2/utils.py +0 -0
  31. {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.68
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 ignores Reflex Cloud regions/VM types — the region and
502
- # sizing come from the org's connected GCP account. Drop them so
503
- # validation and the deploy don't send incompatible values.
504
- if regions or vmtype:
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/--vmtype for the Google Cloud target "
507
- "(region and sizing come from the connected GCP account)."
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
- result = hosting.create_deployment(
632
- app_id=app.get("id"),
633
- app_name=app_name,
634
- project_id=project_id,
635
- regions=regions,
636
- zip_dir=Path(temporary_dir_path),
637
- hostname=extract_domain(host_url) if hostname else None,
638
- vmtype=vmtype,
639
- secrets=processed_envs,
640
- client=authenticated_client,
641
- packages=packages,
642
- strategy=strategy,
643
- description=deployment_description,
644
- )
645
- if "failed" in result:
646
- console.error(result)
647
- raise click.exceptions.Exit(1)
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}"