blocks-cli 0.1.24__tar.gz → 0.1.25__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 (28) hide show
  1. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/PKG-INFO +1 -2
  2. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/README.md +1 -1
  3. blocks-cli-0.1.25/blocks_cli/builds.py +20 -0
  4. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/commands/configure.py +7 -12
  5. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/commands/create.py +1 -1
  6. blocks-cli-0.1.25/blocks_cli/commands/init.py +79 -0
  7. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/commands/push.py +39 -10
  8. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/commands/test.py +10 -9
  9. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/config/auth.py +1 -1
  10. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/setup.py +1 -1
  11. blocks-cli-0.1.24/blocks_cli/builds.py +0 -11
  12. blocks-cli-0.1.24/blocks_cli/commands/init.py +0 -63
  13. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/LICENSE +0 -0
  14. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/MANIFEST.in +0 -0
  15. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/__init__.py +0 -0
  16. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/api.py +0 -0
  17. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/bundles.py +0 -0
  18. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/commands/__base__.py +0 -0
  19. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/commands/__init__.py +0 -0
  20. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/config/__init__.py +0 -0
  21. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/config/config.py +0 -0
  22. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/console.py +0 -0
  23. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/fs.py +0 -0
  24. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/package.py +0 -0
  25. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli/registration.py +0 -0
  26. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/blocks_cli.egg-info/SOURCES.txt +0 -0
  27. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/requirements.txt +0 -0
  28. {blocks-cli-0.1.24 → blocks-cli-0.1.25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: blocks-cli
3
- Version: 0.1.24
3
+ Version: 0.1.25
4
4
  Summary: CLI tool for Blocks, a platform for writing custom AI-enabled codebase automations in Python. Leverage a full codebase-aware API. Automatically trigger automations from Github, Slack, and other providers.
5
5
  Home-page: https://github.com/BlocksOrg/sdk
6
6
  Author: BlocksOrg
@@ -69,4 +69,3 @@ blocks init --api-key <your-api-key>
69
69
  blocks push automation.py
70
70
  ```
71
71
 
72
-
@@ -36,4 +36,4 @@ def my_automation(event):
36
36
  ```bash
37
37
  blocks init --api-key <your-api-key>
38
38
  blocks push automation.py
39
- ```
39
+ ```
@@ -0,0 +1,20 @@
1
+ import time
2
+
3
+ from blocks_cli.api import api_client
4
+ from blocks_cli.config.config import config
5
+
6
+ def poll_build_status(image_id: str, build_id: str):
7
+ build_completed = False
8
+ while not build_completed:
9
+ time.sleep(0.5)
10
+ res = api_client.get(f"{config.clients.orchestrator_url}/v1/images/{image_id}/builds/{build_id}")
11
+ build_status_response = res.json()
12
+
13
+ is_completed = build_status_response.get("is_completed")
14
+ is_succeeded = build_status_response.get("is_succeeded")
15
+
16
+ build_completed = is_completed
17
+
18
+ if is_completed and not is_succeeded:
19
+ raise Exception("Build failed")
20
+
@@ -33,7 +33,6 @@ def configure(apikey: str = typer.Option(None, "--key", help="Blocks API key")):
33
33
  print()
34
34
 
35
35
  new_api_key = typer.prompt("API Key", existing_api_key, show_default=False)
36
-
37
36
  else:
38
37
  new_api_key = apikey
39
38
 
@@ -44,7 +43,7 @@ def configure(apikey: str = typer.Option(None, "--key", help="Blocks API key")):
44
43
  with Progress(
45
44
  SpinnerColumn(),
46
45
  TextColumn("[progress.description]{task.description}"),
47
- transient=True,
46
+ transient=False,
48
47
  ) as init_progress:
49
48
  try:
50
49
  api_task = init_progress.add_task(description="Verifying API key...", total=None)
@@ -54,21 +53,17 @@ def configure(apikey: str = typer.Option(None, "--key", help="Blocks API key")):
54
53
  })
55
54
 
56
55
  if response.status_code > 299:
57
- raise Exception("API Key is invalid. Please check your API key at [link=https://app.blocksorg.com]https://app.blocksorg.com[/link]")
56
+ raise Exception("API Key is invalid. Please check your API key at [white]https://app.blocksorg.com[/white]")
58
57
 
59
58
  config.auth.save_api_key(new_api_key)
60
- init_progress.update(api_task, description=":white_check_mark: API key verified and saved successfully")
61
- init_progress.refresh()
59
+ last_digits = new_api_key[-8:]
60
+ init_progress.update(api_task, description=f"[green]API key verified and saved successfully [italic][dim]...{last_digits}[/dim][/italic][/green]")
62
61
  except Exception as e:
63
- raise InvalidApiKeyError(f"{e}")
64
-
65
- last_digits = new_api_key[-8:]
66
-
67
- console.print(f"[green]API key saved successfully [italic][dim]...{last_digits}[/dim][/italic][/green]")
62
+ raise InvalidApiKeyError(f"Failed to verify API key. Please check your API key at [white]https://app.blocksorg.com[/white]")
68
63
 
69
64
  except InvalidApiKeyError as e:
70
- console.print(f":cross_mark: [red]{e}[/red]")
65
+ console.print(f"[red]{e}[/red]")
71
66
  raise typer.Exit(code=1)
72
67
  except Exception as e:
73
- console.print(f":cross_mark: Error configuring blocks")
68
+ console.print(f"[red]Error configuring blocks[/red]")
74
69
  raise typer.Exit(code=1)
@@ -63,7 +63,7 @@ def {name}(input):
63
63
  f.write('''blocks-sdk>={version}'''.format(version=latest_version))
64
64
 
65
65
  console.print(f"Successfully created automation [green]{name}[/green] in [green]{automation_dir.absolute()}[/green]")
66
- console.print(Panel.fit(f"[green]{name}/\n main.py\n requirements.txt[/green]"))
66
+ console.print(f"[green]{name}/\n main.py\n requirements.txt[/green]")
67
67
  console.print(f"[blue]Choose an event from [white]https://docs.blocksorg.com/events[/white] and run [white]blocks test .blocks/{name}/main.py[/white] to test the automation[/blue]")
68
68
 
69
69
  except Exception as e:
@@ -0,0 +1,79 @@
1
+ import git
2
+ import typer
3
+ from pathlib import Path
4
+
5
+ from rich.progress import Progress, SpinnerColumn, TextColumn
6
+
7
+ from blocks_cli.api import api_client
8
+ from blocks_cli.commands.__base__ import blocks_cli
9
+ from blocks_cli.package import warn_current_package_version
10
+ from blocks_cli.config.config import config
11
+ from blocks_cli.console import console
12
+ from blocks_cli.fs import find_dir
13
+
14
+ class AlreadyInitializedError(Exception):
15
+ pass
16
+
17
+ @blocks_cli.command()
18
+ def init(apikey: str = typer.Option(None, "--key", help="API key for authentication")):
19
+ """Initialize blocks in the current directory."""
20
+ try:
21
+ warn_current_package_version()
22
+
23
+ # finds .blocks directory already inside or .blocks is below the current directory
24
+ blocks_dir = find_dir(target=".blocks")
25
+
26
+ if blocks_dir is not None:
27
+ if apikey:
28
+ console.print(f"[yellow]To change your API key, use [white]blocks configure --key {apikey}[/white][/yellow]")
29
+ raise AlreadyInitializedError(f"Blocks is already initialized: [white]{blocks_dir}[/white]")
30
+ else:
31
+ # We are in some other subdirectory
32
+ working_dir = Path.cwd()
33
+ try:
34
+ # Try to find the root of the directory if git is initialized
35
+ repo = git.Repo(search_parent_directories=True)
36
+ working_dir = repo.working_dir
37
+ except Exception as e:
38
+ pass
39
+
40
+ # working_dir is equal to root of the directory or the current directory if git is not initialized
41
+ working_dir = Path(working_dir)
42
+ blocks_dir = working_dir / ".blocks"
43
+ if not blocks_dir.exists():
44
+ blocks_dir.mkdir()
45
+ console.print(f"[green]Created [white].blocks[/white] folder: [white]{blocks_dir}[/white][/green]")
46
+ else:
47
+ if apikey:
48
+ console.print(f"[yellow]To change your API key, use [white]blocks configure --key {apikey}[/white][/yellow]")
49
+ raise AlreadyInitializedError(f"[yellow]Blocks is already initialized: [white]{blocks_dir}[/white][/yellow]")
50
+
51
+ # Verify and save API key if provided
52
+ if apikey:
53
+ with Progress(
54
+ SpinnerColumn(),
55
+ TextColumn("[progress.description]{task.description}"),
56
+ transient=False,
57
+ ) as progress:
58
+ api_task = progress.add_task(description="Verifying API key...", total=None, style="blue")
59
+
60
+ response = api_client.get(f"{config.clients.client_url}/v1/apikeys/{apikey}", headers={
61
+ "Authorization": f"ApiKey {apikey}"
62
+ })
63
+
64
+ if response.status_code > 299:
65
+ raise Exception("API Key is invalid. Please check your API key at [white]https://app.blocksorg.com[/white]")
66
+
67
+ config.auth.save_api_key(apikey)
68
+ last_digits = apikey[-8:]
69
+ progress.update(api_task, description=f"[green]API key verified and saved successfully [italic][dim]...{last_digits}[/dim][/italic][/green]")
70
+ progress.refresh()
71
+
72
+ console.print("[green]Blocks has been successfully initialized.[/green]")
73
+
74
+ except AlreadyInitializedError as e:
75
+ console.print(f"[yellow]{str(e)}[/yellow]")
76
+ raise typer.Exit(code=0)
77
+ except Exception as e:
78
+ console.print(f"[red]{str(e)}[/red]")
79
+ raise typer.Exit(code=1)
@@ -2,8 +2,9 @@ import git
2
2
  import typer
3
3
  import importlib.util
4
4
  import sys
5
- from pathlib import Path
5
+ import re
6
6
 
7
+ from pathlib import Path
7
8
  from rich.progress import Progress, SpinnerColumn, TextColumn
8
9
 
9
10
  from blocks_cli.console import console
@@ -28,6 +29,32 @@ def push(file: Path = typer.Argument(..., help="Name of blocks file to push.")):
28
29
  ) as init_progress:
29
30
  init_task = init_progress.add_task(description="Initializing...", total=None)
30
31
 
32
+ state, _ = get_blocks_state_and_module_from_file(file)
33
+
34
+ if not state.automations:
35
+ raise Exception(f"No automations found in the specified file")
36
+
37
+ automation_names = []
38
+ try:
39
+ automation_names = [automation.get("task_kwargs",{})["name"] for automation in state.automations]
40
+ except Exception as e:
41
+ raise Exception("Automations must have a name defined in the [white]@task[/white] decorator")
42
+
43
+ for automation_name in automation_names:
44
+ is_valid = re.match(r"^[a-zA-Z0-9][a-zA-Z0-9_-]*$", automation_name)
45
+ if is_valid is None:
46
+ raise Exception(f"[red]Automation [white]'{automation_name}'[/white] is not a valid name. Automation names must start with a letter or number and can only contain letters, numbers, dashes, and underscores.[/red]")
47
+
48
+ trigger_aliases = []
49
+ try:
50
+ trigger_aliases = [automation.get("trigger_alias") for automation in state.automations]
51
+ except Exception as e:
52
+ raise Exception("Event must be defined in the [white]@on[/white] decorator")
53
+
54
+ for trigger_alias in trigger_aliases:
55
+ if trigger_alias is None or trigger_alias == "":
56
+ raise Exception(f"Event [white]'{trigger_alias}'[/white] is not a valid. For a list of supported events, please visit [white]https://docs.blocksorg.com/docs/events[/white]")
57
+
31
58
  # working directory from where the command was invoked
32
59
  cwd = file.resolve().parent
33
60
 
@@ -57,9 +84,7 @@ def push(file: Path = typer.Argument(..., help="Name of blocks file to push.")):
57
84
  )
58
85
 
59
86
  # get pip dependencies
60
-
61
87
  pip_dependencies = []
62
-
63
88
  if Path(requirements_path).exists():
64
89
  with open(requirements_path, "r") as f:
65
90
  pip_dependencies = f.read().splitlines()
@@ -68,8 +93,6 @@ def push(file: Path = typer.Argument(..., help="Name of blocks file to push.")):
68
93
  init_task, total=1, description="Collecting automations..."
69
94
  )
70
95
 
71
- state, _ = get_blocks_state_and_module_from_file(file)
72
-
73
96
  # Construct payload
74
97
  with Progress(
75
98
  SpinnerColumn(),
@@ -161,10 +184,16 @@ def push(file: Path = typer.Argument(..., help="Name of blocks file to push.")):
161
184
  image_id = res.json().get("image_id")
162
185
  is_build_triggered = res.json().get("is_build_triggered")
163
186
  if is_build_triggered:
164
- build_status = poll_build_status(image_id, build_id)
165
- build_progress.update(
166
- build_task, total=1, description="Build completed successfully"
167
- )
187
+ try:
188
+ poll_build_status(image_id, build_id)
189
+ build_progress.update(
190
+ build_task, total=1, description="Build succeeded"
191
+ )
192
+ except Exception as e:
193
+ build_progress.update(
194
+ build_task, total=1, description="Build failed, please try again. If the issue persists check your automation's requirements.txt to ensure all dependencies are valid and/or our status page at https://status.blocksorg.com"
195
+ )
196
+ raise typer.Exit(1)
168
197
  except Exception as e:
169
- console.print(f"[red] Error pushing automation: {str(e)}[/red]")
198
+ console.print(f"[red]{str(e)}[/red]")
170
199
  raise typer.Exit(1)
@@ -26,6 +26,8 @@ def invoke_automation_with_test_event(automation_module, automation):
26
26
  transient=False,
27
27
  ) as test_event_progress:
28
28
  test_event_task = test_event_progress.add_task(description="Preparing automation...", total=None)
29
+ if trigger_alias is None or trigger_alias == "":
30
+ raise Exception("Event must be defined in the [white]@on[/white] decorator. For a list of supported events, please visit [white]https://docs.blocksorg.com/docs/events[/white]")
29
31
  res = api_client.get(f"{config.clients.client_url}/v1/test_events", params={
30
32
  "trigger_alias": trigger_alias,
31
33
  })
@@ -35,18 +37,17 @@ def invoke_automation_with_test_event(automation_module, automation):
35
37
  event_response = res.json()
36
38
  event_data = event_response.get("event_data")
37
39
 
38
- console.print(f"Invoking automation [green]{automation_name}[/green] with event [green]{trigger_alias}[/green]")
40
+ console.print(f"[blue]Invoking automation [white]{automation_name}[/white] with event [white]{trigger_alias}[/white][/blue]")
39
41
  console.print(
40
- Rule("Automation Logs", characters="=", style="white")
42
+ Rule("BEGIN Automation Logs", characters="=", style="blue")
41
43
  )
42
44
 
43
45
  res = function(event_data)
44
-
45
46
  console.print(
46
- Rule("[bold grey]END Automation Logs[/bold grey]", characters="=", style="white")
47
+ Rule("END Automation Logs", characters="=", style="blue")
47
48
  )
48
49
 
49
- console.print(f"Automation [green]{automation_name}[/green] invoked successfully")
50
+ console.print(f"[green]Automation [white]{automation_name}[/white] invoked successfully[/green]")
50
51
 
51
52
 
52
53
 
@@ -72,17 +73,17 @@ def test(
72
73
  invoke_automation_with_test_event(automation_module, automation)
73
74
 
74
75
  elif num_automations > 1 and not name:
75
- raise Exception("[yellow]Multiple automations found in the file, please specify which one to test with the [white]--name[/white] flag.[/yellow]")
76
+ raise Exception("Multiple automations found in the file, please specify which one to test with the [white]--name[/white] flag.")
76
77
  elif num_automations > 1 and name:
77
78
  # find in automations
78
79
  automation = next((a for a in automations if a.get("task_kwargs",{}).get("name") == name), None)
79
80
  if not automation:
80
- raise Exception(f"[yellow]Automation with name [white]{name}[/white] not found.[/yellow]")
81
+ raise Exception(f"Automation with name [white]{name}[/white] not found.")
81
82
 
82
83
  invoke_automation_with_test_event(automation_module, automation)
83
84
  else:
84
- raise Exception("[yellow]No valid automations found in the specified file.[/yellow]")
85
+ raise Exception("No valid automations found in the specified file.")
85
86
 
86
87
  except Exception as e:
87
- console.print(f"[red]Error: {e}[/red]")
88
+ console.print(f"[red]{e}[/red]")
88
89
  raise typer.Exit(1)
@@ -17,7 +17,7 @@ class AuthConfig(BaseModel):
17
17
  # Allow only base64url characters (alphanumeric, '-', '_', and '=')
18
18
  # This prevents invalid characters from blowing up the config file
19
19
  if re.search(r'[^a-zA-Z0-9\-_=]', api_key):
20
- raise ValueError("Invalid API key supplied. Please check your API key at [link=https://app.blocksorg.com]https://app.blocksorg.com[/link]")
20
+ raise ValueError("Invalid API key supplied. Please check your API key at [white]https://app.blocksorg.com[/white]")
21
21
 
22
22
  config_file = Config.get_config_file()
23
23
 
@@ -6,7 +6,7 @@ with open("requirements.txt") as f:
6
6
 
7
7
  setup(
8
8
  name="blocks-cli",
9
- version="0.1.24",
9
+ version="0.1.25",
10
10
  packages=find_packages(),
11
11
  include_package_data=True,
12
12
  install_requires=requirements,
@@ -1,11 +0,0 @@
1
- import time
2
-
3
- from blocks_cli.api import api_client
4
- from blocks_cli.config.config import config
5
-
6
- def poll_build_status(image_id: str, build_id: str):
7
- build_completed = False
8
- while not build_completed:
9
- res = api_client.get(f"{config.clients.orchestrator_url}/v1/images/{image_id}/builds/{build_id}")
10
- build_completed = res.json().get("is_completed")
11
- time.sleep(1)
@@ -1,63 +0,0 @@
1
- import git
2
- import typer
3
- from pathlib import Path
4
-
5
- from rich.progress import Progress, SpinnerColumn, TextColumn
6
-
7
- from blocks_cli.api import api_client
8
- from blocks_cli.commands.__base__ import blocks_cli
9
- from blocks_cli.package import warn_current_package_version
10
- from blocks_cli.config.config import config
11
- from blocks_cli.console import console
12
-
13
- @blocks_cli.command()
14
- def init(apikey: str = typer.Option(None, "--key", help="API key for authentication")):
15
- """Initialize blocks in the current directory."""
16
- try:
17
- warn_current_package_version()
18
-
19
- with Progress(
20
- SpinnerColumn(),
21
- TextColumn("[progress.description]{task.description}"),
22
- transient=False,
23
- ) as progress:
24
-
25
- working_dir = Path.cwd()
26
- try:
27
- repo = git.Repo(search_parent_directories=True)
28
- working_dir = repo.working_dir
29
- except Exception as e:
30
- pass
31
-
32
- working_dir = Path(working_dir)
33
-
34
- # Create .blocks directory if it doesn't exist
35
- blocks_dir = working_dir / ".blocks"
36
-
37
- if not blocks_dir.exists():
38
- blocks_dir.mkdir()
39
- folder_task = progress.add_task(description="Creating .blocks folder...", total=None)
40
- progress.update(folder_task, description="[green]Created .blocks folder[/green]")
41
-
42
- progress.refresh()
43
-
44
- # Verify and save API key if provided
45
- if apikey:
46
- api_task = progress.add_task(description="Verifying API key...", total=None)
47
-
48
- response = api_client.get(f"{config.clients.client_url}/v1/apikeys/{apikey}", headers={
49
- "Authorization": f"ApiKey {apikey}"
50
- })
51
-
52
- if response.status_code > 299:
53
- raise Exception("API Key is invalid. Please check your API key at [link=https://app.blocksorg.com]https://app.blocksorg.com[/link]")
54
-
55
- config.auth.save_api_key(apikey)
56
- progress.update(api_task, description="[green]API key verified and saved successfully[/green]")
57
- progress.refresh()
58
-
59
- console.print("[green]Blocks has been successfully initialized.[/green]")
60
-
61
- except Exception as e:
62
- console.print(f"[red]Error initializing blocks: {str(e)}[/red]")
63
- raise typer.Exit(code=1)
File without changes
File without changes
File without changes