beamflow-cli 0.3.0__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 (47) hide show
  1. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/PKG-INFO +2 -5
  2. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/commands/project.py +108 -4
  3. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/commands/run.py +1 -1
  4. beamflow_cli-0.3.2/beamflow/core/auth_server.py +45 -0
  5. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/main.py +28 -2
  6. beamflow_cli-0.3.2/beamflow/templates/README.md +135 -0
  7. beamflow_cli-0.3.2/beamflow/templates/_deployment/[env]/(backend-asyncio)/docker-compose.yaml +24 -0
  8. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/[env]/(backend-dramatiq)/docker-compose.yaml +4 -4
  9. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/pyproject.toml +2 -5
  10. beamflow_cli-0.3.0/beamflow/core/auth_server.py +0 -36
  11. beamflow_cli-0.3.0/beamflow/templates/_README.md +0 -28
  12. beamflow_cli-0.3.0/beamflow/templates/tests/_test_config_loading.py +0 -10
  13. beamflow_cli-0.3.0/beamflow/templates/tests/_test_integration.py +0 -20
  14. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/__init__.py +0 -0
  15. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/commands/__init__.py +0 -0
  16. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/commands/auth.py +0 -0
  17. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/commands/build.py +0 -0
  18. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/commands/deploy.py +0 -0
  19. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/core/__init__.py +0 -0
  20. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/core/api_client.py +0 -0
  21. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/core/builder.py +0 -0
  22. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/core/config.py +0 -0
  23. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/core/docker_utils.py +0 -0
  24. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_.beamflow +0 -0
  25. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_.dockerignore +0 -0
  26. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_api_main.py +0 -0
  27. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_config/[env]/(backend-asyncio)/backend.yaml +0 -0
  28. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_config/[env]/(backend-dramatiq)/backend.yaml +0 -0
  29. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_config/[env]/(backend-managed)/backend.yaml +0 -0
  30. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_config/[env]/_backend.yaml +0 -0
  31. {beamflow_cli-0.3.0/beamflow/templates/_deployment/shared → beamflow_cli-0.3.2/beamflow/templates/_config}/clients/demoClient.yaml +0 -0
  32. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_config/shared/backend.yaml +0 -0
  33. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/[env]/.env +0 -0
  34. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/shared/.env +0 -0
  35. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/shared/api.Dockerfile +0 -0
  36. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_deployment/shared/worker.Dockerfile +0 -0
  37. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_pyproject.toml +0 -0
  38. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_src/_api/__init__.py +0 -0
  39. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_src/_api/_routes/webhooks.py +0 -0
  40. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_src/_shared/__init__.py +0 -0
  41. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_src/_shared/clients/client.py +0 -0
  42. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_src/_shared/models/models.py +0 -0
  43. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_src/_shared/tasks/sharedTasks.py +0 -0
  44. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_src/_worker/__init__.py +0 -0
  45. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_src/_worker/tasks/tasks.py +0 -0
  46. {beamflow_cli-0.3.0 → beamflow_cli-0.3.2}/beamflow/templates/_worker_main.py +0 -0
  47. {beamflow_cli-0.3.0 → 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.0
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)
@@ -142,6 +142,10 @@ def process_node(
142
142
  try:
143
143
  content = src.read_text()
144
144
  content = content.replace("{project_name}", project_name)
145
+ project_name_slug = project_name.lower().replace("_", "-").strip("-")
146
+ if not project_name_slug:
147
+ project_name_slug = "beamflow-project"
148
+ content = content.replace("{project_name_slug}", project_name_slug)
145
149
  if env_mode:
146
150
  content = content.replace("{env}", env_mode.name)
147
151
  content = content.replace("[env]", env_mode.name)
@@ -238,6 +242,89 @@ def init_env(env_name: str) -> EnvironmentMode:
238
242
  is_managed = (answers.get("backend") == "managed") or (answers.get("observability") == "managed")
239
243
  return EnvironmentMode(name=env_name, managed=is_managed, options=answers)
240
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
+
241
328
 
242
329
  def add_environment(project_config: ProjectConfig, env_name: Optional[str] = None):
243
330
  if not env_name:
@@ -266,11 +353,12 @@ def add_environment(project_config: ProjectConfig, env_name: Optional[str] = Non
266
353
 
267
354
  if not env_name or any(e.name == env_name for e in project_config.environments):
268
355
  console.print("[red]Environment already exists or invalid name![/red]")
269
- return
356
+ return None
270
357
 
271
358
  mode = init_env(env_name)
272
359
  project_config.environments.append(mode)
273
360
  save_project_config(project_config)
361
+ return mode
274
362
 
275
363
 
276
364
  @app.command("add")
@@ -289,11 +377,16 @@ def env_add(
289
377
  console.print("[red]Invalid project configuration.[/red]")
290
378
  raise typer.Exit(code=1)
291
379
 
292
- 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
293
383
 
294
384
  # Reload config to get the newly added environment
295
385
  project_config = load_project_config()
296
386
 
387
+ # We now know the actual environment name picked
388
+ env_name = added_mode.name
389
+
297
390
  # Check if we should ask for linkage
298
391
  is_managed = any(e.managed for e in project_config.environments)
299
392
  if not project_config.project_id and is_managed:
@@ -309,6 +402,10 @@ def env_add(
309
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)
310
403
 
311
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)
312
409
 
313
410
 
314
411
  @app.command("delete")
@@ -388,11 +485,18 @@ def init(
388
485
  # Ask to configure at least the first environment
389
486
  setup_first = Prompt.ask("Setup first eniroment? y/n?", choices=["y", "n"], default="y")
390
487
  if setup_first == "y":
391
- 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)
392
492
  else:
393
493
  if not fix:
394
494
  while Prompt.ask("Add another enviroment? y/n?", choices=["y", "n"], default="n") == "y":
395
- 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
+
396
500
 
397
501
  templates_dir = Path(__file__).parent.parent / "templates"
398
502
  errors = []
@@ -12,7 +12,7 @@ console = Console()
12
12
  @app.command()
13
13
  def run(
14
14
  env: str = typer.Argument(..., help="Environment to run"),
15
- build: bool = typer.Option(False, "--build", help="Build images before starting"),
15
+ build: bool = typer.Option(True, "--build/--no-build", help="Build images before starting"),
16
16
  detach: bool = typer.Option(False, "--detach", "-d", help="Run in background"),
17
17
  logs: bool = typer.Option(False, "--logs", help="Follow logs (useful with --detach)")
18
18
  ):
@@ -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 {}
@@ -7,12 +7,19 @@ from .commands import build as build_cmds
7
7
  from .commands import deploy as deploy_cmds
8
8
  from .commands import run as run_cmds
9
9
  from rich.console import Console
10
+ import importlib.metadata
11
+
12
+ try:
13
+ __version__ = importlib.metadata.version("beamflow-cli")
14
+ except importlib.metadata.PackageNotFoundError:
15
+ __version__ = "unknown"
16
+
10
17
  console = Console()
11
18
 
12
19
  app = typer.Typer(
13
20
  name="beamflow",
14
- help="""
15
- # 🚀 Beamflow CLI
21
+ help=f"""
22
+ # 🚀 Beamflow CLI (v{__version__})
16
23
  Manage your Beamflow projects and deployments with ease.
17
24
 
18
25
  ## 🛠 Usage
@@ -90,6 +97,20 @@ def env_del(
90
97
  """
91
98
  project_cmds.env_delete(env_name=env_name)
92
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
+
93
114
  @app.command()
94
115
  def run(
95
116
  ctx: typer.Context,
@@ -223,5 +244,10 @@ def logout():
223
244
  """Log out from the platform."""
224
245
  auth_cmds.logout()
225
246
 
247
+ @app.command()
248
+ def version():
249
+ """Show the Beamflow CLI version."""
250
+ console.print(f"Beamflow CLI version: [bold green]{__version__}[/bold green]")
251
+
226
252
  if __name__ == "__main__":
227
253
  app()
@@ -0,0 +1,135 @@
1
+ # {project_name}
2
+
3
+ Beamflow application generated for {project_name}.
4
+
5
+ ## Structure
6
+
7
+ - `api.py`: Entry point for the API service.
8
+ - `worker.py`: Entry point for the worker service.
9
+ - `src/shared/`: Shared helpers, constants, and client utilities.
10
+ - `src/api/`: Webhook handlers and API startup.
11
+ - `src/worker/`: Worker startup and task definitions (such as feed consumers).
12
+ - `config/`: Environment-based configuration (e.g. `local/`, `dev/`, `prod/`, `shared/`).
13
+ - `deployment/`: Docker Compose setup and Dockerfiles.
14
+
15
+ ---
16
+
17
+ ## Dependency Management
18
+
19
+ This project defines dependencies using optional extras, allowing you to install only what is needed for a specific service.
20
+
21
+ - **Base dependencies**: Required by both API and Worker.
22
+ - **`api` extra**: Dependencies required only by the API service.
23
+ - **`worker` extra**: Dependencies required only by the Worker service.
24
+ - **`dev` extra** (optional): Development tooling.
25
+
26
+ ### Standard Installation (pip)
27
+
28
+ Install for local development (recommended, installs everything):
29
+ ```bash
30
+ pip install -e ".[api,worker,dev]"
31
+ ```
32
+
33
+ Install API-only:
34
+ ```bash
35
+ pip install -e ".[api]"
36
+ ```
37
+
38
+ Install Worker-only:
39
+ ```bash
40
+ pip install -e ".[worker]"
41
+ ```
42
+
43
+ ### Alternative Installation (Poetry)
44
+
45
+ If you prefer managing your environment with Poetry:
46
+
47
+ Install for local development (everything):
48
+ ```bash
49
+ poetry install --extras "api worker dev"
50
+ ```
51
+
52
+ Install API-only:
53
+ ```bash
54
+ poetry install --extras "api"
55
+ ```
56
+
57
+ Install Worker-only:
58
+ ```bash
59
+ poetry install --extras "worker"
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Running Locally
65
+
66
+ ### 1. Start supporting services (e.g., Redis)
67
+ ```bash
68
+ docker compose -f deployment/local/docker-compose.yaml up -d
69
+ ```
70
+
71
+ ### 2. Set environment
72
+ ```bash
73
+ export BEAMSTACK_ENV=local
74
+ ```
75
+
76
+ ### 3. Run API
77
+ ```bash
78
+ python api.py
79
+ ```
80
+ API will be available at [http://localhost:8000](http://localhost:8000).
81
+
82
+ ### 4. Run Worker
83
+ ```bash
84
+ python worker.py
85
+ ```
86
+
87
+ *(Alternatively, you can use the Beamflow CLI to run your environments: `beamflow run local`)*
88
+
89
+ ---
90
+
91
+ ## Beamflow Core Concepts Usage
92
+
93
+ This project is built on `beamflow_lib` – a core library providing robust context management, queue abstractions, and observability. Here is how you can utilize its components in your application:
94
+
95
+ ### Integration Context
96
+ `IntegrationContext` propagates automatically through all your integration operations. It manages the current `run_id`, `integration`, and `current_record_key` to ensure all logs and traces are correctly correlated without manual passing.
97
+
98
+ ### Ingress & Pipelines
99
+ - **Webhook Ingress**: Use the `@ingress.webhook(pipeline="...", integration="...")` decorator on your FastAPI endpoints to attach integration context without manually wrapping your handlers.
100
+ - **Polling Ingress**: Use `@ingress.poll(pipeline="...", integration="...", schedule="...")` to create durable, stateful scheduled tasks (such as API polling).
101
+
102
+ ### RecordsFeed & Consumers
103
+ Extract and ingest data efficiently using **RecordsFeed**:
104
+ ```python
105
+ from beamflow_lib.pipelines.records_feed import RecordsFeed, RecordData
106
+
107
+ feed = RecordsFeed.get(feed_id="your.pipeline")
108
+ feed.publish(RecordData(record_id="123", record_type="invoice", data={...}))
109
+ ```
110
+ Records are automatically deduplicated and optionally ordered by timestamp.
111
+
112
+ Consume them robustly with **Feed Consumers**, which handle batching, rate-limiting, and parallel execution automatically:
113
+ ```python
114
+ from beamflow_lib.pipelines.consumer import feed_consumer
115
+
116
+ @feed_consumer(
117
+ feed_id="your.pipeline",
118
+ batch=True,
119
+ max_batch_size=100,
120
+ rate_limit_per_sec=50,
121
+ )
122
+ async def process_records(records: list[RecordData]):
123
+ for record in records:
124
+ # Insert your business logic here
125
+ pass
126
+ ```
127
+
128
+ ### Observability
129
+ Use the provided `logger` to naturally correlate your logs with the active context. The framework tracks the lifecycle of records intrinsically (from publishing to consumption) without manual `record_id` logging.
130
+
131
+ ```python
132
+ from beamflow_lib import logger
133
+
134
+ logger.info("Processing the invoice batch", details="step 1 completed")
135
+ ```
@@ -0,0 +1,24 @@
1
+ version: '3.8'
2
+ services:
3
+ api:
4
+ build:
5
+ context: ../..
6
+ dockerfile: deployment/shared/api.Dockerfile
7
+ image: beamflow-{project_name_slug}-api:latest
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - ENVIRONMENT={env}
12
+ extra_hosts:
13
+ - "host.docker.internal:host-gateway"
14
+
15
+ worker:
16
+ build:
17
+ context: ../..
18
+ dockerfile: deployment/shared/worker.Dockerfile
19
+ image: beamflow-{project_name_slug}-worker:latest
20
+ environment:
21
+ - ENVIRONMENT={env}
22
+ extra_hosts:
23
+ - "host.docker.internal:host-gateway"
24
+
@@ -3,8 +3,8 @@ services:
3
3
  api:
4
4
  build:
5
5
  context: ../..
6
- dockerfile: deployment/{env}/api.Dockerfile
7
- image: beamflow-{project_name}-api:latest
6
+ dockerfile: deployment/shared/api.Dockerfile
7
+ image: beamflow-{project_name_slug}-api:latest
8
8
  ports:
9
9
  - "8000:8000"
10
10
  environment:
@@ -18,8 +18,8 @@ services:
18
18
  worker:
19
19
  build:
20
20
  context: ../..
21
- dockerfile: deployment/{env}/worker.Dockerfile
22
- image: beamflow-{project_name}-worker:latest
21
+ dockerfile: deployment/shared/worker.Dockerfile
22
+ image: beamflow-{project_name_slug}-worker:latest
23
23
  environment:
24
24
  - REDIS_URL=redis://redis:6379/0
25
25
  - ENVIRONMENT={env}
@@ -1,20 +1,17 @@
1
1
  [project]
2
2
  name = "beamflow-cli"
3
- version = "0.3.0"
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 {}
@@ -1,28 +0,0 @@
1
- # {project_name}
2
-
3
- Beamflow application generated for {project_name}.
4
-
5
- ## Local Development
6
-
7
- 1. Install dependencies:
8
- ```bash
9
- pip install -e .
10
- ```
11
-
12
- 2. Run local environment:
13
- ```bash
14
- beamflow run dev
15
- ```
16
-
17
- 3. Build images:
18
- ```bash
19
- beamflow build dev
20
- ```
21
-
22
- ## Structure
23
-
24
- - `src/api`: FastAPI routes and webhooks
25
- - `src/worker`: Background tasks and feed consumers
26
- - `src/shared`: Shared models, clients, and tasks
27
- - `config`: Environment-specific configuration
28
- - `deployment`: Docker Compose and Dockerfiles
@@ -1,10 +0,0 @@
1
- from pathlib import Path
2
- from beamflow_lib.config.env_loader import load_config_dir
3
-
4
- def test_dev_config_loading():
5
- """
6
- Verify that the {env} environment configuration loads correctly.
7
- """
8
- config_dir = Path(__file__).parent.parent / "config"
9
- config = load_config_dir(config_dir, environment="{env}")
10
- assert config is not None
@@ -1,20 +0,0 @@
1
- import os
2
- import time
3
- import requests
4
- import pytest
5
-
6
- def get_base_url():
7
- return "http://localhost:8000"
8
-
9
- def test_health():
10
- base_url = get_base_url()
11
- resp = requests.get(f"{{base_url}}/health", timeout=5)
12
- assert resp.status_code == 200
13
- assert resp.json()["status"] == "ok"
14
-
15
- def test_webhook_process_user():
16
- base_url = get_base_url()
17
- resp = requests.post(f"{{base_url}}/webhooks/{project_name}/process_user", json={{"user_id": "test-user-1"}}, timeout=5)
18
- if resp.status_code == 404:
19
- resp = requests.post(f"{{base_url}}/{project_name}/process_user", json={{"user_id": "test-user-1"}}, timeout=5)
20
- assert resp.status_code == 200