(cls: type, method_name: str, boilerplate_args: Union[Sequence[str], None])
| 192 | |
| 193 | |
| 194 | def _extract_docs(cls: type, method_name: str, boilerplate_args: Union[Sequence[str], None]) -> PatcherDoc: |
| 195 | inputs: dict[str, InputDoc] = {} |
| 196 | method = getattr(cls, method_name) |
| 197 | if boilerplate_args is None: |
| 198 | boilerplate_args = [] |
| 199 | |
| 200 | signature = inspect.signature(method) |
| 201 | for name, parameter in signature.parameters.items(): |
| 202 | if name == "self" or name in boilerplate_args: |
| 203 | continue |
| 204 | input_doc: InputDoc = {"name": name} |
| 205 | inputs[name] = input_doc |
| 206 | if isinstance(parameter.default, (str, float, int, bool)): |
| 207 | input_doc["default"] = parameter.default |
| 208 | |
| 209 | # Parse data from type hints. |
| 210 | type_hints = typing.get_type_hints(method) |
| 211 | for input_name in inputs.keys(): |
| 212 | type_hint = type_hints.get(input_name, None) |
| 213 | if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??) |
| 214 | continue |
| 215 | |
| 216 | input_data = inputs[input_name] |
| 217 | # E.g. list[str]. |
| 218 | if isinstance(type_hint, typing.GenericAlias): # pyright: ignore[reportAttributeAccessIssue] |
| 219 | input_data["generic_type"] = type_hint.__name__ |
| 220 | type_hint = typing.get_args(type_hint)[0] |
| 221 | |
| 222 | if isinstance(type_hint, typing._UnionGenericAlias): # pyright: ignore[reportAttributeAccessIssue] |
| 223 | inputs[input_name]["type"] = [t.__name__ for t in typing.get_args(type_hint)] |
| 224 | elif type_hint.__name__ == "Literal": |
| 225 | inputs[input_name]["type"] = "Literal" |
| 226 | inputs[input_name]["enum_items"] = list(typing.get_args(type_hint)) |
| 227 | else: |
| 228 | inputs[input_name]["type"] = type_hint.__name__ |
| 229 | |
| 230 | # Parse the docstring. |
| 231 | description = "" |
| 232 | # `getdoc` instead of `__doc__` for sane indentation. |
| 233 | doc = inspect.getdoc(method) |
| 234 | |
| 235 | def is_valid_param_name(param_name: str) -> bool: |
| 236 | assert ( |
| 237 | param_name in inputs |
| 238 | ), f"Unexpected param name '{param_name}' in {cls.__name__} docstring (missing from signature)." |
| 239 | return True |
| 240 | |
| 241 | def is_valid_filter_glob(filter_glob: str) -> bool: |
| 242 | # e.g. '*.ifc;*.ifczip;*.ifcxml' |
| 243 | if len(filter_glob) < 3: |
| 244 | return False |
| 245 | for pattern in filter_glob.split(";"): |
| 246 | if not re.fullmatch(r"\*\.\w+", pattern): |
| 247 | return False |
| 248 | return True |
| 249 | |
| 250 | if doc is None: |
| 251 | doc_description = "" |
no test coverage detected