-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add new example management command with download and list subcommands - Add new create-app command Resolve BE-1603
- Loading branch information
1 parent
ac70cb6
commit f14c65c
Showing
5 changed files
with
207 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ test | |
__pycache__ | ||
.python-version | ||
.DS_Store | ||
.vscode/launch.json |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = [ | ||
|
@@ -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" | ||
|
@@ -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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters