Skip to content

Commit

Permalink
feat: gh swap (#1915)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Apr 17, 2024
1 parent 66c035b commit f2a565c
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 297 deletions.
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@
"pluggy>=1.3,<2",
"pydantic>=2.5.2,<3",
"pydantic-settings>=2.0.3,<3",
"PyGithub>=1.59,<2",
"pytest>=6.0,<8.0",
"python-dateutil>=2.8.2,<3",
"PyYAML>=5.0,<7",
Expand Down
22 changes: 16 additions & 6 deletions src/ape/managers/project/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
from ape.utils import (
ManagerAccessMixin,
cached_property,
github_client,
load_config,
log_instead_of_fail,
pragma_str_to_specifier_set,
)
from ape.utils._github import github_client


class DependencyManager(ManagerAccessMixin):
Expand Down Expand Up @@ -221,8 +221,16 @@ def version_id(self) -> str:
elif self.version and self.version != "latest":
return self.version

latest_release = github_client.get_release(self.github, "latest")
return latest_release.tag_name
latest_release = github_client.get_latest_release(self.org_name, self.repo_name)
return latest_release["tag_name"]

@cached_property
def org_name(self) -> str:
return self.github.split("/")[0]

@cached_property
def repo_name(self) -> str:
return self.github.split("/")[1]

@property
def uri(self) -> AnyUrl:
Expand Down Expand Up @@ -250,12 +258,14 @@ def extract_manifest(self, use_cache: bool = True) -> PackageManifest:
temp_project_path.mkdir(exist_ok=True, parents=True)

if self.ref:
github_client.clone_repo(self.github, temp_project_path, branch=self.ref)
github_client.clone_repo(
self.org_name, self.repo_name, temp_project_path, branch=self.ref
)

else:
try:
github_client.download_package(
self.github, self.version or "latest", temp_project_path
self.org_name, self.repo_name, self.version or "latest", temp_project_path
)
except UnknownVersionError as err:
logger.warning(
Expand All @@ -265,7 +275,7 @@ def extract_manifest(self, use_cache: bool = True) -> PackageManifest:
)
try:
github_client.clone_repo(
self.github, temp_project_path, branch=self.version
self.org_name, self.repo_name, temp_project_path, branch=self.version
)
except Exception:
# Raise the UnknownVersionError.
Expand Down
3 changes: 2 additions & 1 deletion src/ape/plugins/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from ape.__modules__ import __modules__
from ape.logging import logger
from ape.plugins import clean_plugin_name
from ape.utils import BaseInterfaceModel, get_package_version, github_client, log_instead_of_fail
from ape.utils import BaseInterfaceModel, get_package_version, log_instead_of_fail
from ape.utils._github import github_client
from ape.utils.basemodel import BaseModel
from ape.utils.misc import _get_distributions
from ape.version import version as ape_version_str
Expand Down
3 changes: 0 additions & 3 deletions src/ape/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
ManagerAccessMixin,
injected_before_use,
)
from ape.utils.github import GithubClient, github_client
from ape.utils.misc import (
DEFAULT_LIVE_NETWORK_BASE_FEE_MULTIPLIER,
DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT,
Expand Down Expand Up @@ -84,8 +83,6 @@
"get_relative_path",
"gas_estimation_error_message",
"get_package_version",
"GithubClient",
"github_client",
"GeneratedDevAccount",
"generate_dev_accounts",
"get_all_files_in_directory",
Expand Down
219 changes: 219 additions & 0 deletions src/ape/utils/_github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import os
import shutil
import subprocess
import tempfile
import zipfile
from io import BytesIO
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Union

from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

from ape.exceptions import CompilerError, ProjectError, UnknownVersionError
from ape.logging import logger
from ape.utils.misc import USER_AGENT, cached_property, stream_response


class GitProcessWrapper:
@cached_property
def git(self) -> str:
if path := shutil.which("git"):
return path

raise ProjectError("`git` not installed.")

def clone(self, url: str, target_path: Optional[Path] = None, branch: Optional[str] = None):
command = [self.git, "-c", "advice.detachedHead=false", "clone", url]

if target_path:
command.append(str(target_path))

if branch is not None:
command.extend(("--branch", branch))

logger.debug(f"Running git command: '{' '.join(command)}'")
result = subprocess.call(command)
if result != 0:
fail_msg = f"`git clone` command failed for '{url}'."

if branch and not branch.startswith("v"):
# Often times, `v` is required for tags.
try:
self.clone(url, target_path, branch=f"v{branch}")
except Exception:
raise ProjectError(fail_msg)

# Succeeded when prefixing `v`.
return

# Failed and we don't really know why.
# Shouldn't really happen.
# User will have to run command separately to debug.
raise ProjectError(fail_msg)


# NOTE: This client is only meant to be used internally for ApeWorX projects.
class _GithubClient:
# Generic git/github client attributes.
TOKEN_KEY = "GITHUB_ACCESS_TOKEN"
API_URL_PREFIX = "https://api.github.com"
git: GitProcessWrapper = GitProcessWrapper()

# ApeWorX-specific attributes.
ORGANIZATION_NAME = "ApeWorX"
FRAMEWORK_NAME = "ape"
_repo_cache: Dict[str, Dict] = {}

def __init__(self, session: Optional[Session] = None):
if session:
# NOTE: Mostly allowed for testing purposes.
self.__session = session

else:
headers = {"Content-Type": "application/json", "User-Agent": USER_AGENT}
if auth := os.environ[self.TOKEN_KEY] if self.TOKEN_KEY in os.environ else None:
headers["Authorization"] = f"token {auth}"

session = Session()
session.headers = {**session.headers, **headers}
adapter = HTTPAdapter(
max_retries=Retry(total=10, backoff_factor=1.0, status_forcelist=[403]),
)
session.mount("https://", adapter)
self.__session = session

@cached_property
def org(self) -> Dict:
"""
Our organization on ``Github``.
"""
return self.get_organization(self.ORGANIZATION_NAME)

@cached_property
def available_plugins(self) -> Set[str]:
return {
repo["name"].replace("-", "_")
for repo in self.get_org_repos()
if not repo.get("private", False) and repo["name"].startswith(f"{self.FRAMEWORK_NAME}-")
}

def get_org_repos(self) -> List[Dict]:
return self._get(f"orgs/{self.ORGANIZATION_NAME}/repos")

def get_release(self, org_name: str, repo_name: str, version: str) -> Dict:
if version == "latest":
return self.get_latest_release(org_name, repo_name)

def _try_get_release(vers):
try:
return self._get_release(org_name, repo_name, vers)
except Exception:
return None

if release := _try_get_release(version):
return release
else:
original_version = str(version)
# Try an alternative tag style
if version.startswith("v"):
version = version.lstrip("v")
else:
version = f"v{version}"

if release := _try_get_release(version):
return release

raise UnknownVersionError(original_version, repo_name)

def _get_release(self, org_name: str, repo_name: str, version: str) -> Dict:
return self._get(f"repos/{org_name}/{repo_name}/releases/tags/{version}")

def get_repo(self, org_name: str, repo_name: str) -> Dict:
repo_path = f"{org_name}/{repo_name}"
if repo_path not in self._repo_cache:
try:
self._repo_cache[repo_path] = self._get_repo(org_name, repo_name)
return self._repo_cache[repo_path]
except Exception as err:
raise ProjectError(f"Unknown repository '{repo_path}'") from err

else:
return self._repo_cache[repo_path]

def _get_repo(self, org_name: str, repo_name: str) -> Dict:
return self._get(f"repos/{org_name}/{repo_name}")

def get_latest_release(self, org_name: str, repo_name: str) -> Dict:
return self._get(f"repos/{org_name}/{repo_name}/releases/latest")

def get_organization(self, org_name: str) -> Dict:
return self._get(f"orgs/{org_name}")

def clone_repo(
self,
org_name: str,
repo_name: str,
target_path: Union[str, Path],
branch: Optional[str] = None,
scheme: str = "http",
):
repo = self.get_repo(org_name, repo_name)
branch = branch or repo["default_branch"]
logger.info(f"Cloning branch '{branch}' from '{repo['name']}'.")
url = repo["git_url"]

if "ssh" in scheme or "git" in scheme:
url = url.replace("git://github.com/", "[email protected]:")
elif "http" in scheme:
url = url.replace("git://", "https://")
else:
raise ValueError(f"Scheme '{scheme}' not supported.")

target_path = Path(target_path)
if target_path.exists():
# Else, cloning will fail!
target_path = target_path / repo_name

self.git.clone(url, branch=branch, target_path=target_path)

def download_package(
self, org_name: str, repo_name: str, version: str, target_path: Union[Path, str]
):
target_path = Path(target_path) # Handles str
if not target_path or not target_path.is_dir():
raise ValueError(f"'target_path' must be a valid directory (got '{target_path}').")

release = self.get_release(org_name, repo_name, version)
description = f"Downloading {org_name}/{repo_name}@{version}"
release_content = stream_response(
release["zipball_url"], progress_bar_description=description
)

# Use temporary path to isolate a package when unzipping
with tempfile.TemporaryDirectory() as tmp:
temp_path = Path(tmp)
with zipfile.ZipFile(BytesIO(release_content)) as zf:
zf.extractall(temp_path)

# Copy the directory contents into the target path.
downloaded_packages = [f for f in temp_path.iterdir() if f.is_dir()]
if len(downloaded_packages) < 1:
raise CompilerError(f"Unable to download package at '{org_name}/{repo_name}'.")

package_path = temp_path / downloaded_packages[0]
for source_file in package_path.iterdir():
shutil.move(str(source_file), str(target_path))

def _get(self, url: str) -> Any:
return self._request("GET", url)

def _request(self, method: str, url: str, **kwargs) -> Any:
url = f"{self.API_URL_PREFIX}/{url}"
response = self.__session.request(method, url, **kwargs)
response.raise_for_status()
return response.json()


github_client = _GithubClient()
Loading

0 comments on commit f2a565c

Please sign in to comment.