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
Make core extendable by making classes and initializations configurable #938
Conversation
Is there any way to push this forward? |
Possibly fixes #561 |
I self assigned this a while ago, but never finished writing up feedback. We should address this need, but I'm not sure I'm sold on this particular implementation. Making a class attribute for this feels clunky, IMO. I don't have a clear alternative -- nothing I wrote and tested, anyway -- and maybe this is the best way of doing it. I'd like to try for a cleaner approach though, to see if it's doable. |
Subclassing to override attributes is a common pattern in the Pallets libraries, so the general idea is fine. Some classes can already be passed to decorators, and the context idea is interesting too. So we should take our time to ensure this is all consistent and well documented. |
Is there a list of such classes? Other than the obvious
|
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.
I'm disappointed that I haven't been able to get time to try other versions/variants of this which I'm interested in exploring. C'est la vie, but sorry all the same.
What I actually care about and what can be done to push this forward:
- Should this
Comand.command_class
be an instance attribute (Command.command_class
) rather than a class attribute? - What does usage look like? Can we add some test-cases for this functionality which demonstrate custom Context, custom Command, and custom MultiCommand or Group classes? That would show what the interface is and how to use it.
- I'd want narrative docs long-term. But! I think it's a bit much to demand them for an initial version. I'd like to know enough to write such narrative docs myself.
- Can this usage be supported by the
click.command
andclick.group
decorators? e.g.click.command(cls=MyCustomClass)
?
The basic issue that I'm hung up on is that it seems all of the usage is predicated on creating a class heirarchy which mimics the click
classes -- you need CustomCmd(Command)
, CustomMultiCmd(MultiCommand)
, and CustomGroup(Group)
each of which sets command_class = CustomCmd
, right? Or am I mistaken about that?
And you get in MRO-related trouble quickly if you try to setup CustomMultiCmd(CustomCmd, MultiCommand)
because you may want to override behavior in CustomCmd
which you would like CustomMultiCommand
to inherit from MultiCommand
.
Could we just make command_class
an instance attribute, rather than a class attribute, so that I can have
@click.group('mycmd', command_class=CustomCommandClass)
def cli():
pass
@cli.command('foocmd', cls=CustomCommandClass)
def foo():
pass
# by omitting `command_class` from `bar` and `cls` from `baz`, can we sanely allow
# `mycmd foo` to have one behavior, and `mycmd bar baz` to have another?
@cli.group('bargroup', cls=CustomGroupClass)
def bar():
pass
@bar.command('baz')
def baz():
pass
?
Where/how/why does that break down?
If the intent is that users modify global state by setting Command.command_class = CustomCmd
, I would argue very strongly that that's bad interface/API design. So I don't want that to be the answer, and if that's the current plan, then we should figure out better usage and then what changes need to be made to support it.
A lot of this boils down to "I'm struggling to see what usage looks like."
So some example of usage would go a long way, I think. Ideally in the form of a test case -- and if it can't be put into a test case, please explain why, because that may be a sign of a problem.
click/core.py
Outdated
@@ -526,10 +526,11 @@ def invoke(*args, **kwargs): | |||
# It's also possible to invoke another command which might or | |||
# might not have a callback. In that case we also fill | |||
# in defaults and make a new context for this command. | |||
if isinstance(callback, Command): | |||
if isinstance(callback, self.command.command_class or Command): |
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.
I think that if a user overrides the command class, they should do so with a custom subclass of Command
.
That would mean this check can stay isinstance(callback, Command)
.
Does that sound right?
If so, I'd prefer to use Command
directly still for this because I think it's a little bit more readable.
Applies here and to the check below.
8308ac6
to
3abf95c
Compare
To answer @sirosen, in general, the idea here is not to add any new behavior, but to make it easier for custom classes to persist through the stack. Right now, As far as I can tell, |
338de06
to
8f19a8b
Compare
Added a I'm considering what the |
Decided to add |
7c3939d
to
d1fa187
Compare
d1fa187
to
c465912
Compare
bda7ca5
to
aff5168
Compare
Added tests and a longer changelog entry.
|
I started to create a parallel implementation of Command/Group/etc. to support asyncio (#85) and some other features I need, one of the problems I had is that even if I can instruct
cls=MyCommand
in some calls, internally the modules use hardcoded classes in many cases.The idea of the PR is not to support directly asyncio, but to open to the possibility of doing so. Comments are welcome.