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

dynamically generate HasTraits class documentation #131

Closed
wants to merge 5 commits into from

Conversation

rmorshea
Copy link
Contributor

@rmorshea rmorshea commented Nov 19, 2015

A first go at generating TraitType documentation inside the HasTraits.__init__ docstring.

class A(HasTraits):
    i = Int().tag(allow_none=True, default_value=1)
    j = Any().tag(help='some help text for j', data={'x':1})

    def __init__(self, *args, **kwargs):
        """The constructor for the class

        More details about the constructor"""
        pass

    @default('j')
    def h(self): pass

    @validate('j')
    def g(self, commit):
        return commit['value']

    @observe('i', 'j')
    def f(self, change):
        pass

help(A.__init__)

has a docstring that reads:

:Traits of :class:`A` Instances:
--------------------------------
i : an int
    - trait metadata
        * ``allow_none = True``
        * ``default_value = 1``
    - event handlers
        * :method:`A.f` - observes changes

j : any value
    :help: some help text for j

    - trait metadata
        * ``data = {'x': 1}``
    - event handlers
        * :method:`A.f` - observes changes
        * :method:`A.g` - validates values
        * :method:`A.h` - sets the default

closes #130

@rmorshea rmorshea force-pushed the doc_gen branch 4 times, most recently from c46c6ea to b9a2d30 Compare November 19, 2015 19:02
@rmorshea
Copy link
Contributor Author

@akhmerov I've never used sphinx, but hopefully this is something like what you were looking to get out of #130. I didn't really see an easy way to use a descriptor to get the job done since it needs access to the class, and from what I was able to learn, manipulating call signatures really only seems feasible with PEP 0362, but that's for Python>=3.3

@akhmerov
Copy link
Member

It should be quite sufficient for sphinx, and yes this is roughly what I thought about. How do you treat other trait arguments, (default_value, allow_none, read_only, help)?

Not sure what the proper thing is to do with the formatting; Perhaps the numpy docstring standard:

Traits:
-------
<trait_name> : <trait_type>[, optional]
    <help>[ (default <default_value>)].

You can possibly omit class name from the appended docstring. Numpy often doesn't list defaults since these are apparent from call signatures, however this doesn't seem an option for traits.

Should read-only traits be omitted from the doc? @minrk mentioned that there's a nontrivial decision to be made regarding subclassing.

@akhmerov
Copy link
Member

Signature manipulation: perhaps then do it optionally, checking for the interpreter version?

@rmorshea
Copy link
Contributor Author

I only looked into signature manipulation briefly, so I'll try to confirm, but I think the issue prior to 3.3 is that functions compile differently depending on signature. Given that, eval would really be the only way to fundamentally alter the signature. The other option would be to wrap __init__ (but again, I don't think a decorator would work since you need access to the class).

@akhmerov
Copy link
Member

Oh, I just mean "rewrite the signature on 3.3, do nothing < 3.3". I cannot think of negative consequences of a less informative signature.

@rmorshea
Copy link
Contributor Author

Gotcha. In terms of doc formatting in __init__ would it be confusing that the trait_type instance only exists on the class? With the numpy style, I imagine that someone would expect hastraits_inst.name to be an instance of the trait_type rather than something which satisfies the validation conditions for the trait_type present on the class.

@akhmerov
Copy link
Member

Sorry, didn't get that. What exactly do you mean? What sort of confusion do you expect?

Also a disclaimer I should have made earlier: I'm not a dev, just another guy with an opinion, and I'm not even sure if devs consider this reasonable to implement.

@rmorshea
Copy link
Contributor Author

So with traitlets having a class like this:

class A(HasTraits):
    i = Int()

a = A()
out = a.i

out is not the Int instance defined on the class. out (based on the default value) is 0. I think it would be confusing for a person to see documentation that would make it appear as if a.i was a TraitType instance rather than a value which matches the types accepted by Int descriptors.

@rmorshea
Copy link
Contributor Author

Based on my experience, I think having documentation in some capacity that describes what traits exist on the class would be helpful. At the moment, the only real way to understand what's going on is to look at the source.

I'd have to think about how to implement it, but it should even be possible to include information on what static notifiers are associated with each trait providing more context for what the class does.

@akhmerov
Copy link
Member

Ah, my apologies, I was unclear. By <trait_type> in the doc I really meant also what you have written, so int for an Int trait, and any for Any. I don't think there's a reason why the actual trait types must be mentioned in the docstring.

@rmorshea rmorshea force-pushed the doc_gen branch 3 times, most recently from 574cdb0 to 8e50c54 Compare November 19, 2015 23:13
@rmorshea
Copy link
Contributor Author

I think I'll wait to work through signature manipulation until there's consensus on how I've implemented this so far. Until then, I think this pretty much covers documentation of TraitTypes and their notifiers.

ping @minrk @SylvainCorlay

@rmorshea rmorshea force-pushed the doc_gen branch 5 times, most recently from e647faa to 47672e5 Compare November 24, 2015 15:34
@SylvainCorlay SylvainCorlay added this to the 4.2 milestone Nov 30, 2015
@minrk minrk modified the milestones: 4.2, 4.3 Mar 14, 2016
@akhmerov
Copy link
Member

Is there anything missing in this PR? It's quite important in making ipywidgets interface manageable (because otherwise there are essentially no docstrings).

@rmorshea rmorshea force-pushed the doc_gen branch 3 times, most recently from c59c4ef to c181056 Compare April 20, 2016 20:32
@rmorshea rmorshea changed the title dynamically generate HasTraits __init__ documentation dynamically generate HasTraits class documentation Apr 21, 2016
@rmorshea rmorshea force-pushed the doc_gen branch 6 times, most recently from 081a58e to 97897ba Compare April 22, 2016 03:55
@rmorshea
Copy link
Contributor Author

rmorshea commented Apr 22, 2016

@minrk now rebased and tests pass.

In 92f87ec, the dynamic trait documentation is written to cls.__init__.__doc__. However in 01adb49, it's in cls.__doc__ - this has a simpler implementation because __doc__ operates the same in Python2 and Python3, and cls.__doc__ isn't reference by subclasses.

So depending on where we prefer the documentation to be I can either revert to 01adb49, or squash the commits. I think putting them in cls.__init__.__doc__ makes more sense, because attributes are writable via **kwargs in __init__, but ultimately I think it's subjective.

@minrk
Copy link
Member

minrk commented Jul 6, 2016

Sorry, I think this needs a rebase due to the revert of class_traits changes.

# - - - - - - - - - - - - - - - -

def trait_documentation(cls, name=None):
"""Generate documentaiton for traits and event handlers on the class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation

@rmorshea
Copy link
Contributor Author

rmorshea commented Jul 12, 2016

@minrk I'm giving this an update. However it's still a work in progress. In general I'm making things more modular. This will make it easier to write DocLog classes for new vanilla traits, or custom traits defined by applications that use traitlets. I've still got a lot to do in terms of testing to make it ready for the PR, but for right now this is functionality exists:

import json

class DocLogEncoder(json.JSONEncoder):

    def __init__(self, *a, **kw):
        if kw.pop('pretty', True):
            kw.update(sort_keys=True, indent=4)
        super(DocLogEncoder, self).__init__(*a, **kw)

    def default(self, obj):
        return obj.content

class BaseClass(HasTraits):

    foo = Int()

class SubClass(BaseClass):

    # foo is documented (with added changes)
    foo = Int(default_value=5, min=0, max=10)
    # bar won't be documented by foo_logger
    bar = Int()

foo_logger = hastraits_doclogger(BaseClass)

log_one = foo_logger(BaseClass)
log_two = foo_logger(SubClass)

dle = DocLogEncoder(pretty=True)
print(dle.encode(log_one))
print(dle.encode(log_two))

@minrk minrk modified the milestones: 4.3, 5.0 Jul 29, 2016
@rmorshea rmorshea closed this Aug 1, 2016
@minrk minrk modified the milestones: no action, 5.0 Aug 4, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Trait introspection
4 participants