-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Make it easier to create python stub files #3268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -523,6 +523,18 @@ def __getPythonObjectAndPath(self, parentPath, overloads): | |
|
|
||
| (obj, pypath, jumped) = self.__getPythonObjectByPath(pypath) | ||
|
|
||
| if jumped and overloads[0].isFunction(): | ||
| # We found a module-level function in our module through a permissive | ||
| # search (jumped=True). Do some further vetting. | ||
| if obj and hasattr(obj, "__module__") and \ | ||
| not parentPath[-1].name.startswith(obj.__module__.split(".")[1]): | ||
| # The doxygen object clearly indicates it's from another module. | ||
| obj = None | ||
| elif type(obj).__module__ != 'Boost.Python': | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the module name changed with |
||
| # The function we found is not a boost function, so it can't correspond | ||
| # to our doxygen object | ||
| obj = None | ||
|
|
||
| # check for the property by either possible name (since there | ||
| # are two possible naming conventions for boolean properties) | ||
| ppypath = ppypath1 | ||
|
|
@@ -687,49 +699,89 @@ def __getFullDoc(self, pyname, pyobj, doxy): | |
| # make the doxy element static if it is tagged as such | ||
| if ATTR_STATIC_METHOD in doxy.doc['tags']: | ||
| doxy.static = 'yes' | ||
|
|
||
| lines = self.__getShortDescription(pyname, pyobj, doxy) | ||
| if doxy.isFunction() and type(pyobj) != property: | ||
| lines += self.__getSignatureDescription(pyname, pyobj, doxy) | ||
| lines.append('') | ||
| description = self.__getSignatureDescription(pyname, pyobj, doxy) | ||
| if description is not None: | ||
| lines += description | ||
| lines.append('') | ||
| lines += self.__getDocumentation(pyname, pyobj, doxy) | ||
| lines.append('') | ||
| return lines | ||
|
|
||
| def __getOutputFormat(self, pypath, pyobj, overloads): | ||
| """Return the line that installs the docstring into the namespace.""" | ||
| @classmethod | ||
| def __stripBoostSig(cls, doc): | ||
| def looksLikeBoostSig(l): | ||
| return bool(l and (l[0].isalnum() or l[0] == "_") and ' -> ' in l) | ||
|
|
||
| lines = doc.strip().splitlines() | ||
| if len(lines) and looksLikeBoostSig(lines[0]): | ||
| # boost signature has been prepended. that means if there is | ||
| # an existing description provided in the C++ wrapper it will be | ||
| # indented. strip the signature and dedent the description. | ||
|
Comment on lines
+720
to
+722
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think an example before / after docstring (either real or made up) would greatly help understanding here. I had to re-read this a few times before I thought I understood what's going on, as my initial assumption about what these would look like was wrong. |
||
| found = False | ||
| newLines = [] | ||
| for line in lines: | ||
| if not found and line.startswith(' '): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we also add a Would also (help) prevent us from returning a result that's nothing but whitespace-only lines. (We'd also need to check if found=False after the loop). |
||
| found = True | ||
| if found: | ||
| newLines.append(line) | ||
| return textwrap.dedent('\n'.join(newLines)) | ||
|
Comment on lines
+725
to
+730
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, basically we're finding the first indented line, grabbing it and all following, and then dedenting. I'm assuming you haven't encountered the situation where unindented text follows indented, ie: ...but we should consider what to do if it happens. Current behavior would be to return: ...which is likely not desired. Simplest solution is to just error if we're fairly sure this should never happen, and/or can't make any guesses at what the best behavior would be if it does. Other options would be to return or ...all of which feel more likely to be correct.
Comment on lines
+725
to
+730
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we return None if |
||
| else: | ||
| # None indicates that the existing __doc__ attr should be left as-is | ||
| return None | ||
|
|
||
| def __getDocstring(self, pypath, pyobj, overloads): | ||
| """Return the docstring.""" | ||
|
|
||
| # is there an existing python docstring? we don't want to overwrite | ||
| # this because it may be custom authored in the C++ wrap files. | ||
| # However, we always override the module doc string for now... | ||
| docString = '' | ||
|
|
||
| # boost auto-generates function signatures that can be useful in some | ||
| # contexts (such as generating pyi type stubs) because they more | ||
| # accurately reflect changes made to the API within the C++ wrap files, | ||
| # but for our docstrings we strip them out and replace them with our | ||
| # own function signatures. After stripping out the boost signatures, | ||
| # if there is a custom authored docstring in the C++ wrap files we honor | ||
| # it. | ||
| if hasattr(pyobj, '__doc__') and pyobj.__doc__ is not None: | ||
| doc = pyobj.__doc__.strip() | ||
| if len(doc) > 0 and not doc.startswith("C++ signature:") \ | ||
| and not overloads[0].isModule(): | ||
| Debug("Docstring exists for %s - skipping" % pypath) | ||
| return None | ||
| if len(doc) > 0 and not overloads[0].isModule(): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be nice to add the old |
||
| newDoc = self.__stripBoostSig(doc) | ||
| if newDoc is None: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are some cases where |
||
| # None indicates that the existing __doc__ attr should be | ||
| # left as is | ||
| Debug("Docstring exists for %s - skipping" % pypath) | ||
| return None | ||
| docString = newDoc | ||
|
|
||
| if not docString.strip(): | ||
| # get the full docstring that we want to output | ||
| lines = [] | ||
| pyname = pypath.split('.')[-1] | ||
|
|
||
| # get the full docstring that we want to output | ||
| lines = [] | ||
| pyname = pypath.split('.')[-1] | ||
| docString = '' | ||
|
|
||
| if len(overloads) == 1: | ||
| lines += self.__getFullDoc(pyname, pyobj, overloads[0]) | ||
| if overloads[0].isStatic(): | ||
| docString = LABEL_STATIC # set the return type to static | ||
| else: | ||
| for doxy in overloads: | ||
| if doxy.isStatic(): | ||
| if len(overloads) == 1: | ||
| lines += self.__getFullDoc(pyname, pyobj, overloads[0]) | ||
| if overloads[0].isStatic(): | ||
| docString = LABEL_STATIC # set the return type to static | ||
|
|
||
| desc = self.__getFullDoc(pyname, pyobj, doxy) | ||
| if lines and desc: | ||
| lines.append('-'*70) | ||
| if desc: | ||
| lines += desc | ||
| docString += '\n'.join(lines) | ||
| else: | ||
| for doxy in overloads: | ||
| if doxy.isStatic(): | ||
| docString = LABEL_STATIC # set the return type to static | ||
|
|
||
| desc = self.__getFullDoc(pyname, pyobj, doxy) | ||
| if lines and desc: | ||
| lines.append('-'*70) | ||
| if desc: | ||
| lines += desc | ||
| docString += '\n'.join(lines) | ||
|
|
||
| def __getOutputFormat(self, pypath, pyobj, overloads): | ||
| """Return the line that installs the docstring into the namespace.""" | ||
|
|
||
| docString = self.__getDocstring(pypath, pyobj, overloads) | ||
| if docString is None: | ||
| return None | ||
| # work out the attribute to set to install this docstring | ||
| words = pypath.split('.') | ||
| cls = words[0] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,7 +29,7 @@ | |
| # and newer. These interpreters don't search for DLLs in the path anymore, you | ||
| # have to provide a path explicitly. This re-enables path searching for USD | ||
| # dependency libraries | ||
| import platform, sys | ||
| import os, platform, sys | ||
| if sys.version_info >= (3, 8) and platform.system() == "Windows": | ||
| import contextlib | ||
|
|
||
|
|
@@ -109,15 +109,16 @@ def PreparePythonModule(moduleName=None): | |
| except KeyError: | ||
| pass | ||
|
|
||
| try: | ||
| module = importlib.import_module(".__DOC", f_locals["__name__"]) | ||
| module.Execute(f_locals) | ||
| if os.environ.get("PXR_USD_PYTHON_DISABLE_DOCS", "false").lower() not in ("1", "true", "yes"): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
https://github.com/PixarAnimationStudios/OpenUSD/blob/v25.05.01/pxr/base/tf/getenv.h#L48 |
||
| try: | ||
| del f_locals["__DOC"] | ||
| except KeyError: | ||
| module = importlib.import_module(".__DOC", f_locals["__name__"]) | ||
| module.Execute(f_locals) | ||
| try: | ||
| del f_locals["__DOC"] | ||
| except KeyError: | ||
| pass | ||
| except Exception: | ||
| pass | ||
| except Exception: | ||
| pass | ||
|
|
||
| finally: | ||
| del frame | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the documentation builds also build TF, I'm thinking that this comment may now be out of date, and we can remove the define unconditionally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setting of the
BOOST_PYTHON_NO_PY_SIGNATURESdefine has moved here: https://github.com/PixarAnimationStudios/OpenUSD/blob/dev/pxr/external/boost/python/CMakeLists.txt#L327I tried to port the same behavior, like so:
Unfortunately, this leads to a build error that I don't understand.
My goal in not setting
BOOST_PYTHON_NO_PY_SIGNATURESis to allow boost to add its representation of the python signatures to the python docstrings.In an ideal world, instead of adding these to the
__doc__attribute of each object we would write them to disk as a build artifact that can be read by the custom docstring generator. Now that we have our own fork of boost that might actually be on the table, but either way this build error needs to be resolved.Unfortunately this has reached a level of complexity that I won't be able to solve it on my own, which means I can't make stubs for any versions of USD after the switch to pxr_boost. Any ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how far on the journey I can go, but this particular error is saying that wrapResolver.cpp, with that option enabled, requires a complete declaration of ArAsset, whereas resolver.h is only forward declaring it. So I think you just need to include asset.h in wrapResolver.cpp