@@ -3,12 +3,10 @@ Advanced Patterns
33
44.. currentmodule :: click
55
6- In addition to common functionality that is implemented in the library
7- itself, there are countless patterns that can be implemented by extending
8- Click. This page should give some insight into what can be accomplished.
6+ In addition to common functionality, Click offers some advanced features.
97
108.. contents ::
11- :depth: 2
9+ :depth: 1
1210 :local:
1311
1412Callbacks and Eager Options
@@ -110,133 +108,6 @@ also contain the parameter name.
110108 println()
111109 invoke(roll, input=["42", "2d12"])
112110
113- .. _custom-groups :
114-
115- Custom Groups
116- -------------
117-
118- You can customize the behavior of a group beyond the arguments it accepts by
119- subclassing :class: `click.Group `.
120-
121- The most common methods to override are :meth: `~click.Group.get_command ` and
122- :meth: `~click.Group.list_commands `.
123-
124- The following example implements a basic plugin system that loads commands from
125- Python files in a folder. The command is lazily loaded to avoid slow startup.
126-
127- .. code-block :: python
128-
129- import importlib.util
130- import os
131- import click
132-
133- class PluginGroup (click .Group ):
134- def __init__ (self , name = None , plugin_folder = " commands" , ** kwargs ):
135- super ().__init__ (name = name, ** kwargs)
136- self .plugin_folder = plugin_folder
137-
138- def list_commands (self , ctx ):
139- rv = []
140-
141- for filename in os.listdir(self .plugin_folder):
142- if filename.endswith(" .py" ):
143- rv.append(filename[:- 3 ])
144-
145- rv.sort()
146- return rv
147-
148- def get_command (self , ctx , name ):
149- path = os.path.join(self .plugin_folder, f " { name} .py " )
150- spec = importlib.util.spec_from_file_location(name, path)
151- module = importlib.util.module_from_spec(spec)
152- spec.loader.exec_module(module)
153- return module.cli
154-
155- cli = PluginGroup(
156- plugin_folder = os.path.join(os.path.dirname(__file__ ), " commands" )
157- )
158-
159- if __name__ == " __main__" :
160- cli()
161-
162- Custom classes can also be used with decorators:
163-
164- .. code-block :: python
165-
166- @click.group (
167- cls = PluginGroup,
168- plugin_folder = os.path.join(os.path.dirname(__file__ ), " commands" )
169- )
170- def cli ():
171- pass
172-
173- .. _aliases :
174-
175- Command Aliases
176- ---------------
177-
178- Many tools support aliases for commands. For example, you can configure
179- ``git `` to accept ``git ci `` as alias for ``git commit ``. Other tools also
180- support auto-discovery for aliases by automatically shortening them.
181-
182- It's possible to customize :class: `Group ` to provide this functionality. As
183- explained in :ref: `custom-groups `, a group provides two methods:
184- :meth: `~Group.list_commands ` and :meth: `~Group.get_command `. In this particular
185- case, you only need to override the latter as you generally don't want to
186- enumerate the aliases on the help page in order to avoid confusion.
187-
188- The following example implements a subclass of :class: `Group ` that accepts a
189- prefix for a command. If there was a command called ``push ``, it would accept
190- ``pus `` as an alias (so long as it was unique):
191-
192- .. click :example ::
193-
194- class AliasedGroup(click.Group):
195- def get_command(self, ctx, cmd_name):
196- rv = super().get_command(ctx, cmd_name)
197-
198- if rv is not None:
199- return rv
200-
201- matches = [
202- x for x in self.list_commands(ctx)
203- if x.startswith(cmd_name)
204- ]
205-
206- if not matches:
207- return None
208-
209- if len(matches) == 1:
210- return click.Group.get_command(self, ctx, matches[0])
211-
212- ctx.fail(f"Too many matches: {', '.join(sorted(matches))}")
213-
214- def resolve_command(self, ctx, args):
215- # always return the full command name
216- _, cmd, args = super().resolve_command(ctx, args)
217- return cmd.name, cmd, args
218-
219- It can be used like this:
220-
221- .. click :example ::
222-
223- @click.group(cls=AliasedGroup)
224- def cli():
225- pass
226-
227- @cli.command
228- def push():
229- pass
230-
231- @cli.command
232- def pop():
233- pass
234-
235- See the `alias example `_ in Click's repository for another example.
236-
237- .. _alias example : https://github.com/pallets/click/tree/main/examples/aliases
238-
239-
240111Parameter Modifications
241112-----------------------
242113
@@ -360,64 +231,6 @@ And what it looks like:
360231 invoke(cli, prog_name='cli', args=['dist'])
361232
362233
363- .. _callback-evaluation-order :
364-
365- Callback Evaluation Order
366- -------------------------
367-
368- Click works a bit differently than some other command line parsers in that
369- it attempts to reconcile the order of arguments as defined by the
370- programmer with the order of arguments as defined by the user before
371- invoking any callbacks.
372-
373- This is an important concept to understand when porting complex
374- patterns to Click from optparse or other systems. A parameter
375- callback invocation in optparse happens as part of the parsing step,
376- whereas a callback invocation in Click happens after the parsing.
377-
378- The main difference is that in optparse, callbacks are invoked with the raw
379- value as it happens, whereas a callback in Click is invoked after the
380- value has been fully converted.
381-
382- Generally, the order of invocation is driven by the order in which the user
383- provides the arguments to the script; if there is an option called ``--foo ``
384- and an option called ``--bar `` and the user calls it as ``--bar
385- --foo ``, then the callback for ``bar `` will fire before the one for ``foo ``.
386-
387- There are three exceptions to this rule which are important to know:
388-
389- Eagerness:
390- An option can be set to be "eager". All eager parameters are
391- evaluated before all non-eager parameters, but again in the order as
392- they were provided on the command line by the user.
393-
394- This is important for parameters that execute and exit like ``--help ``
395- and ``--version ``. Both are eager parameters, but whatever parameter
396- comes first on the command line will win and exit the program.
397-
398- Repeated parameters:
399- If an option or argument is split up on the command line into multiple
400- places because it is repeated -- for instance, ``--exclude foo --include
401- baz --exclude bar `` -- the callback will fire based on the position of
402- the first option. In this case, the callback will fire for
403- ``exclude `` and it will be passed both options (``foo `` and
404- ``bar ``), then the callback for ``include `` will fire with ``baz ``
405- only.
406-
407- Note that even if a parameter does not allow multiple versions, Click
408- will still accept the position of the first, but it will ignore every
409- value except the last. The reason for this is to allow composability
410- through shell aliases that set defaults.
411-
412- Missing parameters:
413- If a parameter is not defined on the command line, the callback will
414- still fire. This is different from how it works in optparse where
415- undefined values do not fire the callback. Missing parameters fire
416- their callbacks at the very end which makes it possible for them to
417- default to values from a parameter that came before.
418-
419- Most of the time you do not need to be concerned about any of this,
420- but it is important to know how it works for some advanced cases.
421234
422235.. _forwarding-unknown-options :
423236
@@ -515,74 +328,6 @@ everything below a subcommand be forwarded to another application than to
515328handle some arguments yourself.
516329
517330
518- Global Context Access
519- ---------------------
520-
521- .. versionadded :: 5.0
522-
523- Starting with Click 5.0 it is possible to access the current context from
524- anywhere within the same thread through the use of the
525- :func: `get_current_context ` function which returns it. This is primarily
526- useful for accessing the context bound object as well as some flags that
527- are stored on it to customize the runtime behavior. For instance the
528- :func: `echo ` function does this to infer the default value of the `color `
529- flag.
530-
531- Example usage::
532-
533- def get_current_command_name():
534- return click.get_current_context().info_name
535-
536- It should be noted that this only works within the current thread. If you
537- spawn additional threads then those threads will not have the ability to
538- refer to the current context. If you want to give another thread the
539- ability to refer to this context you need to use the context within the
540- thread as a context manager::
541-
542- def spawn_thread(ctx, func):
543- def wrapper():
544- with ctx:
545- func()
546- t = threading.Thread(target=wrapper)
547- t.start()
548- return t
549-
550- Now the thread function can access the context like the main thread would
551- do. However if you do use this for threading you need to be very careful
552- as the vast majority of the context is not thread safe! You are only
553- allowed to read from the context, but not to perform any modifications on
554- it.
555-
556-
557- Detecting the Source of a Parameter
558- -----------------------------------
559-
560- In some situations it's helpful to understand whether or not an option
561- or parameter came from the command line, the environment, the default
562- value, or :attr: `Context.default_map `. The
563- :meth: `Context.get_parameter_source ` method can be used to find this
564- out. It will return a member of the :class: `~click.core.ParameterSource `
565- enum.
566-
567- .. click :example ::
568-
569- @click.command()
570- @click.argument('port', nargs=1, default=8080, envvar="PORT")
571- @click.pass_context
572- def cli(ctx, port):
573- source = ctx.get_parameter_source("port")
574- click.echo(f"Port came from {source.name}")
575-
576- .. click :run ::
577-
578- invoke(cli, prog_name='cli', args=['8080'])
579- println()
580- invoke(cli, prog_name='cli', args=[], env={"PORT": "8080"})
581- println()
582- invoke(cli, prog_name='cli', args=[])
583- println()
584-
585-
586331Managing Resources
587332------------------
588333
0 commit comments