Skip to content

Commit

Permalink
Add example command (#22)
Browse files Browse the repository at this point in the history
- Add new example management command with download and list subcommands
- Add new create-app command

Resolve BE-1603
  • Loading branch information
nickpetrovic authored Aug 6, 2024
1 parent ac70cb6 commit f14c65c
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 47 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ test
__pycache__
.python-version
.DS_Store
.vscode/launch.json
79 changes: 39 additions & 40 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "beam-client"
version = "0.2.62"
version = "0.2.63"
description = ""
authors = ["beam.cloud <[email protected]>"]
packages = [
Expand All @@ -17,7 +17,7 @@ websockets = "^12.0"
[tool.poetry.group.dev.dependencies]
pytest = "^8.1.1"
pytest-env = "^1.1.3"
ruff = "^0.4.2"
ruff = "*"

[tool.poetry.scripts]
beam = "beam.cli.main:cli"
Expand All @@ -31,9 +31,5 @@ pythonpath = ["src"]

[tool.ruff]
line-length = 100
ignore-init-module-imports = true
exclude = []
src = ["src", "test", "bin"]

[tool.ruff.per-file-ignores]
"src/beam/__init__.py" = ["F403"]
163 changes: 163 additions & 0 deletions src/beam/cli/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import io
import os
import zipfile
from collections import defaultdict
from dataclasses import dataclass, field
from pathlib import Path
from typing import DefaultDict, List, Optional

import click
import requests
from beta9 import terminal
from beta9.cli.extraclick import ClickCommonGroup, ClickManagementGroup
from rich.table import Column, Table, box

# GIT_REPO_ZIP_FILE defaults to the main branch.
repo_zip = os.getenv("GIT_REPO_ZIP_FILE") or "main.zip"
repo_uri = os.getenv("GIT_REPO_URL") or "https://github.com/beam-cloud/examples"
repo_archive_uri = f"{repo_uri}/archive/refs/heads/{repo_zip}"


@click.group(cls=ClickCommonGroup)
def common(**_):
pass


@click.group(
name="example",
cls=ClickManagementGroup,
help="Manage example apps.",
)
def management(**_):
pass


@common.command(
name="create-app",
help="Downloads an examlpe app.",
)
@click.argument(
"name",
type=str,
nargs=1,
required=True,
)
@click.pass_context
def create_app(ctx: click.Context, name: str):
ctx.invoke(download_example, name=name)


@management.command(
name="download",
help="Downloads an example app.",
)
@click.argument(
"name",
type=str,
nargs=1,
required=True,
)
def download_example(name: str):
dirs = download_repo()
if not dirs:
return terminal.error(f"No files found in the repository {repo_uri}.")

app_dir = find_app_dirs_by_name(name, dirs)
if not app_dir:
return terminal.error(f"App example '{name}' not found in repository {repo_uri}.")

terminal.header(f"Creating app {name}...")
for file in app_dir.files:
terminal.detail(f"Writing {file.path}")
file.path.parent.mkdir(parents=True, exist_ok=True)
file.path.write_bytes(file.content)

terminal.success(f"App example '{name}' created! 🎉")


@management.command(
name="list",
help="List all available example apps.",
)
def list_examples():
dirs = download_repo()
app_dirs = find_app_dirs(dirs)

table = Table(
Column("Name"),
Column("Size", justify="right"),
box=box.SIMPLE,
)

for app in app_dirs:
table.add_row(
app.path.as_posix(),
terminal.humanize_memory(sum(len(f.content) for f in app.files)),
)

table.add_section()
table.add_row(f"[bold]{len(app_dirs)} items")
terminal.print(table)


@dataclass
class RepoFile:
path: Path
content: bytes


@dataclass
class RepoDir:
path: Path = Path()
files: List[RepoFile] = field(default_factory=list)


def download_repo(url: str = repo_archive_uri) -> List[RepoDir]:
"""
Downloads the repository into memory and returns a list of RepoDirs.
"""
response = requests.get(url)
response.raise_for_status()

files: List[RepoFile] = []
with zipfile.ZipFile(io.BytesIO(response.content)) as zip:
for file_info in zip.infolist():
if file_info.is_dir():
continue
with zip.open(file_info) as file:
path = Path(*file_info.filename.split("/")[1:])
files.append(RepoFile(path=path, content=file.read()))

files.sort(key=lambda f: f.path.name)

dirs: DefaultDict[Path, RepoDir] = defaultdict(RepoDir)
for file in files:
repo_dir = dirs[file.path.parent]
repo_dir.path = file.path.parent
repo_dir.files.append(file)

return list(dirs.values())


def find_app_dirs(dirs: List[RepoDir]) -> List[RepoDir]:
"""
Finds example app dirs.
An example app dir is a directory containing a README.md file.
"""
return sorted(
[RepoDir(path=d.path, files=d.files) for d in dirs if has_readme(d.files)],
key=lambda d: d.path.as_posix(),
)


def find_app_dirs_by_name(name: str, dirs: List[RepoDir]) -> Optional[RepoDir]:
"""
Finds an example app dir by name.
"""
apps = find_app_dirs(dirs)
return next((app for app in apps if app.path.as_posix() == name), None)


def has_readme(files: List[RepoFile]) -> bool:
return any(f.path.name.upper().endswith("README.MD") for f in files)
3 changes: 2 additions & 1 deletion src/beam/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from beta9 import config
from beta9.cli.main import load_cli

from . import configure, login, logs, quickstart
from . import configure, example, login, logs, quickstart


@dataclass
Expand All @@ -27,4 +27,5 @@ class SDKSettings(config.SDKSettings):
cli.register(quickstart)
cli.register(login)
cli.register(logs)
cli.register(example)
cli.load_version("beam-client")

0 comments on commit f14c65c

Please sign in to comment.