Coverage for obj2cli/parser.py: 91%

71 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-05 14:23 +0000

1import inspect 

2from logging import getLogger 

3 

4log = getLogger(__name__) 

5 

6 

7class Parser: 

8 def __init__(self, deftype=str): 

9 self.deftype = deftype 

10 

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 } 

32 

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 

41 

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 

58 

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 

67 

68 def parse_new(self, cls): 

69 return self.parse_fn(cls.__init__) 

70 

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