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

use argparse & small refactoring #25

Merged
merged 1 commit into from
Apr 8, 2022
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
12 changes: 6 additions & 6 deletions flake.lock

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

8 changes: 5 additions & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
let
pkgs = import nixpkgs {
inherit system;
overlays = [ poetry2nix.overlay ];
overlays = [
poetry2nix.overlay
];
};
python = pkgs.python310;
packageName = "hydra-check";
Expand All @@ -35,7 +37,7 @@
nativeBuildInputs = with python.pkgs; [ poetry-core ];
propagatedBuildInputs = with python.pkgs; [ requests beautifulsoup4 docopt ];
src = ./.;
checkInputs = with pkgs; [ mypy ];
checkInputs = with pkgs; [ python.pkgs.mypy ];
checkPhase = ''
export MYPYPATH=$PWD/src
mypy src/hydra_check
Expand All @@ -48,7 +50,6 @@
default = pkgs.mkShell {
buildInputs = with pkgs; [
pyright
mypy
(pkgs.poetry.override { python = python; })
(pkgs.poetry2nix.mkPoetryEnv {
inherit python;
Expand All @@ -63,6 +64,7 @@
] ++ (with python.pkgs; [
black
pylint
mypy
]);
shellHook = ''
export MYPYPATH=$PWD/src
Expand Down
17 changes: 14 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = ["Felix Richter <[email protected]>"]
hydra-check = 'hydra_check.cli:main'

[tool.poetry.dependencies]
python = "^3.9"
python = "^3.10"
requests = "^2.2"
beautifulsoup4 = "^4.1"
docopt = "^0.6"
Expand Down Expand Up @@ -42,12 +42,23 @@ ignore_missing_imports = true


[tool.black]
line-length = 88
line-length = 120
target_version = ['py310']

[tool.pylint.master]
init-hook = "import sys; sys.path.append('src')"

[tool.pylint.FORMAT]
output-format = "colorized"
max-line-length = 88
max-line-length = 120

[tool.pylint.messages_control]
disable = [
# Many functions (e.g. callbacks) will naturally have unused arguments.
"unused-argument",

# Disable failure for TODO items in the codebase (code will always have TODOs).
"fixme",

"missing-docstring"
]
53 changes: 53 additions & 0 deletions src/hydra_check/arguments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import argparse
import textwrap


def process_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent('''\
Other channels can be:
unstable - alias for nixos/trunk-combined (Default)
master - alias for nixpkgs/trunk
staging - alias for nixos/staging
19.03 - alias for nixos/release-19.03
19.09 - alias for nixos/release-19.09
20.03 - alias for nixos/release-20.03
nixpkgs/nixpkgs-20.03-darwin - verbatim jobset name

Jobset names can be constructed with the project name (e.g. `nixos/` or `nixpkgs/`)
followed by a branch name. The available jobsets can be found at:
* https://hydra.nixos.org/project/nixos
* https://hydra.nixos.org/project/nixpkgs
''')
)
parser.add_argument(
"PACKAGES",
action="append",
)
parser.add_argument(
"--url",
action="store_true",
help="only print the hydra build url, then exit",
)
parser.add_argument(
"--json",
action="store_true",
help="output json",
)
parser.add_argument(
"--short",
action="store_true",
help="write only the latest build even if last build failed",
)
parser.add_argument(
"--arch",
default="x86_64-linux",
help="system architecture to check",
)
parser.add_argument(
"--channel",
default="unstable",
help="Channel to check packages for",
)
return parser.parse_args()
88 changes: 33 additions & 55 deletions src/hydra_check/cli.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
"""usage: hydra-check [options] PACKAGES...

options:
--arch=SYSTEM system architecture to check [default: x86_64-linux]
--json write builds in machine-readable format
--short write only the latest build even if last build failed
--url only print the hydra build url, then exit
--channel=CHAN Channel to check packages for [Default: unstable]

Other channels can be:
unstable - alias for nixos/trunk-combined (Default)
master - alias for nixpkgs/trunk
staging - alias for nixos/staging
19.03 - alias for nixos/release-19.03
19.09 - alias for nixos/release-19.09
20.03 - alias for nixos/release-20.03
nixpkgs/nixpkgs-20.03-darwin - verbatim jobset name

Jobset names can be constructed with the project name (e.g. `nixos/` or `nixpkgs/`)
followed by a branch name. The available jobsets can be found at:
* https://hydra.nixos.org/project/nixos
* https://hydra.nixos.org/project/nixpkgs

"""
from bs4 import BeautifulSoup
import requests
import json
from sys import exit as sysexit
from typing import Dict, Iterator, Union

from sys import exit
import requests
from bs4 import BeautifulSoup

from hydra_check.arguments import process_args

# TODO: use TypedDict
BuildStatus = Dict[str, Union[str, bool]]
Expand All @@ -36,30 +14,31 @@
# guess functions are intended to be fast without external queries
def guess_jobset(channel: str) -> str:
# TODO guess the latest stable channel
if channel == "master":
return "nixpkgs/trunk"
elif channel == "unstable":
return "nixos/trunk-combined"
elif channel == "staging":
return "nixos/staging"
elif channel[0].isdigit():
# 19.09, 20.03 etc
return f"nixos/release-{channel}"
else:
# we asume that the user knows the jobset name ( nixos/release-19.09 )
return channel
match channel:
case "master":
return "nixpkgs/trunk"
case "unstable":
return "nixos/trunk-combined"
case "staging":
return "nixos/staging"
case _:
if channel[0].isdigit():
# 19.09, 20.03 etc
return f"nixos/release-{channel}"
return channel


def guess_packagename(package: str, arch: str, is_channel: bool) -> str:
# TODO: maybe someone provides the architecture in the package name?
if package.startswith("nixpkgs.") or package.startswith("nixos."):
# we assume user knows the full package name
return f"{package}.{arch}"
elif is_channel:

if is_channel:
# we simply guess, that the user searches for a package and not a test
return f"nixpkgs.{package}.{arch}"
else:
return f"{package}.{arch}"

return f"{package}.{arch}"


def get_url(ident: str) -> str:
Expand All @@ -75,7 +54,7 @@ def fetch_data(ident: str) -> str:
resp = requests.get(url, timeout=20)
if resp.status_code == 404:
print(f"package {ident} not found at url {url}")
exit(1)
sysexit(1)
return resp.text


Expand All @@ -97,8 +76,7 @@ def parse_build_html(data: str) -> Iterator[BuildStatus]:
except ValueError:
if row.find("td").find("a")["href"].endswith("/all"):
continue
else:
raise
raise
status = status.find("img")["title"]
build_id = build.find("a").text
build_url = build.find("a")["href"]
Expand All @@ -124,27 +102,27 @@ def print_buildreport(build: BuildStatus) -> None:
if build["evals"]:
extra = "" if build["success"] else f" ({build['status']})"
print(
f"{build['icon']}{extra} {build['name']} from {str(build['timestamp']).split('T')[0]} - {build['build_url']}"
f"{build['icon']}{extra} {build['name']} from "
f"{str(build['timestamp']).split('T', maxsplit=1)[0]} - {build['build_url']}",
)
else:
print(f"{build['icon']} {build['status']}")


def main() -> None:
from docopt import docopt

args = docopt(__doc__)
channel = args["--channel"]
packages = args["PACKAGES"]
arch = args["--arch"]
only_url = args["--url"]
args = process_args()

channel = args.channel
packages: list[str] = args.PACKAGES
only_url = args.url
jobset = guess_jobset(channel)
is_channel = jobset.startswith("nixos/")
as_json = args["--json"]
as_json = args.json
all_builds = {}

for package in packages:
package_name = guess_packagename(package, arch, is_channel)
package_name = guess_packagename(package, args.arch, is_channel)
ident = f"{jobset}/{package_name}"
if only_url:
print(get_url(ident))
Expand All @@ -158,7 +136,7 @@ def main() -> None:
latest = builds[0]
print(f"Build Status for {package_name} on {channel}")
print_buildreport(latest)
if not latest["success"] and latest["evals"] and not args["--short"]:
if not latest["success"] and latest["evals"] and not args.short:
print()
print("Last Builds:")
for build in builds[1:]:
Expand Down