136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
# Import python libs
|
|
import inspect
|
|
|
|
# Import pop libs
|
|
import pop.exc
|
|
import pop.hub
|
|
import pop.loader
|
|
from typing import Any, Callable, Dict, Iterable, List
|
|
|
|
|
|
def contract(
|
|
hub: "pop.hub.Hub", # pylint: disable=unused-argument
|
|
raws: Iterable["pop.loader.LoadedMod"],
|
|
mod: "pop.loader.LoadedMod",
|
|
):
|
|
"""
|
|
Verify module level contract - functions only
|
|
:param hub: The redistributed pop central hub
|
|
:param raws: A list of loaded modules with contracts
|
|
:param mod: A loader module
|
|
"""
|
|
sig_errs = []
|
|
sig_miss = []
|
|
mname = mod.__name__
|
|
for raw in raws:
|
|
if isinstance(raw, pop.loader.LoadError):
|
|
sig_errs.append(str(raw))
|
|
continue
|
|
else:
|
|
for fun in raw._funcs:
|
|
if fun.startswith("sig_"):
|
|
tfun = fun[4:]
|
|
if tfun not in mod._funcs:
|
|
sig_miss.append(tfun)
|
|
continue
|
|
sig_errs.extend(sig(mod._funcs[tfun].func, raw._funcs[fun].func))
|
|
if sig_errs or sig_miss:
|
|
msg = ""
|
|
if sig_errs:
|
|
msg += f"Signature Errors in {mname}:\n"
|
|
for err in sig_errs:
|
|
msg += f"{err}\n"
|
|
if sig_miss:
|
|
msg += f"Signature Functions Missing in {mname}:\n"
|
|
for err in sig_miss:
|
|
msg += f"{err}\n"
|
|
msg = msg.strip()
|
|
raise pop.exc.ContractSigException(msg)
|
|
|
|
|
|
def sig_map(ver: Callable) -> Dict[str, Any]:
|
|
"""
|
|
Generates the map dict for the signature verification
|
|
"""
|
|
vsig = inspect.signature(ver)
|
|
vparams = list(vsig.parameters.values())
|
|
vdat = {"args": [], "v_pos": -1, "kw": [], "kwargs": False, "ann": {}}
|
|
for ind in range(len(vparams)):
|
|
param = vparams[ind]
|
|
val = param.kind.value
|
|
name = param.name
|
|
if val == 0 or val == 1:
|
|
vdat["args"].append(name)
|
|
if param.default != inspect._empty: # Is a KW, can be inside of **kwargs
|
|
vdat["kw"].append(name)
|
|
elif val == 2:
|
|
vdat["v_pos"] = ind
|
|
elif val == 3:
|
|
vdat["kw"].append(name)
|
|
elif val == 4:
|
|
vdat["kwargs"] = ind
|
|
if param.annotation != inspect._empty:
|
|
vdat["ann"][name] = param.annotation
|
|
return vdat
|
|
|
|
|
|
def sig(func: Callable, ver: Callable) -> List[str]:
|
|
"""
|
|
Takes 2 functions, the first function is verified to have a parameter signature
|
|
compatible with the second function
|
|
"""
|
|
errors = []
|
|
fsig = inspect.signature(func)
|
|
fparams = list(fsig.parameters.values())
|
|
vdat = sig_map(ver)
|
|
arg_len = len(vdat["args"])
|
|
v_pos = False
|
|
for ind in range(len(fparams)):
|
|
param = fparams[ind]
|
|
val = param.kind.value
|
|
name = param.name
|
|
has_default = param.default != inspect._empty
|
|
ann = param.annotation
|
|
vann = vdat["ann"].get(name, inspect._empty)
|
|
if vann != ann:
|
|
errors.append(f'Parameter, "{name}" is type "{str(ann)}" not "{str(vann)}"')
|
|
if val == 2:
|
|
v_pos = True
|
|
if val == 0 or val == 1:
|
|
if ind >= arg_len: # Past available positional args
|
|
if not vdat["v_pos"] == -1: # Has a *args
|
|
if ind >= vdat["v_pos"] and v_pos:
|
|
# Invalid unless it is a kw
|
|
if not name in vdat["kw"]:
|
|
# Is a kw
|
|
errors.append(f'Parameter "{name}" is invalid')
|
|
if vdat["kwargs"] is False:
|
|
errors.append(f'Parameter "{name}" not defined as kw only')
|
|
continue
|
|
elif vdat["kwargs"] is not False and not has_default:
|
|
errors.append(
|
|
f'Parameter "{name}" is past available positional params'
|
|
)
|
|
elif vdat["kwargs"] is False:
|
|
errors.append(
|
|
f'Parameter "{name}" is past available positional params'
|
|
)
|
|
else:
|
|
v_param = vdat["args"][ind]
|
|
if v_param != name:
|
|
errors.append(
|
|
f'Parameter "{name}" does not have the correct name: {v_param}'
|
|
)
|
|
if val == 2:
|
|
if ind < vdat["v_pos"]:
|
|
errors.append(
|
|
f'Parameter "{name}" is not in the correct position for *args'
|
|
)
|
|
if val == 3:
|
|
if name not in vdat["kw"] and not vdat["kwargs"]:
|
|
errors.append(f'Parameter "{name}" is not available as a kwarg')
|
|
if val == 4:
|
|
if vdat["kwargs"] is False:
|
|
errors.append(f"Kwargs are not permitted as a parameter")
|
|
return errors
|