Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to update robotpy dependency #114

Merged
merged 1 commit into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions robotpy_installer/cli_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import argparse
import json
import logging
import pathlib
import typing


from .installer import RobotpyInstaller
from . import pyproject

logger = logging.getLogger("project")


class UpdateRobotpy:
"""
Update the version of RobotPy your project depends on
"""

def __init__(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--use-certifi",
action="store_true",
default=False,
help="Use SSL certificates from certifi",
)

def run(self, project_path: pathlib.Path, use_certifi: bool) -> bool:
try:
project = pyproject.load(project_path)
except FileNotFoundError:
logger.error("Could not load pyproject.toml")
return False

print("Project robotpy version is", project.robotpy_version)

installer = RobotpyInstaller(log_startup=False)

# Determine what the latest version is
v = installer.get_pypi_version("robotpy", use_certifi)
print("Latest version of robotpy is", v)

if project.robotpy_version > v:
print("ERROR: refusing to update pyproject.toml!")
return False

# Update it in pyproject.toml
pyproject.set_robotpy_version(project_path, v)

return True


class Project:
"""
Manage your robot project
"""

subcommands = [
("update-robotpy", UpdateRobotpy),
]
21 changes: 21 additions & 0 deletions robotpy_installer/cli_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,20 @@ def __init__(self, parser: argparse.ArgumentParser):
help="Do not install any packages",
)

parser.add_argument(
"--no-upgrade-project",
action="store_true",
default=False,
help="Do not check to see if the project can be upgraded",
)

@handle_cli_error
def run(
self,
project_path: pathlib.Path,
main_file: pathlib.Path,
no_install: bool,
no_upgrade_project: bool,
user: bool,
use_certifi: bool,
):
Expand All @@ -87,6 +95,19 @@ def run(

# parse pyproject.toml to determine the requirements
project = pyproject.load(project_path, write_if_missing=True)
logger.info(
"RobotPy version in `pyproject.toml` is '%s'", project.robotpy_version
)

# Check for upgrade
if not no_upgrade_project:
latest_robotpy_version = installer.get_pypi_version("robotpy", use_certifi)
logger.info("Latest version of RobotPy is '%s'", latest_robotpy_version)
if project.robotpy_version < latest_robotpy_version:
msg = f"Update robotpy_version in `pyproject.toml` to {latest_robotpy_version}?"
if yesno(msg):
pyproject.set_robotpy_version(project_path, latest_robotpy_version)
project.robotpy_version = latest_robotpy_version

# Get the local version and don't accidentally downgrade them
try:
Expand Down
29 changes: 29 additions & 0 deletions robotpy_installer/installer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import contextlib
import inspect
import io
import json
import logging
import pathlib
import re
Expand All @@ -12,6 +13,8 @@

from os.path import basename, exists

from packaging.version import Version

from .version import version as __version__
from . import roborio_utils
from .cacheserver import CacheServer
Expand Down Expand Up @@ -632,6 +635,32 @@ def pip_uninstall(
with catch_ssh_error("uninstalling packages"):
self.ssh.exec_cmd(shlex.join(pip_args), check=True, print_output=True)

def get_pypi_version(self, package: str, use_certifi: bool) -> Version:
"""
Retrieves the latest version of a package on pypi that corresponds to the current year
"""
fname = self.cache_root / f"pypi-{package}.json"
_urlretrieve(
f"https://pypi.org/simple/{package}",
fname,
True,
_make_ssl_context(use_certifi),
False,
{"Accept": "application/vnd.pypi.simple.v1+json"},
)
with open(fname, "r") as fp:
data = json.load(fp)

versions = [Version(v) for v in data["versions"]]

# Sort the versions
maxv = Version(str(int(_WPILIB_YEAR) + 1))
versions = sorted(v for v in versions if v < maxv)
if not versions:
raise InstallerException(f"could not find {package} version on pypi")

return versions[-1]


def _make_ssl_context(use_certifi: bool):
if not use_certifi:
Expand Down
17 changes: 17 additions & 0 deletions robotpy_installer/pyproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from packaging.requirements import Requirement
from packaging.version import Version, InvalidVersion
import tomli
import tomlkit

from . import pypackages
from .pypackages import Packages, Env
Expand Down Expand Up @@ -245,3 +246,19 @@ def _load(
robotpy_extras=robotpy_extras,
requires=requires,
)


def set_robotpy_version(project_path: pathlib.Path, version: Version):
pyproject_path = toml_path(project_path)
with open(pyproject_path) as fp:
data = tomlkit.parse(fp.read())

try:
data["tool"]["robotpy"]["robotpy_version"] = str(version) # type: ignore
except Exception as e:
raise ValueError("`pyproject.toml` is not valid") from e

rawdata = tomlkit.dumps(data)

with open(pyproject_path, "w") as fp:
fp.write(rawdata)
38 changes: 28 additions & 10 deletions robotpy_installer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pathlib
import socket
import sys
import typing
import urllib.request

from robotpy_installer import __version__
Expand All @@ -26,9 +27,17 @@ def md5sum(fname):
return md5.hexdigest()


def _urlretrieve(url, fname: pathlib.Path, cache: bool, ssl_context):
# Get it
print("Downloading", url)
def _urlretrieve(
url,
fname: pathlib.Path,
cache: bool,
ssl_context,
show_status: bool = True,
reqheaders: typing.Optional[typing.Dict[str, str]] = None,
):
if show_status:
# Get it
print("Downloading", url)

# Save bandwidth! Use stored metadata to prevent re-downloading
# stuff we already have
Expand Down Expand Up @@ -59,14 +68,19 @@ def _reporthook(read, totalsize):
sys.stdout.flush()

try:
if reqheaders:
reqheaders = reqheaders.copy()
else:
reqheaders = {}

# adapted from urlretrieve source
headers = {"User-Agent": _useragent}
reqheaders["User-Agent"] = _useragent
if last_modified:
headers["If-Modified-Since"] = last_modified
reqheaders["If-Modified-Since"] = last_modified
if etag:
headers["If-None-Match"] = etag
reqheaders["If-None-Match"] = etag

req = urllib.request.Request(url, headers=headers)
req = urllib.request.Request(url, headers=reqheaders)

with contextlib.closing(
urllib.request.urlopen(req, context=ssl_context)
Expand All @@ -86,7 +100,9 @@ def _reporthook(read, totalsize):
break
read += len(block)
dfp.write(block)
_reporthook(read, size)

if show_status:
_reporthook(read, size)

if size >= 0 and read < size:
raise ValueError("Only retrieved %s of %s bytes" % (read, size))
Expand All @@ -104,7 +120,8 @@ def _reporthook(read, totalsize):
json.dump(md, fp)
except urllib.error.HTTPError as e:
if e.code == 304:
sys.stdout.write("Not modified")
if show_status:
sys.stdout.write("Not modified")
else:
raise
except Exception as e:
Expand All @@ -117,7 +134,8 @@ def _reporthook(read, totalsize):
raise Exception(msg) from e
else:
raise e
sys.stdout.write("\n")
if show_status:
sys.stdout.write("\n")


def _resolve_addr(hostname):
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ install_requires =
pynetconsole~=2.0.2
robotpy-cli~=2024.0
tomli
tomlkit
setup_requires =
setuptools_scm > 6
python_requires = >=3.8
Expand All @@ -41,5 +42,6 @@ robotpy =
deploy-info = robotpy_installer.cli_deploy_info:DeployInfo
init = robotpy_installer.cli_init:Init
installer = robotpy_installer.cli_installer:Installer
project = robotpy_installer.cli_project:Project
sync = robotpy_installer.cli_sync:Sync
undeploy = robotpy_installer.cli_undeploy:Undeploy
Loading