beamflow-cli 0.3.1__tar.gz → 0.3.2__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 (44) hide show
  1. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/PKG-INFO +2 -5
  2. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/commands/project.py +104 -4
  3. beamflow_cli-0.3.2/beamflow/core/auth_server.py +45 -0
  4. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/main.py +14 -0
  5. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/pyproject.toml +2 -5
  6. beamflow_cli-0.3.1/beamflow/core/auth_server.py +0 -36
  7. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/__init__.py +0 -0
  8. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/commands/__init__.py +0 -0
  9. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/commands/auth.py +0 -0
  10. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/commands/build.py +0 -0
  11. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/commands/deploy.py +0 -0
  12. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/commands/run.py +0 -0
  13. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/core/__init__.py +0 -0
  14. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/core/api_client.py +0 -0
  15. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/core/builder.py +0 -0
  16. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/core/config.py +0 -0
  17. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/core/docker_utils.py +0 -0
  18. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/README.md +0 -0
  19. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_.beamflow +0 -0
  20. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_.dockerignore +0 -0
  21. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_api_main.py +0 -0
  22. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_config/[env]/(backend-asyncio)/backend.yaml +0 -0
  23. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_config/[env]/(backend-dramatiq)/backend.yaml +0 -0
  24. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_config/[env]/(backend-managed)/backend.yaml +0 -0
  25. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_config/[env]/_backend.yaml +0 -0
  26. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_config/clients/demoClient.yaml +0 -0
  27. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_config/shared/backend.yaml +0 -0
  28. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/[env]/(backend-asyncio)/docker-compose.yaml +0 -0
  29. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/[env]/(backend-dramatiq)/docker-compose.yaml +0 -0
  30. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/[env]/.env +0 -0
  31. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/shared/.env +0 -0
  32. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/shared/api.Dockerfile +0 -0
  33. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/shared/worker.Dockerfile +0 -0
  34. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_pyproject.toml +0 -0
  35. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_src/_api/__init__.py +0 -0
  36. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_src/_api/_routes/webhooks.py +0 -0
  37. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_src/_shared/__init__.py +0 -0
  38. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_src/_shared/clients/client.py +0 -0
  39. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_src/_shared/models/models.py +0 -0
  40. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_src/_shared/tasks/sharedTasks.py +0 -0
  41. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_src/_worker/__init__.py +0 -0
  42. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_src/_worker/tasks/tasks.py +0 -0
  43. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/templates/_worker_main.py +0 -0
  44. {beamflow_cli-0.3.1 → beamflow_cli-0.3.2}/beamflow/ui/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: beamflow-cli
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: CLI for the Beamflow Managed Platform
5
5
  Author: juraj.bezdek@gmail.com
6
6
  Author-email: juraj.bezdek@gmail.com
@@ -9,14 +9,11 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Programming Language :: Python :: 3.13
12
- Requires-Dist: beamflow-runtime (>=0.3.0,<0.4.0)
13
- Requires-Dist: fastapi (>=0.110.0)
12
+ Requires-Dist: beamflow-runtime (>=0.3.0,<0.3.2)
14
13
  Requires-Dist: httpx (>=0.27.0)
15
14
  Requires-Dist: keyring (>=25.0.0)
16
15
  Requires-Dist: pydantic (>=2.0.0)
17
- Requires-Dist: python-dotenv (>=1.0.1)
18
16
  Requires-Dist: pyyaml (>=6.0.1)
19
17
  Requires-Dist: questionary (>=2.0.1)
20
18
  Requires-Dist: rich (>=13.7.0)
21
19
  Requires-Dist: typer[all] (>=0.12.0)
22
- Requires-Dist: uvicorn (>=0.29.0)
@@ -242,6 +242,89 @@ def init_env(env_name: str) -> EnvironmentMode:
242
242
  is_managed = (answers.get("backend") == "managed") or (answers.get("observability") == "managed")
243
243
  return EnvironmentMode(name=env_name, managed=is_managed, options=answers)
244
244
 
245
+ def init_vscode_launch(env_name: str, force: bool = False):
246
+ """Initialize VS Code launch settings for a specific environment."""
247
+ root = find_project_root()
248
+ if not root:
249
+ console.print("[red]Not in a Beamflow project. Run 'beamflow init' first.[/red]")
250
+ raise typer.Exit(code=1)
251
+
252
+ project_config = load_project_config()
253
+ if not project_config:
254
+ console.print("[red]Invalid project configuration.[/red]")
255
+ raise typer.Exit(code=1)
256
+
257
+ # Find the environment
258
+ env_to_init = next((e for e in project_config.environments if e.name == env_name), None)
259
+ if not env_to_init:
260
+ console.print(f"[red]Environment '{env_name}' not found.[/red]")
261
+ raise typer.Exit(code=1)
262
+
263
+ if env_to_init.managed:
264
+ console.print(f"[red]Cannot initialize VS Code launch for managed environment '{env_name}'.[/red]")
265
+ raise typer.Exit(code=1)
266
+
267
+ vscode_dir = root / ".vscode"
268
+ vscode_dir.mkdir(parents=True, exist_ok=True)
269
+ launch_json_path = vscode_dir / "launch.json"
270
+
271
+ import json
272
+
273
+ launch_data = {"version": "0.2.0", "configurations": []}
274
+ if launch_json_path.exists():
275
+ try:
276
+ with open(launch_json_path, "r") as f:
277
+ launch_data = json.load(f)
278
+ except json.JSONDecodeError:
279
+ # Maybe there are comments or it's malformed. We'll ask to overwrite.
280
+ if not force:
281
+ console.print(f"[yellow]Warning: {launch_json_path} contains invalid JSON or comments. Cannot parse it automatically. Use --force to overwrite it entirely.[/yellow]")
282
+ raise typer.Exit(code=1)
283
+ launch_data = {"version": "0.2.0", "configurations": []}
284
+
285
+ api_config_name = f"Launch {env_name} API"
286
+ worker_config_name = f"Launch {env_name} Worker"
287
+
288
+ existing_configs = {cfg.get("name") for cfg in launch_data.get("configurations", [])}
289
+
290
+ added = False
291
+
292
+ if api_config_name not in existing_configs or force:
293
+ # Remove old if force
294
+ launch_data["configurations"] = [c for c in launch_data.setdefault("configurations", []) if c.get("name") != api_config_name]
295
+
296
+ launch_data["configurations"].append({
297
+ "name": api_config_name,
298
+ "type": "debugpy",
299
+ "request": "launch",
300
+ "program": "${workspaceFolder}/api_main.py",
301
+ "console": "integratedTerminal",
302
+ "env": {"ENVIRONMENT": env_name}
303
+ })
304
+ added = True
305
+
306
+ if worker_config_name not in existing_configs or force:
307
+ # Remove old if force
308
+ launch_data["configurations"] = [c for c in launch_data.setdefault("configurations", []) if c.get("name") != worker_config_name]
309
+
310
+ launch_data["configurations"].append({
311
+ "name": worker_config_name,
312
+ "type": "debugpy",
313
+ "request": "launch",
314
+ "program": "${workspaceFolder}/worker_main.py",
315
+ "console": "integratedTerminal",
316
+ "env": {"ENVIRONMENT": env_name}
317
+ })
318
+ added = True
319
+
320
+ if added:
321
+ with open(launch_json_path, "w") as f:
322
+ json.dump(launch_data, f, indent=4)
323
+ console.print(f"[green]Added VS Code launch configurations for '{env_name}' to {launch_json_path}[/green]")
324
+ else:
325
+ console.print(f"VS Code launch configurations already exist for '{env_name}'.")
326
+
327
+
245
328
 
246
329
  def add_environment(project_config: ProjectConfig, env_name: Optional[str] = None):
247
330
  if not env_name:
@@ -270,11 +353,12 @@ def add_environment(project_config: ProjectConfig, env_name: Optional[str] = Non
270
353
 
271
354
  if not env_name or any(e.name == env_name for e in project_config.environments):
272
355
  console.print("[red]Environment already exists or invalid name![/red]")
273
- return
356
+ return None
274
357
 
275
358
  mode = init_env(env_name)
276
359
  project_config.environments.append(mode)
277
360
  save_project_config(project_config)
361
+ return mode
278
362
 
279
363
 
280
364
  @app.command("add")
@@ -293,11 +377,16 @@ def env_add(
293
377
  console.print("[red]Invalid project configuration.[/red]")
294
378
  raise typer.Exit(code=1)
295
379
 
296
- add_environment(project_config, env_name=env_name)
380
+ added_mode = add_environment(project_config, env_name=env_name)
381
+ if not added_mode:
382
+ return
297
383
 
298
384
  # Reload config to get the newly added environment
299
385
  project_config = load_project_config()
300
386
 
387
+ # We now know the actual environment name picked
388
+ env_name = added_mode.name
389
+
301
390
  # Check if we should ask for linkage
302
391
  is_managed = any(e.managed for e in project_config.environments)
303
392
  if not project_config.project_id and is_managed:
@@ -313,6 +402,10 @@ def env_add(
313
402
  process_node(child, root, None, is_fix=True, is_check=False, force=force, project_name=project_name, root_path=root, errors=errors, target_env=env_name)
314
403
 
315
404
  console.print(f"[green]Environment configuration updated![/green]")
405
+
406
+ if not added_mode.managed:
407
+ if Prompt.ask("Would you like to initialize VS Code launch configurations for this environment?", choices=["y", "n"], default="y") == "y":
408
+ init_vscode_launch(env_name=env_name, force=force)
316
409
 
317
410
 
318
411
  @app.command("delete")
@@ -392,11 +485,18 @@ def init(
392
485
  # Ask to configure at least the first environment
393
486
  setup_first = Prompt.ask("Setup first eniroment? y/n?", choices=["y", "n"], default="y")
394
487
  if setup_first == "y":
395
- add_environment(project_config)
488
+ added_mode = add_environment(project_config)
489
+ if added_mode and not added_mode.managed:
490
+ if Prompt.ask(f"Would you like to initialize VS Code launch configurations for {added_mode.name}?", choices=["y", "n"], default="y") == "y":
491
+ init_vscode_launch(env_name=added_mode.name, force=force)
396
492
  else:
397
493
  if not fix:
398
494
  while Prompt.ask("Add another enviroment? y/n?", choices=["y", "n"], default="n") == "y":
399
- add_environment(project_config)
495
+ added_mode = add_environment(project_config)
496
+ if added_mode and not added_mode.managed:
497
+ if Prompt.ask(f"Would you like to initialize VS Code launch configurations for {added_mode.name}?", choices=["y", "n"], default="y") == "y":
498
+ init_vscode_launch(env_name=added_mode.name, force=force)
499
+
400
500
 
401
501
  templates_dir = Path(__file__).parent.parent / "templates"
402
502
  errors = []
@@ -0,0 +1,45 @@
1
+ from typing import Dict, Optional
2
+ import threading
3
+ import queue
4
+ from http.server import HTTPServer, BaseHTTPRequestHandler
5
+ from urllib.parse import urlparse, parse_qs
6
+
7
+ result_queue = queue.Queue()
8
+
9
+ class CallbackHandler(BaseHTTPRequestHandler):
10
+ def do_GET(self):
11
+ query_components = parse_qs(urlparse(self.path).query)
12
+ params = {k: v[0] for k, v in query_components.items()}
13
+ result_queue.put(params)
14
+
15
+ self.send_response(200)
16
+ self.send_header('Content-type', 'text/html')
17
+ self.end_headers()
18
+
19
+ content = """
20
+ <html>
21
+ <body style="font-family: sans-serif; text-align: center; padding-top: 50px;">
22
+ <h1>Authentication Successful</h1>
23
+ <p>You can now close this window and return to the CLI.</p>
24
+ </body>
25
+ </html>
26
+ """
27
+ self.wfile.write(content.encode('utf-8'))
28
+
29
+ def log_message(self, format, *args):
30
+ # Suppress logging
31
+ pass
32
+
33
+ def start_callback_server(port: int = 8888) -> Dict[str, str]:
34
+ server = HTTPServer(('127.0.0.1', port), CallbackHandler)
35
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
36
+ thread.start()
37
+
38
+ # Wait for result
39
+ try:
40
+ result = result_queue.get(timeout=300) # 5 minutes timeout
41
+ server.shutdown()
42
+ return result
43
+ except queue.Empty:
44
+ server.shutdown()
45
+ return {}
@@ -97,6 +97,20 @@ def env_del(
97
97
  """
98
98
  project_cmds.env_delete(env_name=env_name)
99
99
 
100
+ @env_app.command("init-vscode")
101
+ def env_init_vscode(
102
+ env_name: str = typer.Argument(..., help="Environment name to initialize VS Code for"),
103
+ force: bool = typer.Option(False, "--force", help="Force overwrite existing launch.json if it's malformed or already has the configuration")
104
+ ):
105
+ """
106
+ [bold blue]Initialize VS Code launch configurations[/bold blue] for an environment.
107
+
108
+ This command will add 'Launch <env> API' and 'Launch <env> Worker' to your .vscode/launch.json,
109
+ allowing you to easily debug your environment's API and Worker using VS Code.
110
+ """
111
+ project_cmds.init_vscode_launch(env_name=env_name, force=force)
112
+
113
+
100
114
  @app.command()
101
115
  def run(
102
116
  ctx: typer.Context,
@@ -1,20 +1,17 @@
1
1
  [project]
2
2
  name = "beamflow-cli"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "CLI for the Beamflow Managed Platform"
5
5
  authors = [{name = "juraj.bezdek@gmail.com", email = "juraj.bezdek@gmail.com"}]
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
8
- "beamflow-runtime>=0.3.0,<0.4.0",
8
+ "beamflow-runtime>=0.3.0,<0.3.2",
9
9
  "typer[all]>=0.12.0",
10
10
  "httpx>=0.27.0",
11
11
  "pyyaml>=6.0.1",
12
12
  "keyring>=25.0.0",
13
13
  "rich>=13.7.0",
14
14
  "pydantic>=2.0.0",
15
- "python-dotenv>=1.0.1",
16
- "fastapi>=0.110.0",
17
- "uvicorn>=0.29.0",
18
15
  "questionary>=2.0.1",
19
16
  ]
20
17
 
@@ -1,36 +0,0 @@
1
- from typing import Dict
2
- from fastapi import FastAPI, Request
3
- from fastapi.responses import HTMLResponse
4
- import uvicorn
5
- import threading
6
- import queue
7
- from typing import Optional
8
-
9
- app = FastAPI()
10
- result_queue = queue.Queue()
11
-
12
- @app.get("/callback")
13
- async def callback(request: Request):
14
- params = dict(request.query_params)
15
- result_queue.put(params)
16
- return HTMLResponse(content="""
17
- <html>
18
- <body style="font-family: sans-serif; text-align: center; padding-top: 50px;">
19
- <h1>Authentication Successful</h1>
20
- <p>You can now close this window and return to the CLI.</p>
21
- </body>
22
- </html>
23
- """)
24
-
25
- def run_server(port: int):
26
- uvicorn.run(app, host="127.0.0.1", port=port, log_level="error")
27
-
28
- def start_callback_server(port: int = 8888) -> Dict[str, str]:
29
- thread = threading.Thread(target=run_server, args=(port,), daemon=True)
30
- thread.start()
31
- # Wait for result
32
- try:
33
- result = result_queue.get(timeout=300) # 5 minutes timeout
34
- return result
35
- except queue.Empty:
36
- return {}