Source code for frame_cli.push
"""Module for `frame push` command."""
import os
import subprocess
import yaml
import git
import github
from github.AuthenticatedUser import AuthenticatedUser
from github.Repository import Repository as GithubRepository
from .config import FRAME_REPO, FRAME_REPO_NAME, EXTERNAL_REFERENCES_PATH
from .info import get_github_token, get_home_info_path, get_local_model_info
from .logging import logger
from .metadata import validate as validate_metadata_file
[docs]
class MissingModelURLError(Exception):
"""Exception raised when the model URL is missing in the metadata file."""
[docs]
class ModelAlreadyTrackedError(Exception):
"""Exception raised when the model is already tracked by the Frame repository."""
[docs]
def create_frame_fork(upstream_repo: GithubRepository, github_user: AuthenticatedUser) -> GithubRepository:
return github_user.create_fork(upstream_repo)
[docs]
def get_local_frame_repo(github_client: github.Github, github_user: AuthenticatedUser):
local_repo_path = os.path.join(get_home_info_path(), FRAME_REPO_NAME)
logger.debug("Getting local Frame repository")
try:
local_repo = git.Repo(local_repo_path)
logger.debug(f"Found cloned Frame repository at {local_repo_path}")
except git.NoSuchPathError:
upstream_repo = github_client.get_repo(FRAME_REPO)
try:
fork = github_user.get_repo(FRAME_REPO_NAME)
logger.debug(f"Found fork of {upstream_repo.clone_url} for user {github_user.login}")
except github.UnknownObjectException:
logger.info(f"Creating fork of {upstream_repo.clone_url} for user {github_user.login}")
fork = create_frame_fork(upstream_repo, github_user)
logger.info(f"Cloning {fork.clone_url} into {local_repo_path}")
local_repo = git.Repo.clone_from(fork.clone_url, local_repo_path)
local_repo.remotes.origin.set_url(
fork.clone_url.replace("://", f"://{github_user.login}:{get_github_token()}@")
)
local_repo.create_remote("upstream", url=upstream_repo.clone_url)
logger.debug("Fetching upstream repository")
local_repo.remotes.upstream.fetch()
return local_repo
[docs]
def get_model_name() -> str:
model_info = get_local_model_info()
if "name" not in model_info:
raise ValueError("Model name not found in local model info.")
return model_info["name"]
[docs]
def get_model_url() -> str:
model_info = get_local_model_info()
if "url" not in model_info:
raise ValueError("Model URL not found in local model info.")
if not model_info["url"]:
raise MissingModelURLError
return model_info["url"]
[docs]
def generate_branch_name() -> str:
model_name = get_model_name()
return f"feat-{model_name}"
[docs]
def add_model_to_local_frame_repo(local_repo: git.Repo):
branch_name = generate_branch_name()
logger.debug("Checking out main branch")
local_repo.git.checkout("main")
try:
logger.debug(f"Creating branch {branch_name} from upstream/main")
local_repo.git.branch("-f", branch_name, "upstream/main")
except git.GitCommandError:
pass
logger.debug(f"Checking out branch {branch_name}")
local_repo.git.checkout(branch_name)
external_references_path = os.path.join(str(local_repo.working_tree_dir), EXTERNAL_REFERENCES_PATH)
with open(external_references_path, "r") as file:
external_references = yaml.safe_load(file) or []
model_url = get_model_url()
if model_url in external_references:
raise ModelAlreadyTrackedError("Model URL already in external references.")
external_references.append(model_url)
with open(external_references_path, "w") as file:
yaml.dump(external_references, file)
local_repo.git.add(EXTERNAL_REFERENCES_PATH)
model_name = get_model_name()
local_repo.git.commit(m=f"feat(metadata): add {model_name} to external references")
[docs]
def push_to_frame_fork(local_repo: git.Repo):
logger.info("Pushing changes to Frame fork")
local_repo.git.push("origin", generate_branch_name(), force_with_lease=True)
[docs]
def create_pull_request(github_client: github.Github, github_user: AuthenticatedUser):
branch_name = generate_branch_name()
upstream_repo = github_client.get_repo(FRAME_REPO)
logger.info(f"Creating pull request from {github_user.login}:{branch_name} to {upstream_repo.full_name}:main")
try:
pull_request = upstream_repo.create_pull(
title=f'feat(metadata): Add "{get_model_name()}" to external references',
body="",
head=f"{github_user.login}:{branch_name}",
base="main",
)
except github.GithubException as e:
for error in e.data["errors"]:
logger.error(error["message"])
print("Error creating pull request.")
return
print(f"Pull request created: {pull_request.html_url}")
[docs]
def validate_integration() -> bool:
"""Validate metadata file and absence of conflicts with other Frame models."""
logger.info("Validating integration with Frame repository")
frame_api_path = os.path.join(get_home_info_path(), FRAME_REPO_NAME, "backend")
logger.info("Creating virtual environment using uv")
ret = subprocess.run("uv venv".split(), cwd=frame_api_path, capture_output=True)
if ret.returncode != 0:
print(
"Error creating virtual environment using uv. Please check that uv and a recent version of Python are installed."
)
return False
logger.info("Installing dependencies")
ret = subprocess.run("uv pip install -e .[test]".split(), cwd=frame_api_path, capture_output=True)
if ret.returncode != 0:
print("Error installing dependencies.")
return False
logger.info("Running integration tests")
ret = subprocess.run("uv run pytest --disable-warnings".split(), cwd=frame_api_path)
if ret.returncode != 0:
print("Integration tests failed.")
return False
logger.info("Integration tests passed")
return True
[docs]
def push(use_new_token: bool = False):
"""Submit a pull request to the Frame project with new/updated metadata."""
if not validate_metadata_file():
print("Metadata file does not follow schema.")
return
github_client = github.Github(get_github_token(use_new_token))
github_user = github_client.get_user()
try:
logger.info(f'Accessing GitHub API as "{github_user.login}"')
except github.BadCredentialsException:
print(
"Invalid GitHub token. Please check whether your token is still valid, or run the command with the --use-new-token option."
)
return
local_repo = get_local_frame_repo(github_client, github_user)
try:
add_model_to_local_frame_repo(local_repo)
except MissingModelURLError:
print("Model URL is empty. Please set the model URL to the remote repository URL in the metadata file.")
return
except ModelAlreadyTrackedError:
print("Model URL already tracked in external references.")
return
if not validate_integration():
print("Validation failed. Please check the metadata file and resolve any conflicts.")
return
push_to_frame_fork(local_repo)
create_pull_request(github_client, github_user)