sphinx autodoc-skip-member handler: can't show __init__() when using napoleon - python

I want to include the docstrings for __init__() in my sphinx-generated documentation.
I was following the accepted answer to this stackoverflow question to add a handler for autodoc-skip-member and was still unable to see my __init__() documentation. Trace code inside the if name == "__init__": block shows I am hitting that code.
On a hunch I removed 'sphinx.ext.napoleon' from my extensions definition, leaving
extensions = [
'sphinx.ext.autodoc',
# 'sphinx.ext.napoleon',
]
and then I can see the __init__() documentation.
The only thing I see in the napoleon documentation that seems relevant is napoleon_include_special_with_doc, which it says defaults to True. Explicitly setting it to True in conf.py doesn't seem to change anything.
ETA: If I add the following method:
def __blah__(self):
'''blah blah blah'''
print self.__class__
I see __blah__() in my generated documentation.
If I change the name of __blah__ to __repr__ or __str__,
I see them in the generated documentation.
If I comment out the existing __init__ and change
__blah__ to __init__ I don't see it.
So it seems specific to __init__().
Is this a known issue, and is there another way to control this when using napoleon?

Napoleon defers to your autodoc configuration for how you want to handle the __init__ method.
Check your autodoc settings in conf.py. In particular, make sure autoclass_content is set to either init or both.

Per Rob's followon at https://github.com/sphinx-doc/sphinx/issues/2374, if you're using any extension that also sets a handler for the "autodoc-skip-member" event only one of the handlers will be used. This would seem to be the issue at hand. Thanks Rob!

Related

Python: How to deprecate a function alias

As the title states, what I want to achieve is the following:
I have a python code base for which I want to rename all the functions from camelCasing to underscore_naming. In order to maintain backwards compatibility, I have renamed all the functions, but have created function aliases for all the old names. So far, so good.
Now what I want to do is add deprecation warnings to the function aliases, preferably in a fashion like this:
def do_something():
...
#deprecated(deprecated_in='2.0',
details='All functions have been adapted to fit the PEP8 standard. Please use "do_something" instead')
doSomething = do_something
So that if somebody uses the old API call, they will get a deprecation warning. I've taken a look at deprecation and Deprecated, but neither of them seem to work on aliases.
I realise that I can create a function definition for every deprecated name and decorate that, but that loses the elegance of the function alias, and makes for more mucky code. Does anybody have a good suggestion to achieve what I want?
I actually ended up going for a different solution, similar to what's suggested in Method and property aliases with custom docstring in Python.
I modified my code to
#alias('doSomething', deprecated=True)
def do_something():
...
and added a deprecation warning to the alias decorator.

how to override a method defined at __init__.py?

I'm trying to use a module (pandas_access) witch has an issue on a method (_extract_dtype) defined at __init__.py
What is the proper way (or any) to overwrite a method defined on __init__.py?
You can just "monkey patch" the method e.g.
def _extract_dtype_patched(self):
print("Patched version")
TargetClass._extract_dtype = _extract_dtype_patched
Although care must be taken, per #bracco23's link above, and if you've discovered a bug it would be cool to report it to the upstream project!

How to specialize documenters in sphinx autodoc

I am documenting a project with Sphinx I want to create a specialized version of the autoclass:: directive that allows me to modify the documentation string for certain classes.
Here is one thing I have tried: searching the sphinx source, I found that the autoclass directive is created via the ClassDocumenter object. Following this, my idea was to register a subclass of ClassDocumenter for the classes of interest, and then override get_doc to modify the docstring.
Here's my attempt at such an extension:
from six import class_types
from sphinx.ext.autodoc import ClassDocumenter
from testmodule import Foo # the class that needs modified documentation
class MyClassDocumenter(ClassDocumenter):
objtype = 'myclass'
priority = 20 # higher priority than ClassDocumenter
#classmethod
def can_document_member(cls, member, membername, isattr, parent):
return isinstance(member, class_types) and issubclass(member, Foo)
def get_doc(self, encoding=None, ignore=1):
doc = super(MyClassDocumenter, self).get_doc(encoding, ignore)
# do something to modify the output documentation
doc[0].insert(0, "ADD SOMETHING TO THE DOC")
return doc
def setup(app):
app.add_autodocumenter(MyClassDocumenter)
The problem is, when I run this I get an error: ERROR: Unknown directive type "py:myclass". It seems that registering a documenter is not enough to register the associated directive, but I've not been able to find any clues in the sphinx source to tell me how such a registration is supposed to happen. It's not as simple as using the standard add_directive() methods, because I have no explicit directive to register.
How can I correctly accomplish such a specialization of an auto-documenter in sphinx?
(note: the full set of files to reproduce the error is available in this gist)
I have found something in Docutils markup API
Directives are handled by classes derived from docutils.parsers.rst.Directive. They have to be registered by an extension using Sphinx.add_directive() or Sphinx.add_directive_to_domain().
Is it what you are looking for?
Documenter classes require an existing directive to apply, when appropriate. This can either be a "built in" directive, or it can be a custom directive you create and register. Either way, the directive to use is specified by the directivetype class attribute. You can specify this value explicitly:
class MyClassDocumenter(ClassDocumenter):
directivetype = 'foo' # corresponds to :foo: in the doc source
objtype = 'bar'
priority = 20
Or, it looks like you can omit directivetype and it will use the same value as objtype by default. (This is an educated guess)
So then the question is: have you registered a new directive for :myclass:? If not I think that is the problem. Alternatively, if there is some other directive that you want to be used (whether built-in, or custom) the answer is probably to specify it explicitly by including a value for directivetype in your Documenter class definition.

Pylint complains about method 'data_received' not overridden, for RequestHandler

For example:
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('data.html', items = [])
It yields the following Pylint error:
warning (W0223, abstract-method, MainHandler) Method 'data_received' is abstract in class 'RequestHandler' but is not overridden
I understand that somehow it wants me to override this data_received method, but I do not understand why, and what it is for?
This is actually a problem with Pylint that's sort of unavoidable with the nature of Python.
The RequestHandler class has a lot of methods that act as hooks you can override in order to do different things, but only some of those hooks may actually be called, depending on your application's code. To make sure you're implementing everything you're supposed to when you're using certain functionality, the default data_received implementation throws a NotImplementedError that will get triggered when you do something that expects your class to have a custom implementation.
Normally this isn't any kind of issue because Python lets you have code paths that fail and doesn't throw any errors. Because Pylint tries to "help" make sure you've done everything you're supposed to, it's seeing that NotImplementedError throw and is warning you that you could trigger it depending on what you do.
The real problem is that because Python is an interpreted language, it's hard for a tool like Pylint to look at your code and make sure it's "safe". Python gives you a lot of flexibility and power, but in turn you bear the burden of keeping your program's logic straight in your head and knowing what possible problems are actually problems, and what aren't.
Luckily, Pylint is aware of its own limitations and gives you nice tools to disable extraneous warnings. Add the comment line
# pylint: disable=W0223
right before your class definition and the warning should stop popping up for this instance while leaving everything else alone.
I am running into the same issue as the OP, except my PyCharm (2018.3.4) seems not to be using Pylint, but its own inspection engine. I managed to solve a similar issue with the similar trick as R Phillip Castagna suggested:
# noinspection PyAbstractClass
class XyzRequestHandler(tornado.web.RequestHandler):
def prepare(self):
print('-' * 100)
self.set_header('Access-Control-Allow-Origin', '*')
def print_n_respond(self, result):
response = json.dumps(result)
print('responding with:\n', response)
print()
self.write(response)
Here is a list of PyCharm's inspections.

Pylint best practices

Pylint looks like a good tool for running analysis of Python code.
However, our main objective is to catch any potential bugs and not coding conventions. Enabling all Pylint checks seems to generate a lot of noise. What is the set of Pylint features you use and is effective?
You can block any warnings/errors you don't like, via:
pylint --disable=error1,error2
I've blocked the following (description from http://www.logilab.org/card/pylintfeatures):
W0511: Used when a warning note as FIXME or XXX is detected
W0142: Used * or * magic*. Used when a function or method is called using *args or **kwargs to dispatch arguments. This doesn't improve readability and should be used with care.
W0141: Used builtin function %r. Used when a black listed builtin function is used (see the bad-function option). Usual black listed functions are the ones like map, or filter, where Python offers now some cleaner alternative like list comprehension.
R0912: Too many branches (%s/%s). Used when a function or method has too many branches, making it hard to follow.
R0913: Too many arguments (%s/%s). Used when a function or method takes too many arguments.
R0914: Too many local variables (%s/%s). Used when a function or method has too many local variables.
R0903: Too few public methods (%s/%s). Used when class has too few public methods, so be sure it's really worth it.
W0212: Access to a protected member %s of a client class. Used when a protected member (i.e. class member with a name beginning with an underscore) is access outside the class or a descendant of the class where it's defined.
W0312: Found indentation with %ss instead of %ss. Used when there are some mixed tabs and spaces in a module.
C0111: Missing docstring. Used when a module, function, class or method has no docstring. Some special methods like __init__ don't necessarily require a docstring.
C0103: Invalid name "%s" (should match %s). Used when the name doesn't match the regular expression associated to its type (constant, variable, class...).
To persistently disable warnings and conventions:
Create a ~/.pylintrc file by running pylint --generate-rcfile > ~/.pylintrc
Edit ~/.pylintrc
Uncomment disable= and change that line to disable=W,C
Pyflakes should serve your purpose well.
-E will only flag what Pylint thinks is an error (i.e., no warnings, no conventions, etc.)
Using grep like:
pylint my_file.py | grep -v "^C"
Edit :
As mentionned in the question, to remove the conventions advices from pylint output, you remove the lines that start with an uppercase C.
From the doc of pylint, the output consists in lines that fit the format
MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE
and the message type can be:
[R]efactor for a “good practice” metric violation
[C]onvention for coding standard violation
[W]arning for stylistic problems, or minor programming issues
[E]rror for important programming issues (i.e. most probably bug)
[F]atal for errors which prevented further processing
Only the first letter is displayed, so you can play with grep to select/remove the level of message type you want.
I didn't use Pylint recently, but I would probably use a parameter inside Pylint to do so.

Categories