reflex-hosting-cli 0.1.64__tar.gz → 0.1.66__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 (27) hide show
  1. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/PKG-INFO +1 -1
  2. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/utils/hosting.py +22 -4
  3. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/cli.py +44 -58
  4. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/gcp.py +32 -0
  5. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/.gitignore +0 -0
  6. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/README.md +0 -0
  7. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/pyproject.toml +0 -0
  8. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/__init__.py +0 -0
  9. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/cli.py +0 -0
  10. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/constants/__init__.py +0 -0
  11. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/constants/base.py +0 -0
  12. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/constants/compiler.py +0 -0
  13. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/constants/hosting.py +0 -0
  14. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/core/__init__.py +0 -0
  15. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/core/config.py +0 -0
  16. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/deployments.py +0 -0
  17. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/utils/__init__.py +0 -0
  18. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/utils/console.py +0 -0
  19. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/utils/dependency.py +0 -0
  20. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/utils/exceptions.py +0 -0
  21. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/__init__.py +0 -0
  22. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/apps.py +0 -0
  23. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/deployments.py +0 -0
  24. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/project.py +0 -0
  25. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/secrets.py +0 -0
  26. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/utils.py +0 -0
  27. {reflex_hosting_cli-0.1.64 → reflex_hosting_cli-0.1.66}/src/reflex_cli/v2/vmtypes_regions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reflex-hosting-cli
3
- Version: 0.1.64
3
+ Version: 0.1.66
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>
@@ -1078,6 +1078,20 @@ def select_project(project: str, token: str | None = None) -> str:
1078
1078
  return f"{project} is now selected."
1079
1079
 
1080
1080
 
1081
+ def normalize_project_id(value: Any) -> str | None:
1082
+ """Normalize a project ID value, treating empty/whitespace strings and non-strings as None.
1083
+
1084
+ Args:
1085
+ value: The raw project ID value from config, CLI args, or hosting.json.
1086
+
1087
+ Returns:
1088
+ The stripped project ID, or None if the value is missing or blank.
1089
+ """
1090
+ if isinstance(value, str) and value.strip():
1091
+ return value.strip()
1092
+ return None
1093
+
1094
+
1081
1095
  def get_selected_project() -> str | None:
1082
1096
  """Retrieve the currently selected project ID.
1083
1097
 
@@ -1088,10 +1102,10 @@ def get_selected_project() -> str | None:
1088
1102
  try:
1089
1103
  with constants.Hosting.HOSTING_JSON.open() as config_file:
1090
1104
  hosting_config = json.load(config_file)
1091
- return hosting_config.get("project")
1105
+ return normalize_project_id(hosting_config.get("project"))
1092
1106
  except Exception as ex:
1093
1107
  console.debug(
1094
- f"Unable to fetch token from {constants.Hosting.HOSTING_JSON} due to: {ex}"
1108
+ f"Unable to read selected project from {constants.Hosting.HOSTING_JSON} due to: {ex}"
1095
1109
  )
1096
1110
  return None
1097
1111
 
@@ -1970,14 +1984,18 @@ def authenticate_on_browser() -> tuple[str, dict[str, Any]]:
1970
1984
  constants.Hosting.HOSTING_SERVICE_UI, f"/cli/login?request_id={request_id}"
1971
1985
  )
1972
1986
 
1973
- console.print(f"Opening {auth_url} ...")
1974
-
1975
1987
  if not is_valid_url(constants.Hosting.HOSTING_SERVICE_UI):
1976
1988
  console.error(
1977
1989
  f"Invalid hosting URL: {constants.Hosting.HOSTING_SERVICE_UI}. Ensure the URL is in the correct format and includes a valid scheme"
1978
1990
  )
1979
1991
  raise click.exceptions.Exit(1)
1980
1992
 
1993
+ console.print(
1994
+ f"Opening {auth_url} ... By connecting your account, you agree to "
1995
+ "Reflex Cloud [Terms of Service] and [Privacy Policy].",
1996
+ markup=False,
1997
+ )
1998
+
1981
1999
  if not webbrowser.open(auth_url):
1982
2000
  console.warn(
1983
2001
  f"Unable to automatically open the browser. Please go to {auth_url} to authenticate."
@@ -28,7 +28,8 @@ def login(
28
28
  loglevel: The log level to use.
29
29
 
30
30
  Returns:
31
- Information about the logged in user.
31
+ Information about the newly logged in user or empty dict if already
32
+ logged in.
32
33
 
33
34
  Raises:
34
35
  SystemExit: If the command fails.
@@ -42,7 +43,7 @@ def login(
42
43
  access_token, validated_info = hosting.authenticated_token()
43
44
  if access_token:
44
45
  console.print("You already logged in.")
45
- return validated_info
46
+ return {}
46
47
 
47
48
  # If not already logged in, open a browser window/tab to the login page.
48
49
  access_token, validated_info = hosting.authenticate_on_browser()
@@ -179,19 +180,24 @@ def deploy(
179
180
  if not description:
180
181
  description = config.get("description", None)
181
182
 
182
- # resolve the project id from the project name.
183
+ project_id = hosting.normalize_project_id(project_id)
184
+
183
185
  if project_name and not project_id:
184
186
  result = hosting.search_project(
185
187
  project_name, client=authenticated_client, interactive=interactive
186
188
  )
187
- project_id = result.get("id") if result else None
189
+ project_id = hosting.normalize_project_id(result.get("id")) if result else None
190
+
191
+ selected_project_id = hosting.get_selected_project()
188
192
 
193
+ validated_project: dict[str, Any] | None = None
189
194
  try:
190
- # check if provided project exists.
195
+ if not project_id:
196
+ project_id = selected_project_id
191
197
  if project_id:
192
- hosting.get_project(project_id, client=authenticated_client)
193
- else:
194
- project_id = hosting.get_selected_project()
198
+ validated_project = hosting.get_project(
199
+ project_id, client=authenticated_client
200
+ )
195
201
  except httpx.HTTPStatusError as ex:
196
202
  try:
197
203
  console.error(ex.response.json().get("detail"))
@@ -209,9 +215,7 @@ def deploy(
209
215
  try:
210
216
  if app_name and not app_id:
211
217
  search_project_id = project_id
212
- if not interactive and not project and not search_project_id:
213
- search_project_id = hosting.get_selected_project()
214
- elif interactive and not project:
218
+ if interactive and not project:
215
219
  search_project_id = None
216
220
 
217
221
  app = hosting.search_app(
@@ -230,16 +234,13 @@ def deploy(
230
234
  raise click.exceptions.Exit(1) from ex
231
235
 
232
236
  if app and interactive and not project and not app_id:
233
- default_project_id = hosting.get_selected_project()
237
+ default_project_id = selected_project_id
234
238
  app_project_id = app.get("project_id")
235
239
 
236
240
  if app_project_id and (
237
241
  not default_project_id or app_project_id != default_project_id
238
242
  ):
239
- app_project = hosting.get_project(
240
- app_project_id, client=authenticated_client
241
- )
242
- app_project_name = app_project.get("name", "Unknown")
243
+ app_project_name = (app.get("project") or {}).get("name") or app_project_id
243
244
  if (
244
245
  console.ask(
245
246
  f"Deploy to app '{app['name']}' in project '{app_project_name}'?",
@@ -262,55 +263,40 @@ def deploy(
262
263
  )
263
264
  == "y"
264
265
  ):
265
- # Check if we need confirmation for deploying to non-default project
266
266
  if not project:
267
- default_project_id = hosting.get_selected_project()
268
- if not default_project_id:
269
- try:
270
- if project_id:
271
- target_project = hosting.get_project(
272
- project_id, client=authenticated_client
273
- )
274
- project_name = target_project.get("name", "Unknown")
275
- else:
276
- token = hosting.get_existing_access_token()
277
- default_project_id = hosting.get_default_project(
278
- authenticated_client
267
+ needs_confirmation = not selected_project_id or (
268
+ project_id and project_id != selected_project_id
269
+ )
270
+ if needs_confirmation:
271
+ if project_id:
272
+ project_display_name = (
273
+ (
274
+ validated_project.get("name")
275
+ if validated_project
276
+ else None
279
277
  )
280
- if default_project_id:
281
- default_project = hosting.get_project(
282
- default_project_id, client=authenticated_client
283
- )
284
- project_name = default_project.get(
285
- "name", "Default Project"
286
- )
287
- else:
288
- project_name = "Default Project"
289
- except Exception:
290
- project_name = "Unknown"
291
-
292
- if (
293
- console.ask(
294
- f"Create and deploy app '{app_name}' in project '{project_name}'?",
295
- choices=["y", "n"],
296
- default="y",
278
+ or project_name
279
+ or project_id
297
280
  )
298
- != "y"
299
- ):
300
- console.info("Deployment cancelled.")
301
- raise click.exceptions.Exit(0)
302
- elif project_id and project_id != default_project_id:
303
- try:
304
- target_project = hosting.get_project(
305
- project_id, client=authenticated_client
281
+ else:
282
+ project_display_name = "your default project"
283
+ fallback_project_id = hosting.get_default_project(
284
+ authenticated_client
306
285
  )
307
- project_name = target_project.get("name", "Unknown")
308
- except Exception:
309
- project_name = "Unknown"
286
+ if fallback_project_id:
287
+ try:
288
+ fallback_project = hosting.get_project(
289
+ fallback_project_id, client=authenticated_client
290
+ )
291
+ project_display_name = (
292
+ fallback_project.get("name") or project_display_name
293
+ )
294
+ except Exception:
295
+ pass
310
296
 
311
297
  if (
312
298
  console.ask(
313
- f"Create and deploy app '{app_name}' in project '{project_name}'?",
299
+ f"Create and deploy app '{app_name}' in project '{project_display_name}'?",
314
300
  choices=["y", "n"],
315
301
  default="y",
316
302
  )
@@ -6,6 +6,7 @@ a Cloud Build job (via a ``cloudbuild.yaml`` written to a tempfile and
6
6
  referenced with ``gcloud builds submit --config=...``) — the user's project
7
7
  tree is never modified. The script reads its parameters from environment
8
8
  variables (GCP_PROJECT, GCP_REGION, SERVICE_NAME, AR_REPO, VERSION,
9
+ CLOUD_RUN_CPU, CLOUD_RUN_MEMORY, CLOUD_RUN_MIN_INSTANCES,
9
10
  REFLEX_CLOUDBUILD_YAML).
10
11
  """
11
12
 
@@ -37,6 +38,9 @@ ENV_GCP_REGION = "GCP_REGION"
37
38
  ENV_SERVICE_NAME = "SERVICE_NAME"
38
39
  ENV_AR_REPO = "AR_REPO"
39
40
  ENV_VERSION = "VERSION"
41
+ ENV_CPU = "CLOUD_RUN_CPU"
42
+ ENV_MEMORY = "CLOUD_RUN_MEMORY"
43
+ ENV_MIN_INSTANCES = "CLOUD_RUN_MIN_INSTANCES"
40
44
  # Path to the Cloud Build config file written by the CLI. The rewritten
41
45
  # deploy script references it as ``--config="${REFLEX_CLOUDBUILD_YAML}"``.
42
46
  ENV_REFLEX_CLOUDBUILD_YAML = "REFLEX_CLOUDBUILD_YAML"
@@ -136,6 +140,28 @@ DEPLOY_ENV_ALLOWLIST = frozenset({
136
140
  default=None,
137
141
  help="The image version tag (sets VERSION). Defaults to a UTC timestamp.",
138
142
  )
143
+ @click.option(
144
+ "--cpu",
145
+ "cpu",
146
+ default="1",
147
+ show_default=True,
148
+ help="Cloud Run CPU allocation, e.g. '1', '2', '4' (sets CLOUD_RUN_CPU).",
149
+ )
150
+ @click.option(
151
+ "--memory",
152
+ "memory",
153
+ default="1Gi",
154
+ show_default=True,
155
+ help="Cloud Run memory allocation, e.g. '512Mi', '1Gi', '2Gi' (sets CLOUD_RUN_MEMORY).",
156
+ )
157
+ @click.option(
158
+ "--min-instances",
159
+ "min_instances",
160
+ default=1,
161
+ show_default=True,
162
+ type=click.IntRange(min=0),
163
+ help="Minimum number of Cloud Run instances to keep warm (sets CLOUD_RUN_MIN_INSTANCES). Set to 0 to scale to zero.",
164
+ )
139
165
  @click.option(
140
166
  "--source",
141
167
  "source_dir",
@@ -170,6 +196,9 @@ def deploy_command(
170
196
  service_name: str,
171
197
  ar_repo: str,
172
198
  version_tag: str | None,
199
+ cpu: str,
200
+ memory: str,
201
+ min_instances: int,
173
202
  source_dir: str,
174
203
  token: str | None,
175
204
  interactive: bool,
@@ -252,6 +281,9 @@ def deploy_command(
252
281
  ENV_SERVICE_NAME: service_name,
253
282
  ENV_AR_REPO: ar_repo,
254
283
  ENV_VERSION: version_value,
284
+ ENV_CPU: cpu,
285
+ ENV_MEMORY: memory,
286
+ ENV_MIN_INSTANCES: str(min_instances),
255
287
  }
256
288
 
257
289
  console.info("Received deploy manifest from Reflex.")