Coverage for obj2cli/parser.py: 91%
71 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-05 14:23 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-05 14:23 +0000
1import inspect
2from logging import getLogger
4log = getLogger(__name__)
7class Parser:
8 def __init__(self, deftype=str):
9 self.deftype = deftype
11 def onearg(self, name, param, p):
12 log.debug("onearg param: %s", param)
13 if param.default != inspect._empty and param.default is not None:
14 dflt = param.default
15 else:
16 dflt = None
17 thint = param.annotation
18 if thint == inspect._empty:
19 if dflt is not None: 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true
20 typ = type(dflt)
21 else:
22 log.debug("default type %s", self.deftype)
23 typ = self.deftype
24 else:
25 typ = thint
26 return {
27 "name": name,
28 "default": dflt,
29 "type": typ,
30 "kind": param.kind,
31 }
33 def inspect_flags(self, v):
34 res = []
35 for f in filter(
36 lambda f: f.startswith("is") and callable(getattr(inspect, f)), dir(inspect)
37 ):
38 if getattr(inspect, f)(v):
39 res.append(f[2:])
40 return res
42 def parse_fn(self, fn):
43 sig = inspect.signature(fn)
44 log.debug("signature %s", sig)
45 res = {
46 "flags": self.inspect_flags(fn),
47 "args": [],
48 "fn": fn,
49 }
50 fndoc = inspect.getdoc(fn)
51 if fndoc is not None:
52 res["doc"] = fndoc
53 for name, param in sig.parameters.items():
54 res["args"].append(self.onearg(name, param, res))
55 if sig.return_annotation != inspect._empty:
56 res["return"] = sig.return_annotation
57 return res
59 def fn_args(self, data):
60 if "staticmethod" in data.get("flags", []) or "classmethod" in data.get(
61 "flags", []
62 ):
63 args = data.get("args", [])
64 else:
65 args = data.get("args", [])[1:]
66 return args
68 def parse_new(self, cls):
69 return self.parse_fn(cls.__init__)
71 def parse_cls(self, cls):
72 if not inspect.isclass(cls):
73 cls = cls.__class__
74 res = {
75 "__init__": self.parse_new(cls),
76 "__classmeta__": {
77 "class": cls,
78 "name": cls.__name__,
79 "qualname": cls.__qualname__,
80 "flags": self.inspect_flags(cls),
81 },
82 }
83 clsdoc = inspect.getdoc(cls)
84 if clsdoc is not None: 84 ↛ 85line 84 didn't jump to line 85 because the condition on line 84 was never true
85 res["doc"] = clsdoc
86 for m, fn in inspect.getmembers(cls, lambda f: callable(f)):
87 if m.startswith("_"):
88 continue
89 try:
90 res[m] = self.parse_fn(fn)
91 except ValueError as e:
92 log.error("cannot inspect %s: %s", m, e)
93 if "class" in res.get("__classmeta__"): 93 ↛ 98line 93 didn't jump to line 98 because the condition on line 93 was always true
94 # class
95 kls = cls
96 else:
97 # object
98 kls = cls.__class__
99 for k, v in kls.__dict__.items():
100 if k not in res:
101 continue
102 if isinstance(v, staticmethod):
103 res[k]["flags"].append("staticmethod")
104 elif isinstance(v, classmethod):
105 res[k]["flags"].append("classmethod")
106 elif isinstance(v, property): 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true
107 res[k]["flags"].append("property")
108 return res