generate all config, not just new traits on each class#385
generate all config, not just new traits on each class#385minrk merged 7 commits intoipython:masterfrom
Conversation
Yes, that is intentional by #289, to deduce clutter when generating help and sample-config files with non-trivial hierarchies of Configurables. And to compensate the "split", that PR had added the base-classes in the output, to help user decide in which class to set a config-param. To give an example of why I had crafted that PR, in our application , it reduced the number of lines of the generated sample configuration file by a factor of x5: $ wc -l config-PR385.py config-PR289.py
3578 config-PR385.py
729 config-PR289.pyThe extra lines were all identical copies of help-messages from traits included already in subclasses. So would it be possible to identify the root problem instead of going back to old behavior?
If that is happening, that's a certain bug: adding a subclass in the |
|
A possible fix would be not to include the help-message again on each subclass but just refere to the baseclass? |
|
I realize it was intentional, but I discovered after using it that the result is a significant regression.
When generating config for an object, I want to see everything I can configure on that object in one place. Relying on references and inheritance means that I have to look in several different disconnected places in the file in order to see the configuration of one object. So the important thing for me is that when I see the section of a config file corresponding to one object, I get every configurable for that object. Since this is a regression from previous releases, perhaps we could make the 'only not inherited' behavior an off-by-default option? |
|
Certainly adding a flag is doable, but maybe the following 2 alternatives improve the UI experience: a. Not repeating help messages on each config-param copy. If you think it is worth it, instead of a flag, I can craft some mockups here for config-files/help msgs to compare and decide, should I? |
sure, that would be great! The most important thing for me is that when I see a class's entry in a config file, I see every configurable trait for that class. There does need to be some explanation of it, at least. How's this for a compromise:
|
|
Here's the pattern I found that prompted this PR: A collection of configurable classes with the same/similar config options inherited from a Mixin. That Mixin is intended to be an implementation detail, and shouldn't affect the config file. In a typical config file, only one of these classes will be configured, so the norm is to find the section corresponding to your chosen class and configure it. In master, all of these classes produce empty config sections, which is not a good experience, and the Mixin defining the configurable traits isn't present, so it is impossible to discover what is configurable. The best experience in these cases would be for every class in the So something like this should get the right result:
|
using class_own_traits splits up config for classes with parents, and hides some config options entirely if traits are defined in base classes not in the final `classes` list. The change has not been released, so this restores the behavior of all released traitlets versions.
|
I've updated this PR to reflect the last proposal - a class always shows its configurable traits. The help is truncated to the first line plus "See also" if its parent class is also in the config file. The implicit inclusion of all parent classes is also removed, because it is no longer needed to ensure that all the config options show up somewhere. The implicit behavior effectively changed the meaning of the The behavior is now much closer to the current release behavior, with the exception that redundant help output is truncated. |
|
Given this class diagram: I'm trying to edit: pasting the "rough" code for the above diagram: import traitlets.config as trtc
import traitlets as trt
Base = trtc.Configurable
class _Irrelevant(Base):
pass
class TT1(Base):
tt1_a = trt.Bool(help="GRANDMOTHER HELP", config=True)
class T1(TT1):
t1_b = trt.Bool(help="Multi-line help message.\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. "
"Quisque eu nunc cursus, aliquam ante at, porttitor eros. Vestibulum et dignissim erat. "
"Suspendisse tempor, est eget pretium mollis, nulla libero eleifend ante, "
"eu tristique est quam sed ligula. Phasellus ac odio nunc. "
"Phasellus gravida ornare lorem, hendrerit venenatis velit facilisis mollis. ",
config=True)
class T2(Base):
t2_a = trt.Bool(help="Instructios for destruction.", config=True)
t2_b = trt.Bool(help="Overriden help-msg.\n\nAnd is now a bit longer, at least...", config=True)
class A(trtc.Application, T2, _Irrelevant):
classes = [T1, ]
t2_a = trt.Bool(help="Simple verbose flag.", config=True)
a = A()
print(a.generate_config_file()) |
|
(pasted the code for my previous comment) |
|
@minrk is this PR (738ce4d) supposed to work correctly, or rather WIP? # Configuration file for application.
#------------------------------------------------------------------------------
# A configuration
#------------------------------------------------------------------------------
## This is an application.
## The date format used by logging formatters for %(asctime)s
# Default: '%Y-%m-%d %H:%M:%S'
# c.A.log_datefmt = '%Y-%m-%d %H:%M:%S'
## The Logging format template
# Default: '[%(name)s]%(highlevel)s %(message)s'
# c.A.log_format = '[%(name)s]%(highlevel)s %(message)s'
## Set the log level by value or name.
# Choices: (0, 10, 20, 30, 40, 50, 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL')
# Default: 30
# c.A.log_level = 30
## Simple verbose flag.
# Default: False
# c.A.t2_a = False
## Overriden help-msg.
#
# And is now a bit longer, at least...
# Default: False
# c.A.t2_b = False
#------------------------------------------------------------------------------
# T1 configuration
#------------------------------------------------------------------------------
## Multi-line help message.
#
# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eu nunc
# cursus, aliquam ante at, porttitor eros. Vestibulum et dignissim erat.
# Suspendisse tempor, est eget pretium mollis, nulla libero eleifend ante, eu
# tristique est quam sed ligula. Phasellus ac odio nunc. Phasellus gravida
# ornare lorem, hendrerit venenatis velit facilisis mollis.
# Default: False
# c.T1.t1_b = False
## GRANDMOTHER HELP
# Default: False
# c.T1.tt1_a = False |
master(c622c8e): Missing base-classes bug
Strangely, i still cannot reproduce that bug you describe also in OP. For the above class-hierarchy, master lists all, and only all, configurables that have config==True traits. Check the printout in the next section. @minrk what am I missing here? master: Printout of configurables on masterIf I understand correctly what you want, the only thing missing from the printout below is to list all config-traits on each class, with a "see above" reference., correct? # Configuration file for application.
#------------------------------------------------------------------------------
# TT1(Configurable) configuration
#------------------------------------------------------------------------------
## GRANDMOTHER HELP
# Default: False
#c.TT1.tt1_a = False
#------------------------------------------------------------------------------
# T1(TT1,Irrelevant) configuration
#------------------------------------------------------------------------------
## Multi-line help message.
#
# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eu nunc
# cursus, aliquam ante at, porttitor eros. Vestibulum et dignissim erat.
# Suspendisse tempor, est eget pretium mollis, nulla libero eleifend ante, eu
# tristique est quam sed ligula. Phasellus ac odio nunc. Phasellus gravida
# ornare lorem, hendrerit venenatis velit facilisis mollis.
# Default: False
#c.T1.t1_b = False
#------------------------------------------------------------------------------
# T2(Configurable) configuration
#------------------------------------------------------------------------------
## Instructios for destruction.
# Default: False
#c.T2.t2_a = False
## Overriden help-msg.
#
# And is now a bit longer, at least...
# Default: False
#c.T2.t2_b = False
#------------------------------------------------------------------------------
# Application(SingletonConfigurable) configuration
#------------------------------------------------------------------------------
## This is an application.
## The date format used by logging formatters for %(asctime)s
# Default: '%Y-%m-%d %H:%M:%S'
#c.Application.log_datefmt = '%Y-%m-%d %H:%M:%S'
## The Logging format template
# Default: '[%(name)s]%(highlevel)s %(message)s'
#c.Application.log_format = '[%(name)s]%(highlevel)s %(message)s'
## Set the log level by value or name.
# Choices: (0, 10, 20, 30, 40, 50, 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL')
# Default: 30
#c.Application.log_level = 30
#------------------------------------------------------------------------------
# A(Application,T2,Irrelevant) configuration
#------------------------------------------------------------------------------
## This is an application.
## Simple verbose flag.
# Default: False
#c.A.t2_a = FalseThis PR fails if baseclass is a
|
|
|
||
| if classes: | ||
| help_classes = self._classes_with_config_traits() | ||
| help_classes = self.classes |
There was a problem hiding this comment.
We should respect the classes present in Application.classes, but we cannot hide classes that are relevant when applying config-values.
Actually, _classes_with_config_traits() reads this list, and augments it with any relevant baseclasses.
There was a problem hiding this comment.
Sorry, now I saw the change in loader below, so you really mean that :-)
But isn't that a breaking change?
Although this is a bold step that have its merit (i.e. produce real terse results & see below), I don't think it is preserving released 4.3.2 behavior. For instance, this program fails to print import traitlets.config as trtc
import traitlets as trt
class C(trtc.Configurable):
a = trt.Bool(help='1st help line.\n\nOther lines.').tag(config=True)
class A(trtc.Application, C):
pass
print(A().generate_config_file())
c=trtc.get_config()
## Set trait on base-class.
c.C.a = True
print("A.a: ", A(config=c).a)v4.3.2:This PR(738ce4d):master + minor fixes(1fe0d48):Assuming that this bug is fixed, it might make sense to introduce a new flag (e.g. |
|
@minrk how about printing all the parent parameters at the end of each class, like this(1e2c6cb)? |
make it a real list
was being added to the class attr instead of the instance attr, having no practical effect.
exclude 'Choices' and 'Default' on repeated entries
excludes mixins, etc.
|
I got sidetracked on the class list, that was a mistake. The list of classes should now actually be unchanged from 4.x and master.
That's almost there, as long as there is at least some help for every inherited trait. Showing no help and not where it came from isn't enough. This PR now meets my basic requirement:
and should have some benefit from the changes for brevity:
|
| # Truncate help to first line + "See also Original.trait" | ||
| if trait.help: | ||
| lines.append(c(trait.help.split('\n', 1)[0])) | ||
| lines.append('# See also %s.%s' % (defining_class.__name__, name)) |
There was a problem hiding this comment.
I would put a : at the end of the label to look like a "field", like "# Default:" and "# Choices:".
edit: Or just "# See: ...".
Mine too. Thanks. Have you also retrofitted the help-message & RsT doc generation? |
I haven't, I've left them alone. I might prefer to keep this PR confined to config-file output. |
| dvr = trait.default_value_repr() | ||
| lines.append(indent('# Default: %s' % dvr, 4)) | ||
| lines.append('#c.%s.%s = %s' % (cls.__name__, name, dvr)) | ||
| lines.append('# c.%s.%s = %s' % (cls.__name__, name, default_repr)) |
There was a problem hiding this comment.
You added a space after # on the command; is that on purpose?
I had put no space there to be easy to spot which lines are comments and which comments,
and to comment/uncomment a line with a single keystroke.
[edit] review started by mistake.
| # section header | ||
| breaker = '#' + '-'*78 | ||
| parent_classes = ','.join(p.__name__ for p in cls.__bases__) | ||
| breaker = '#' + '-' * 78 |
There was a problem hiding this comment.
breaker = '##' + '-' * 76
To separate class headers even better.
| if issubclass(p, Configurable) | ||
| ) | ||
|
|
||
| s = "# %s(%s) configuration" % (cls.__name__, parent_classes) |
There was a problem hiding this comment.
s = "## %s(%s) configuration" % (cls.__name__, parent_classes)
Same as above.
|
Apologize for being so late here. |
Difference: PREVIOUSLY: #------------------------------------------------------------------------------ # A(Application, C) configuration #------------------------------------------------------------------------------ ## This is an application. ## 1st help line. # See also C.a # c.A.a = traitlets.Undefined THIS PR: ##------------------------------------------------------------------------------ ## A(Application, C) configuration ##------------------------------------------------------------------------------ ## This is an application. ## 1st help line. # See also: C.a #c.A.a = traitlets.Undefined
style(cfg): apply review items in #385 for gen-config
using class_own_traits splits up config for classes with parents, and hides some config options entirely if traits are defined in base classes not in the final
classeslist.The change has not been released, so this restores the behavior of all released traitlets versions.