Skip to content

Commit

Permalink
Merge pull request #170 from YunoHost/typing
Browse files Browse the repository at this point in the history
  • Loading branch information
Salamandar authored Nov 18, 2024
2 parents 675a1fb + c3a304e commit 0bd4630
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 246 deletions.
91 changes: 50 additions & 41 deletions lib/lib_package_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import os
import time
import urllib
import urllib.request
from typing import Any, Callable, Generator, TypeVar

import jsonschema

from lib.print import _print
Expand All @@ -25,10 +27,13 @@ class c:


class TestReport:
def __init__(self, message):
style: str
test_name: str

def __init__(self, message: str) -> None:
self.message = message

def display(self, prefix=""):
def display(self, prefix: str = "") -> None:
_print(prefix + self.style % self.message)


Expand All @@ -52,38 +57,38 @@ class Critical(TestReport):
style = c.FAIL + " ✘✘✘ %s" + c.END


def report_warning_not_reliable(str):
_print(c.MAYBE_FAIL + "?", str, c.END)
def report_warning_not_reliable(message: str) -> None:
_print(c.MAYBE_FAIL + "?", message, c.END)


def print_happy(str):
_print(c.OKGREEN + " ☺ ", str, "♥")
def print_happy(message: str) -> None:
_print(c.OKGREEN + " ☺ ", message, "♥")


def urlopen(url):
def urlopen(url: str) -> tuple[int, str]:
try:
conn = urllib.request.urlopen(url)
except urllib.error.HTTPError as e:
return {"content": "", "code": e.code}
return e.code, ""
except urllib.error.URLError as e:
_print("Could not fetch %s : %s" % (url, e))
return {"content": "", "code": 0}
return {"content": conn.read().decode("UTF8"), "code": 200}
return 0, ""
return 200, conn.read().decode("UTF8")


def file_exists(file_path):
def file_exists(file_path: str) -> bool:
return os.path.isfile(file_path) and os.stat(file_path).st_size > 0


def cache_file(cachefile: str, ttl_s: int):
def cache_is_fresh():
def cache_file(cachefile: str, ttl_s: int) -> Callable[[Callable[..., str]], Callable[..., str]]:
def cache_is_fresh() -> bool:
return (
os.path.exists(cachefile)
and time.time() - os.path.getmtime(cachefile) < ttl_s
)

def decorator(function):
def wrapper(*args, **kwargs):
def decorator(function: Callable[..., str]) -> Callable[..., str]:
def wrapper(*args: Any, **kwargs: Any) -> str:
if not cache_is_fresh():
with open(cachefile, "w+") as outfile:
outfile.write(function(*args, **kwargs))
Expand All @@ -95,47 +100,47 @@ def wrapper(*args, **kwargs):


@cache_file(".spdx_licenses", 3600)
def spdx_licenses():
return urlopen("https://spdx.org/licenses/")["content"]
def spdx_licenses() -> str:
return urlopen("https://spdx.org/licenses/")[1]


@cache_file(".manifest.v2.schema.json", 3600)
def manifest_v2_schema():
return urlopen(
"https://raw.githubusercontent.com/YunoHost/apps/master/schemas/manifest.v2.schema.json"
)["content"]
def manifest_v2_schema() -> str:
url = "https://raw.githubusercontent.com/YunoHost/apps/master/schemas/manifest.v2.schema.json"
return urlopen(url)[1]


@cache_file(".tests.v1.schema.json", 3600)
def tests_v1_schema():
return urlopen(
"https://raw.githubusercontent.com/YunoHost/apps/master/schemas/tests.v1.schema.json"
)["content"]
def tests_v1_schema() -> str:
url = "https://raw.githubusercontent.com/YunoHost/apps/master/schemas/tests.v1.schema.json"
return urlopen(url)[1]


@cache_file(".config_panel.v1.schema.json", 3600)
def config_panel_v1_schema():
return urlopen(
"https://raw.githubusercontent.com/YunoHost/apps/master/schemas/config_panel.v1.schema.json"
)["content"]
def config_panel_v1_schema() -> str:
url = "https://raw.githubusercontent.com/YunoHost/apps/master/schemas/config_panel.v1.schema.json"
return urlopen(url)[1]


def validate_schema(name: str, schema, data):
def validate_schema(name: str, schema: dict[str, Any], data: dict[str, Any]) -> Generator[Info, None, None]:
v = jsonschema.Draft7Validator(schema)

for error in v.iter_errors(data):
try:
error_path = " > ".join(error.path)
except:
except TypeError:
error_path = str(error.path)

yield Info(
f"Error validating {name} using schema: in key {error_path}\n {error.message}"
)


tests = {}
tests_reports = {
TestResult = Generator[TestReport, None, None]
TestFn = Callable[[Any], TestResult]

tests: dict[str, list[tuple[TestFn, Any]]] = {}
tests_reports: dict[str, list[Any]] = {
"success": [],
"info": [],
"warning": [],
Expand All @@ -144,8 +149,9 @@ def validate_schema(name: str, schema, data):
}


def test(**kwargs):
def decorator(f):

def test(**kwargs: Any) -> Callable[[TestFn], TestFn]:
def decorator(f: TestFn) -> TestFn:
clsname = f.__qualname__.split(".")[0]
if clsname not in tests:
tests[clsname] = []
Expand All @@ -155,8 +161,11 @@ def decorator(f):
return decorator


class TestSuite:
def run_tests(self):
class TestSuite():
name: str
test_suite_name: str

def run_tests(self) -> None:

reports = []

Expand All @@ -174,7 +183,7 @@ def run_tests(self):

# Display part

def report_type(report):
def report_type(report: TestReport) -> str:
return report.__class__.__name__.lower()

if any(report_type(r) in ["warning", "error", "critical"] for r in reports):
Expand All @@ -198,11 +207,11 @@ def report_type(report):
for report in reports:
tests_reports[report_type(report)].append((report.test_name, report))

def run_single_test(self, test):
def run_single_test(self, test: TestFn) -> None:

reports = list(test(self))

def report_type(report):
def report_type(report: TestReport) -> str:
return report.__class__.__name__.lower()

for report in reports:
Expand Down
2 changes: 2 additions & 0 deletions lib/nginxparser/nginxparser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env python3
# type: ignore
"""Very low-level nginx config parser based on pyparsing."""

# Taken from https://github.com/certbot/certbot (Apache licensed)
Expand Down
6 changes: 4 additions & 2 deletions lib/print.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
#!/usr/bin/env python3

from typing import Any

output = "plain"


def _print(*args, **kwargs):
def _print(*args: Any, **kwargs: Any) -> None:
if not is_json_output():
print(*args, **kwargs)


def set_output_json():
def set_output_json() -> None:
global output
output = "json"

Expand Down
1 change: 0 additions & 1 deletion package_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from lib.lib_package_linter import c
from lib.print import _print, set_output_json

from tests.test_app import App


Expand Down
Loading

0 comments on commit 0bd4630

Please sign in to comment.