diff --git a/comtypes/test/test_typeannotator.py b/comtypes/test/test_typeannotator.py index 8b7578fce..f5e7cdf17 100644 --- a/comtypes/test/test_typeannotator.py +++ b/comtypes/test/test_typeannotator.py @@ -29,16 +29,20 @@ def _create_typedesc_disp_interface(self) -> typedesc.DispInterface: itf = typedesc.DispInterface("IDispDerived", idispatch_type, guid, [], None) ham = typedesc.DispProperty(0, "ham", BSTR_type, ["readonly"], None) bacon = typedesc.DispMethod( - 472, 1, "bacon", VARIANT_BOOL_type, ["hidden"], None + 472, 1, "bacon", VARIANT_BOOL_type, ["hidden"], "bacon's \\ help" ) bacon.add_argument(VARIANT_type, "and", ["in"], None) bacon.add_argument(VARIANT_type, "foo", ["in"], None) - get_spam = typedesc.DispMethod(6, 2, "spam", VARIANT_type, ["propget"], None) + get_spam = typedesc.DispMethod( + 6, 2, "spam", VARIANT_type, ["propget"], "spam help" + ) get_spam.add_argument(VARIANT_type, "arg1", ["in", "optional"], None) - put_spam = typedesc.DispMethod(6, 4, "spam", void_type, ["propput"], None) + put_spam = typedesc.DispMethod( + 6, 4, "spam", void_type, ["propput"], "spam setter" + ) put_spam.add_argument(VARIANT_type, "arg1", ["in", "optional"], None) put_spam.add_argument(VARIANT_type, "arg2", ["in"], None) - except_ = typedesc.DispProperty(1, "except", BSTR_type, ["readonly"], None) + except_ = typedesc.DispProperty(1, "except", BSTR_type, ["readonly"], "help") raise_ = typedesc.DispMethod( 474, 1, "raise", VARIANT_BOOL_type, ["hidden"], None ) @@ -70,11 +74,11 @@ def test_disp_interface(self): " @property # dispprop\n" " def ham(self) -> hints.Incomplete: ...\n" " pass # @property # dispprop\n" - " pass # avoid using a keyword for def except(self) -> hints.Incomplete: ...\n" # noqa - " def bacon(self, *args: hints.Any, **kwargs: hints.Any) -> hints.Incomplete: ...\n" # noqa - " def _get_spam(self, arg1: hints.Incomplete = ..., /) -> hints.Incomplete: ...\n" # noqa + ' pass # avoid using a keyword for def except(self) -> hints.Incomplete: """help"""; ...\n' # noqa + ' def bacon(self, *args: hints.Any, **kwargs: hints.Any) -> hints.Incomplete: """bacon\'s \\\\ help"""; ...\n' # noqa + ' def _get_spam(self, arg1: hints.Incomplete = ..., /) -> hints.Incomplete: """spam help"""; ...\n' # noqa " def _set_spam(self, arg1: hints.Incomplete = ..., /, *args: hints.Unpack[tuple[hints.Incomplete]]) -> hints.Incomplete: ...\n" # noqa - " spam = hints.named_property('spam', _get_spam, _set_spam)\n" + ' spam = hints.named_property(\'spam\', _get_spam, _set_spam); """spam help"""\n' " pass # avoid using a keyword for def raise(self, foo: hints.Incomplete, bar: hints.Incomplete = ..., /) -> hints.Incomplete: ...\n" # noqa " def _get_def(self, arg1: hints.Incomplete = ..., /) -> hints.Incomplete: ...\n" # noqa " def _set_def(self, arg1: hints.Incomplete = ..., /, *args: hints.Unpack[tuple[hints.Incomplete]]) -> hints.Incomplete: ...\n" # noqa @@ -105,13 +109,15 @@ def _create_typedesc_com_interface(self) -> typedesc.ComInterface: 2, 1610678269, "spam", HRESULT_type, ["propget"], None ) get_ham = typedesc.ComMethod( - 2, 1610678270, "ham", HRESULT_type, ["propget"], None + 2, 1610678270, "ham", HRESULT_type, ["propget"], "ham help" ) put_ham = typedesc.ComMethod( - 4, 1610678270, "ham", HRESULT_type, ["propput"], None + 4, 1610678270, "ham", HRESULT_type, ["propput"], "ham setter" ) put_ham.add_argument(VARIANT_type, "arg1", ["in"], None) - bacon = typedesc.ComMethod(1, 1610678271, "bacon", HRESULT_type, [], None) + bacon = typedesc.ComMethod( + 1, 1610678271, "bacon", HRESULT_type, [], "bacon's \\ help" + ) bacon.add_argument(VARIANT_type, "foo", ["in"], None) bacon.add_argument(VARIANT_type, "or", ["in"], None) global_ = typedesc.ComMethod( @@ -138,10 +144,10 @@ def test_com_interface(self): expected = ( " def _get_spam(self) -> hints.Hresult: ...\n" " spam = hints.normal_property(_get_spam)\n" - " def _get_ham(self) -> hints.Hresult: ...\n" + ' def _get_ham(self) -> hints.Hresult: """ham help"""; ...\n' " def _set_ham(self, arg1: hints.Incomplete) -> hints.Hresult: ...\n" - " ham = hints.normal_property(_get_ham, _set_ham)\n" - " def bacon(self, *args: hints.Any, **kwargs: hints.Any) -> hints.Hresult: ...\n" # noqa + ' ham = hints.normal_property(_get_ham, _set_ham); """ham help"""\n' + ' def bacon(self, *args: hints.Any, **kwargs: hints.Any) -> hints.Hresult: """bacon\'s \\\\ help"""; ...\n' # noqa " def _get_global(self) -> hints.Hresult: ...\n" " pass # avoid using a keyword for global = hints.normal_property(_get_global)\n" # noqa " def _get_class(self, arg1: hints.Incomplete) -> hints.Hresult: ...\n" diff --git a/comtypes/tools/codegenerator/codegenerator.py b/comtypes/tools/codegenerator/codegenerator.py index da389c7cd..53111d496 100644 --- a/comtypes/tools/codegenerator/codegenerator.py +++ b/comtypes/tools/codegenerator/codegenerator.py @@ -15,7 +15,6 @@ from comtypes import typeinfo from comtypes.tools import tlbparser, typedesc from comtypes.tools.codegenerator import heads, namespaces, packing -from comtypes.tools.codegenerator.comments import ComInterfaceBodyImplCommentWriter from comtypes.tools.codegenerator.helpers import ( ASSUME_STRINGS, ComMethodGenerator, @@ -662,8 +661,6 @@ def ComInterfaceBody(self, body: typedesc.ComInterfaceBody) -> None: raise TypeError("what's this?") print("]", file=ofi) - print(file=self.stream) - ComInterfaceBodyImplCommentWriter(self.stream).write(body) def DispInterface(self, itf: typedesc.DispInterface) -> None: self.generate(itf.get_head()) diff --git a/comtypes/tools/codegenerator/comments.py b/comtypes/tools/codegenerator/comments.py deleted file mode 100644 index 57f5b4a31..000000000 --- a/comtypes/tools/codegenerator/comments.py +++ /dev/null @@ -1,79 +0,0 @@ -import io - -from comtypes.tools import typedesc - - -class ComInterfaceBodyImplCommentWriter: - def __init__(self, stream: io.StringIO): - self.stream = stream - - def write(self, body: typedesc.ComInterfaceBody) -> None: - print( - "################################################################", - file=self.stream, - ) - print(f"# code template for {body.itf.name} implementation", file=self.stream) - print(f"# class {body.itf.name}_Impl(object):", file=self.stream) - - methods = {} - for m in body.itf.members: - if isinstance(m, typedesc.ComMethod): - # m.arguments is a sequence of tuples: - # (argtype, argname, idlflags, docstring) - # Some typelibs have unnamed method parameters! - inargs = [a[1] or "" for a in m.arguments if "out" not in a[2]] - outargs = [a[1] or "" for a in m.arguments if "out" in a[2]] - if "propget" in m.idlflags: - methods.setdefault(m.name, [0, inargs, outargs, m.doc])[0] |= 1 - elif "propput" in m.idlflags: - methods.setdefault(m.name, [0, inargs[:-1], inargs[-1:], m.doc])[ - 0 - ] |= 2 - else: - methods[m.name] = [0, inargs, outargs, m.doc] - - for name, (typ, inargs, outargs, doc) in methods.items(): - if typ == 0: # method - print( - f"# def {name}({', '.join(['self'] + inargs)}):", - file=self.stream, - ) - print(f"# {(doc or '-no docstring-')!r}", file=self.stream) - print(f"# #return {', '.join(outargs)}", file=self.stream) - elif typ == 1: # propget - print("# @property", file=self.stream) - print( - f"# def {name}({', '.join(['self'] + inargs)}):", - file=self.stream, - ) - print(f"# {(doc or '-no docstring-')!r}", file=self.stream) - print(f"# #return {', '.join(outargs)}", file=self.stream) - elif typ == 2: # propput - print( - f"# def _set({', '.join(['self'] + inargs + outargs)}):", - file=self.stream, - ) - print(f"# {(doc or '-no docstring-')!r}", file=self.stream) - print( - f"# {name} = property(fset = _set, doc = _set.__doc__)", - file=self.stream, - ) - elif typ == 3: # propget + propput - print( - f"# def _get({', '.join(['self'] + inargs)}):", - file=self.stream, - ) - print(f"# {(doc or '-no docstring-')!r}", file=self.stream) - print(f"# #return {', '.join(outargs)}", file=self.stream) - print( - f"# def _set({', '.join(['self'] + inargs + outargs)}):", - file=self.stream, - ) - print(f"# {(doc or '-no docstring-')!r}", file=self.stream) - print( - f"# {name} = property(_get, _set, doc = _set.__doc__)", - file=self.stream, - ) - else: - raise RuntimeError("BUG") - print("#", file=self.stream) diff --git a/comtypes/tools/codegenerator/typeannotator.py b/comtypes/tools/codegenerator/typeannotator.py index 20b37fd67..a9e7ca173 100644 --- a/comtypes/tools/codegenerator/typeannotator.py +++ b/comtypes/tools/codegenerator/typeannotator.py @@ -10,6 +10,7 @@ class _MethodTypeDesc(Protocol): arguments: list[tuple[Any, str, list[str], Optional[Any]]] idlflags: list[str] name: str + doc: Optional[str] _T_MTD = TypeVar("_T_MTD", bound=_MethodTypeDesc) @@ -19,6 +20,9 @@ class _MethodAnnotator(abc.ABC, Generic[_T_MTD]): def __init__(self, method: _T_MTD) -> None: self.method = method + def is_put_or_putref(self) -> bool: + return "propput" in self.method.idlflags or "propputref" in self.method.idlflags + @property def inarg_specs(self) -> Sequence[tuple[Any, str, Optional[Any]]]: index = 0 @@ -99,7 +103,11 @@ def _patch_dunder(self, name: str) -> None: self._define_member(f"__iter__ = hints.to_dunder_iter({name})") def _define_named_prop( - self, mem_name: str, getter: Optional[str] = None, setter: Optional[str] = None + self, + mem_name: str, + getter: Optional[str] = None, + setter: Optional[str] = None, + doc: Optional[str] = None, ) -> None: if getter and setter: content = ( @@ -113,10 +121,16 @@ def _define_named_prop( return if keyword.iskeyword(mem_name): content = f"pass # avoid using a keyword for {content}" + if doc is not None: + content = f"{content}; {_to_docstr(doc)}" self._define_member(content) def _define_normal_prop( - self, mem_name: str, getter: Optional[str] = None, setter: Optional[str] = None + self, + mem_name: str, + getter: Optional[str] = None, + setter: Optional[str] = None, + doc: Optional[str] = None, ) -> None: if getter and setter: content = f"{mem_name} = hints.normal_property({getter}, {setter})" @@ -128,6 +142,8 @@ def _define_normal_prop( return if keyword.iskeyword(mem_name): content = f"pass # avoid using a keyword for {content}" + if doc is not None: + content = f"{content}; {_to_docstr(doc)}" self._define_member(content) def _define_member(self, content: str) -> None: @@ -140,9 +156,9 @@ def _gen_prop_get(self, name: str, fget: _T_MTD) -> None: getter_anno = self.to_method_annotator(fget) self._define_member(getter_anno.getvalue(f"_get_{name}")) if getter_anno.inarg_specs: - self._define_named_prop(name, f"_get_{name}") + self._define_named_prop(name, f"_get_{name}", doc=fget.doc) else: - self._define_normal_prop(name, f"_get_{name}") + self._define_normal_prop(name, f"_get_{name}", doc=fget.doc) def _gen_prop_get_put(self, name: str, fget: _T_MTD, fput: _T_MTD) -> None: getter_anno = self.to_method_annotator(fget) @@ -150,9 +166,9 @@ def _gen_prop_get_put(self, name: str, fget: _T_MTD, fput: _T_MTD) -> None: self._define_member(getter_anno.getvalue(f"_get_{name}")) self._define_member(setter_anno.getvalue(f"_set_{name}")) if getter_anno.inarg_specs: - self._define_named_prop(name, f"_get_{name}", f"_set_{name}") + self._define_named_prop(name, f"_get_{name}", f"_set_{name}", doc=fget.doc) else: - self._define_normal_prop(name, f"_get_{name}", f"_set_{name}") + self._define_normal_prop(name, f"_get_{name}", f"_set_{name}", doc=fget.doc) def _gen_prop_get_putref(self, name: str, fget: _T_MTD, fputref: _T_MTD) -> None: getter_anno = self.to_method_annotator(fget) @@ -160,9 +176,13 @@ def _gen_prop_get_putref(self, name: str, fget: _T_MTD, fputref: _T_MTD) -> None self._define_member(getter_anno.getvalue(f"_get_{name}")) self._define_member(setter_anno.getvalue(f"_setref_{name}")) if getter_anno.inarg_specs: - self._define_named_prop(name, f"_get_{name}", f"_setref_{name}") + self._define_named_prop( + name, f"_get_{name}", f"_setref_{name}", doc=fget.doc + ) else: - self._define_normal_prop(name, f"_get_{name}", f"_setref_{name}") + self._define_normal_prop( + name, f"_get_{name}", f"_setref_{name}", doc=fget.doc + ) def _gen_prop_get_put_putref( self, name: str, fget: _T_MTD, fput: _T_MTD, fputref: _T_MTD @@ -175,9 +195,9 @@ def _gen_prop_get_put_putref( self._define_member(putref_anno.getvalue(f"_setref_{name}")) setter = f"hints.put_or_putref(_set_{name}, _setref_{name})" if getter_anno.inarg_specs: - self._define_named_prop(name, f"_get_{name}", setter) + self._define_named_prop(name, f"_get_{name}", setter, doc=fget.doc) else: - self._define_normal_prop(name, f"_get_{name}", setter) + self._define_normal_prop(name, f"_get_{name}", setter, doc=fget.doc) def _gen_prop_put(self, name: str, fput: _T_MTD) -> None: setter_anno = self.to_method_annotator(fput) @@ -234,6 +254,10 @@ def _generate_trailing_params(specs: Sequence[tuple[Any, str, Optional[Any]]]) - return f"*args: hints.Unpack[{params}]" +def _to_docstr(doc: str) -> str: + return '"""' + doc.replace("\\", "\\\\").replace('"', '\\"') + '"""' + + class ComMethodAnnotator(_MethodAnnotator[typedesc.ComMethod]): def _iter_outarg_specs(self) -> Iterator[tuple[Any, str]]: for typ, name, flags, _ in self.method.arguments: @@ -270,7 +294,10 @@ def getvalue(self, name: str) -> str: else: out = "tuple[" + ", ".join(outargs) + "]" in_ = ("self, " + ", ".join(inargs)) if inargs else "self" - content = f"def {name}({in_}) -> {out}: ..." + if self.method.doc is not None and not self.is_put_or_putref(): + content = f"def {name}({in_}) -> {out}: {_to_docstr(self.method.doc)}; ..." + else: + content = f"def {name}({in_}) -> {out}: ..." if keyword.iskeyword(name): content = f"pass # avoid using a keyword for {content}" return content @@ -323,10 +350,11 @@ def getvalue(self, name: str) -> str: if inargs: inargs.append("/") out = _to_outtype(self.method.returns) - if inargs: - content = f"def {name}(self, {', '.join(inargs)}) -> {out}: ..." + in_ = ("self, " + ", ".join(inargs)) if inargs else "self" + if self.method.doc is not None and not self.is_put_or_putref(): + content = f"def {name}({in_}) -> {out}: {_to_docstr(self.method.doc)}; ..." else: - content = f"def {name}(self) -> {out}: ..." + content = f"def {name}({in_}) -> {out}: ..." if keyword.iskeyword(name): content = f"pass # avoid using a keyword for {content}" return content @@ -359,7 +387,10 @@ def generate(self) -> str: for mem in props: out = _to_outtype(mem.typ) decorator = "@property # dispprop" - content = f"def {mem.name}(self) -> {out}: ..." + if mem.doc is not None: + content = f"def {mem.name}(self) -> {out}: {_to_docstr(mem.doc)}; ..." + else: + content = f"def {mem.name}(self) -> {out}: ..." if keyword.iskeyword(mem.name): decorator = f"pass # {decorator}" content = f"pass # avoid using a keyword for {content}" diff --git a/comtypes/tools/typedesc.py b/comtypes/tools/typedesc.py index 0b0bc49d1..755b6e02d 100644 --- a/comtypes/tools/typedesc.py +++ b/comtypes/tools/typedesc.py @@ -121,7 +121,7 @@ def add_argument( class DispProperty: # dispatchable COM property, parsed from typelib def __init__( - self, dispid: int, name: str, typ: Any, idlflags: list[str], doc: Optional[Any] + self, dispid: int, name: str, typ: Any, idlflags: list[str], doc: Optional[str] ) -> None: self.dispid = dispid self.name = name