Skip to content
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

Can I only document the module itself? #15

Open
marksweiss opened this issue Jul 26, 2014 · 17 comments

Comments

Projects
None yet
5 participants
@marksweiss
Copy link

commented Jul 26, 2014

I am very happy to find pdoc! I looked at other choices and they were heavy and poorly documented. So excellent to find something that works right away and is easy to manipulate.

That said, I have spent a few hours with the code but haven't quite figured out how to restrict the scope of documentation to just the contents of the module. I want to be able to only document:

  • module scope variables
  • module scope functions
  • classes in a module
  • methods in a class in a module
  • instance variables in a class in a module

A standard use case for this that I imagine isn't just mine is for unit tests. Including in the documentation all the methods inherited from UnitTest does not add useful information.

I think this applies generally for any situation where you want the documentation to only focus on the module source, rather than being comprehensive about all the context of that module. In my case, I am documenting an intentionally minimal library and a very strong design goal is keeping every aspect of it as brief and simple as possible.

I will happily provide a pull request if necessary to implement this.

Thanks!

@BurntSushi

This comment has been minimized.

Copy link
Contributor

commented Jul 27, 2014

I fully agree with your use case. There should absolutely be a way to limit the presence of ancestor classes in the documentation.

I think the only thing that needs to be decided is how pdoc should make such a configuration available. My suspicion is that this should probably be controlled on a per class basis (one options for showing all ancestors or showing none seems a bit heavy handed to me), which means the special __pdoc__ variable needs to be involved. Any thoughts?

@marksweiss

This comment has been minimized.

Copy link
Author

commented Jul 27, 2014

Yeah, I have this working locally. My goal was to modify your code as little as possible. I found that the cleanest way to scope the objects being documented is to modify __public_objs in the __init__.py. Basically, instead of returning the result of inspect.getmembers I return the intersection of that and self.cls.__dict__.keys(), because the latter is limited to just the elements found in the module. The code does all this as a filter on the existing return list expression, so it could be more efficient. But it works and it's very localized. Here is the total change:

# LINE 1036

idents = dict(inspect.getmembers(self.cls, inspect.ismethod))```
# ADDED
# Only include attributes in this module or class, not inherited attributes
cls_dict_names = self.cls.__dict__.keys()
def is_in_dict(n):
    return n in cls_dict_names
# /ADDED
return dict([(n, o) for n, o in idents.items() if exported(n) and is_in_dict(n)]) # MODIFIED

I spent some time trying to see how to pass an argument to control this, looking at the docfilter argument for example. But it's not clear to me how to control this behavior with a command-line switch. I'm sure I could figure it out, but I'll bet you already know. :-)

To me a working solution would be:

  • set a command line switch like --no-inherited-attributes (or some better name than that)
  • check it in a conditional around the code above

I was about to fork and submodule, but I'd much rather we achieve something like this, which adds value for everyone for what we agree seems like a reasonably common use case. I'm happy to pull request just the above change, or the entire patch including passing in the argument to control the behavior. Let me know what you think makes the most sense.

(I might still fork because I'd like to customize the HTML template, but that is a separate issue.)

Thanks!

@marksweiss

This comment has been minimized.

Copy link
Author

commented Jul 27, 2014

By the way here is a link to documentation created using pdoc, with my change to only include elements from the module scope and also my template change to remove the index and mro blocks. Uses github static hosting. http://marksweiss.github.io/sofine/docs/sofine/runner.m.html. I think it looks great! Thanks again for your lib! Great decision to just support markdown doc strings too.

@marksweiss

This comment has been minimized.

Copy link
Author

commented Jul 27, 2014

I didn't read your answer carefully enough the first time. I can see the point of making this configurable per module and/or per class. That does complicate how you would tell pdoc how to document each module. The only thing I can think of, based on my knowledge of the system, is some kind of flag at the start of a module docstring or class docstring that you could parse for. If found you could put a key into the __pdoc__ dict with a True value. That said, I think you should also support the simpler use case of passing a command-line arg that supports this behavior for every module in a project.

@marksweiss

This comment has been minimized.

Copy link
Author

commented Aug 5, 2014

Any thoughts on how to proceed with this?

@marksweiss

This comment has been minimized.

Copy link
Author

commented Aug 27, 2014

Checking in one more time on this. Are you still interested in collaborating on a solution?

@BurntSushi

This comment has been minimized.

Copy link
Contributor

commented Aug 27, 2014

@marksweiss Yes! Sorry about my absurd tardiness. How about you submit a PR for what you have and we (or I) can take it from there? As for __pdoc__, I was thinking something like this:

class Something (object):
    __pdoc__['Something__no-inherited-attributes'] = True

Then it could be toggled per class. I think I might go for a global option too.

@mriehl

This comment has been minimized.

Copy link

commented Jan 22, 2015

Personally I don't think that completely disabling inherited attributes would be workable.
If you want to hide all functionality provided by the superclass, then why use inheritance at all (instead of composition)?

I have a use case where the parent class is bloated with private methods but has a few methods I would like to expose. So I'm much more in favour of a whitelist/blacklist.

For example some kind of __all__ at the class level, e.G.

class Dog(Animal):
    __all__ = ["bark", "eat", "sleep"]

I thought about a decorator too but that would make it that much more difficult to document parent class behaviour.

@marksweiss

This comment has been minimized.

Copy link
Author

commented Jan 22, 2015

Sorry I never saw your response from last August.

Do you still want a pull request? It should be simple unless there are many subsequent changes to merge in, as I'm working from a fork that is just this diff I needed to get this to work. @mriehl @BurntSushi I'm all for a more flexible approach, so again let me know if you want a pull request for my broader change.

Thanks!

@BurntSushi

This comment has been minimized.

Copy link
Contributor

commented Jan 25, 2015

@marksweiss I think I like @mriehl's suggested approach better. Reusing the concept of __all__ is really nice IMO. :-) Thank you though!

@leotrs

This comment has been minimized.

Copy link

commented Feb 21, 2015

Hi, I tried using this solution found in #25 (which was closed in favor of this):

class MyCmd(Cmd):
    for field in Cmd.__dict__.keys():
        __pdoc__['MyCmd.%s' % field] = None

But didn't work. After this code, for some reason, __pdoc__ has only a few keys set to None (module, init, doc, etc). I also tried to use

    for field in dir(Cmd):
        ...

In which case __pdoc__ IS full of the methods that I want to ignore, but they are still being documented.

Thanks.

@BurntSushi

This comment has been minimized.

Copy link
Contributor

commented Feb 21, 2015

@Leockard Can you produce a minimal test case? It seems to be working fine over here:

class A(object):
    afoo = 1
    def abaz(self):
        pass


class B(A):
    bfoo = 1
    def bbaz(self):
        pass

Then:

[andrew@Liger pdoc] pdoc 15.py
Module 15
---------

Classes
-------
A 
    Ancestors (in MRO)
    ------------------
    15.A
    __builtin__.object

    Descendents
    -----------
    15.B

    Class variables
    ---------------
    afoo

    Methods
    -------
    abaz(self)

B 
    Ancestors (in MRO)
    ------------------
    15.B
    15.A
    __builtin__.object

    Class variables
    ---------------
    afoo

    bfoo

    Methods
    -------
    abaz(self)

    bbaz(self)

Now applying the __pdoc__ trick:

__pdoc__ = {}

class A(object):
    afoo = 1
    def abaz(self):
        pass


class B(A):
    __pdoc__['B.k'] = None
    for k in A.__dict__:
        __pdoc__['B.%s' % k] = None

    bfoo = 1
    def bbaz(self):
        pass

And the a methods no longer show up for B:

[andrew@Liger pdoc] pdoc 15.py
Module 15
---------

Classes
-------
A 
    Ancestors (in MRO)
    ------------------
    15.A
    __builtin__.object

    Descendents
    -----------
    15.B

    Class variables
    ---------------
    afoo

    Methods
    -------
    abaz(self)

B 
    Ancestors (in MRO)
    ------------------
    15.B
    15.A
    __builtin__.object

    Class variables
    ---------------
    bfoo

    Methods
    -------
    bbaz(self)
@leotrs

This comment has been minimized.

Copy link

commented Feb 21, 2015

OK, first, I thought the __pdoc__ variable was a class member, and didn't notice it was supposed to be a module-global variable. I got it to work now, BUT, see below.

I'm using wxpython for my application. Here's the test code:

import wx

__pdoc__ = {}

class Test(wx.Frame):
    for k in wx.Frame.__dict__.keys():
        __pdoc__['Test.%s' % k] = None

    def method(self, arg1):
        """method doc"""
        pass

And the output:

leo@lily:~$ pdoc test.py | tail -n15

    WindowToClientSize(*args, **kwargs)
        WindowToClientSize(self, Size size) -> Size

        Converts window size ``size`` to corresponding client area size. In
        other words, the returned value is what `GetClientSize` would return
        if this window had given window size. Components with
        ``wxDefaultCoord`` (-1) value are left unchanged.

        Note that the conversion is not always exact, it assumes that
        non-client area doesn't change and so doesn't take into account things
        like menu bar (un)wrapping or (dis)appearance of the scrollbars.

    method(self, arg1)
        method doc

Clearly, WindowToClientSize comes from wx.Frame.

I have also tried

for k in wx.Frame.__dict__:
        __pdoc__['Test.%s' % k] = None

per your suggestion, but I get the same output.

I finally got it to work by using:

for k in dir(wx.Frame):
        __pdoc__['Test.%s' % k] = None

Which outputs:

leo@lily:~$ pdoc test.py | tail -n15
    wx._windows.Frame
    wx._windows.TopLevelWindow
    wx._core.Window
    wx._core.EvtHandler
    wx._core.Object
    __builtin__.object

    Class variables
    ---------------
    k

    Methods
    -------
    method(self, arg1)
        method doc
@BurntSushi

This comment has been minimized.

Copy link
Contributor

commented Feb 21, 2015

@Leockard Ah, great! So dir is the trick. It must be including something that isn't covered in __dict__. Neat. I'll suggest that from now on. Thanks for reporting back! :-)

@leotrs

This comment has been minimized.

Copy link

commented Feb 21, 2015

OK, by looking at it more closely, it looks like wx.Frame.__dict__ only contains the methods from wx.Frame proper, while dir(wx.Frame) is returning the list of all its members, including the ones from its ancestors.
Of course, I wanted to ignore all the methods coming from wx, so I wanted the latter one.

@leotrs

This comment has been minimized.

Copy link

commented Feb 21, 2015

Well, setting everything in dir(base_class) to None didn't work either. For example,

__pdoc__ = {}

class Base():
    def override_method(self, arg1):
        """base method doc"""
        pass

class Child(Base):
    __pdoc__["Child.k"] = None
    for k in dir(Base):
        __pdoc__['Child.%s' % k] = None

    def override_method(self, arg1):
        """overriden method doc"""
        pass

Output:

leo@lily:~$ pdoc test.py
Module test
-----------

Classes
-------
Base 
    Ancestors (in MRO)
    ------------------
    test.Base

    Methods
    -------
    override_method(self, arg1)
        base method doc

Child 
    Ancestors (in MRO)
    ------------------
    test.Child
    test.Base

Of course, since we are setting __pdoc__[Base.override_method] = None, the Child.override_method doesn't get documented either.

My workaround for now is to write something like this at the end of the file:

# pdoc setup
__pdoc__ = {}
for k in dir(Base):
    __pdoc__['Child.%s' % k] = None
for k in Child.__dict__.keys():
    del __pdoc__['Child.%s' % k]

While I think this is still concise enough, I think this solution is approaching dangerous levels...

EDIT: Of course, before doing del __pdoc__[key], I have to check if the key exists...

@dakov

This comment has been minimized.

Copy link

commented Mar 18, 2019

Is this really the only way to accomplish that? Don't you think it's a lot of overhead in cases with dozens of classes?

For example, we want to generate a documentation for people who know very little programming. Therefore, we prepared many wrapper classes and methods for them to work with. So what I would need is to whitelist methods and attributes which will be reflected in the documentation, instead of black-listing those which will not.

I'm a fan of the __all__ approach, but I guess it's still not possible?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.