Positional arguments on a command are treated as positional command line arguments by Typer. For example to define an integer positional argument we could simply do:
def handle(self, int_arg: int): ...
You will likely want to add additional meta information to your arguments for Typer to render things like helps and usage strings. You can do this by annotating the type hint with the typer.Argument class:
import typing as t from typer import Argument # ... def handle(self, int_arg: t.Annotated[int, Argument(help="An integer argument")]): ...Define an Option¶
Options are like arguments but are not position dependent and instead provided with a preceding identifier string (e.g. –name).
When a default value is provided for a parameter, Typer will treat it as an option. For example:
def handle(self, flag: bool = False): ...
Would be called like this:
If the type hint on the option is something other than a boolean it will accept a value:
def handle(self, name: str = "world"): ...
Would be called like this:
$ mycommand --name=world $ mycommand --name world # this also works
To add meta information, we annotate with the typer.Option class:
import typing as t from typer import Option # ... def handle(self, name: t.Annotated[str, Option(help="The name of the thing")]): ...Define Multiple Subcommands¶
Commands with a single executable function should simply implement handle(), but if you would like have multiple subcommands you can define any number of functions decorated with command()
or command()
:
from django_typer.management import TyperCommand, command class Command(TyperCommand): @command() def subcommand1(self): ... @command() def subcommand2(self): ...
from django_typer.management import Typer app = Typer() @app.command() def subcommand1(): ... @app.command() def subcommand2(): ...
Note
When no handle() method is defined, you cannot invoke a command instance as a callable. instead you should invoke subcommands directly:
from django_typer.management import get_command command = get_command("mycommand") command.subcommand1() command.subcommand2() command() # this will raise an errorDefine Multiple Subcommands w/ a Default¶
We can also implement a default subcommand by defining a handle() method, and we can rename it to whatever we want the command to be. For example to define three subcommands but have one as the default we can do this:
1from django_typer.management import TyperCommand, command 2 3 4class Command(TyperCommand): 5 @command(name="subcommand1") 6 def handle(self): 7 return "handle" 8 9 @command() 10 def subcommand2(self): 11 return "subcommand2" 12 13 @command() 14 def subcommand3(self): 15 return "subcommand3"
1from django_typer.management import Typer 2 3app = Typer() 4 5 6@app.command(name="subcommand1") 7def handle(): 8 return "handle" 9 10 11@app.command() 12def subcommand2(): 13 return "subcommand2" 14 15 16@app.command() 17def subcommand3(): 18 return "subcommand3"
from django_typer.management import get_command command = get_command("mycommand") assert command.subcommand2() == 'subcommand2' assert command.subcommand3() == 'subcommand3' # this will invoke handle (i.e. subcommand1) assert command() == 'handle' command() # note - we *cannot* do this: command.handle() # but we can do this! assert command.subcommand1() == 'handle'
Lets look at the help output:
management Usage: management [OPTIONS] COMMAND [ARGS]... ╭─ Options ─────────────────────────────────────────────────────────────────── ─╮ │ - -help Show this message and exit. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Django ──────────────────────────────────────────────────────────────────── ─╮ │ - -version Show program's version number and exit. │ │ - -settings TEXT The Python path to a settings module, e.g. │ │ "myproject.settings.main". If this isn't │ │ provided, the DJANGO_SETTINGS_MODULE environment │ │ variable will be used. │ │ - -pythonpath PATH A directory to add to the Python path, e.g. │ │ "/home/djangoprojects/myproject". │ │ - -traceback Raise on CommandError exceptions │ │ - -show -locals Print local variables in tracebacks. │ │ - -no -color Don't colorize the command output. │ │ - -force -color Force colorization of the command output. │ │ - -skip -checks Skip system checks. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ────────────────────────────────────────────────────────────────── ─╮ │ subcommand2 │ │ subcommand3 │ │ subcommand1 │ ╰──────────────────────────────────────────────────────────────────────────────╯
Define Groups of Commands¶Any depth of command tree can be defined. Use the group()
or add_typer()
decorator to define a group of subcommands:
1from django_typer.management import TyperCommand, group 2 3 4class Command(TyperCommand): 5 @group() 6 def group1(self, common_option: bool = False): 7 # you can define common options that will be available to all 8 # subcommands of the group, and implement common initialization 9 # logic here. 10 ... 11 12 @group() 13 def group2(self): ... 14 15 # attach subcommands to groups by using the command decorator on the group 16 # function 17 @group1.command() 18 def grp1_subcommand1(self): ... 19 20 @group1.command() 21 def grp1_subcommand2(self): ... 22 23 # groups can have subgroups! 24 @group1.group() 25 def subgroup1(self): ... 26 27 @subgroup1.command() 28 def subgrp_command(self): ...
1from django_typer.management import Typer 2 3app = Typer() 4 5# the normal way to add a subgroup in typer is to create a new Typer instance 6# and add it to the parent group using add_typer() 7grp1 = Typer() 8app.add_typer(grp1, name="group1") 9 10grp2 = Typer() 11app.add_typer(grp2, name="group2") 12 13 14@grp1.callback() 15def group1(common_option: bool = False): 16 # you can define common options that will be available to all subcommands 17 # of the group, and implement common initialization logic here. 18 ... 19 20 21@grp2.callback() 22def group2(self): ... 23 24 25# attach subcommands to groups by using the command decorator on the group 26# function 27@grp1.command() 28def grp1_subcommand1(self): ... 29 30 31@grp1.command() 32def grp1_subcommand2(self): ... 33 34 35# this is not standard typer, but we can use the group function 36# as a shortcut instead of having to create a new Typer instance 37# and add it 38@grp1.group() 39def subgroup1(self): ... 40 41 42@subgroup1.command() 43def subgrp_command(self): ...
The hierarchy of groups and commands from the above example looks like this:
Define an Initialization Callback¶You can define an initializer function that takes arguments and options that will be invoked before your handle() command or subcommands using the initialize()
decorator. This is like defining a group at the command root and is an extension of the typer callback mechanism.
1from django_typer.management import TyperCommand, command, initialize 2 3 4class Command(TyperCommand): 5 @initialize() 6 def init(self, common_option: bool = False): 7 # you can define common options that will be available to all 8 # subcommands of the command, and implement common initialization 9 # logic here. This will be invoked before the chosen command 10 self.common_option = common_option 11 12 @command() 13 def subcommand1(self): 14 return self.common_option 15 16 @command() 17 def subcommand2(self): 18 return self.common_option
1from django_typer.management import Typer 2 3app = Typer() 4 5 6# initializers are called callbacks in Typer, but we may also use initialize() 7# as an alias 8@app.callback() 9def init(self, common_option: bool = False): 10 # you can define common options that will be available to all subcommands 11 # of the command, and implement common initialization logic here. This 12 # will be invoked before the chosen command 13 self.common_option = common_option 14 15 16@app.command() 17def subcommand1(self): 18 return self.common_option 19 20 21@app.command() 22def subcommand2(self): 23 return self.common_option
$> ./manage initializer --common-option subcommand1 True $> ./manage.py initializer --no-common-option subcommand2 False
from django_typer.management import get_command command = get_command("initializer") command.init(common_option=True) assert command.subcommand1() command.init(False) assert not command.subcommand2()Collect Results with @finalize¶
Typer and Click have a results_callback
mechanism on MultiCommands
that allow a function hook to be registered to operate on the results of subroutines before the command exits. You may use this same results_callback
mechanism directly through the Typer interface, but django-typer offers a more convenient class-aware way to do this with the finalize()
decorator.
For example lets say we have two subcommands that return strings, we could turn them into a csv string by registering a callback with finalize()
:
import typing as t from django_typer.management import TyperCommand, command, finalize # chain=True allows multiple subroutines to be called from the command line class Command(TyperCommand, chain=True): @finalize() def to_csv(self, results: t.List[str]): return ", ".join(results) @command() def cmd1(self): return "result1" @command() def cmd2(self): return "result2"
from django_typer.management import Typer def to_csv(results, **_): # result_callback is passed the CLI parameters on the current context # if we are uninterested in them, we can use the **_ syntax to ignore them return ", ".join(results) app = Typer(result_callback=to_csv, chain=True) @app.command() def cmd1(): return "result1" @app.command() def cmd2(): return "result2"
from django_typer.management import Typer # alternatively we can use the finalize nomenclature of the TyperCommand # interface - this is a non-standard Typer extension app = Typer(chain=True) # The Typer interface is extended with the finalize decorator @app.finalize() def to_csv(results): return ", ".join(results) @app.command() def cmd1(): return "result1" @app.command() def cmd2(): return "result2"
$> ./manage.py finalizer cmd1 cmd1 cmd2 result1, result2, result3
Tip
@finalize() wrapped callbacks will be passed the CLI parameters on the current context if the function signature accepts them. While convenient, we recommend using command state to track these parameters instead. This will be more amenable to direct invocations of command object functions.
Use @finalize on groups¶Finalizers are hierarchical. The finalize()
decorator is available for use on subgroups. When used on a group, the callback will be invoked after the group’s subcommands have been executed and the return value of the finalizer will be passed up to any finalizers at higher levels in the command hierarchy.
import typing as t from django_typer.management import TyperCommand, command, finalize, group class Command(TyperCommand, chain=True): """ Show that finalizers are hierarchical and results are collected and passed to the finalizer of the parent group if one exists. """ @finalize() def to_csv(self, results: t.List[str]): return ", ".join(results) @command() def cmd1(self): return "result1" @command() def cmd2(self): return "result2" @group(chain=True) def grp(self): return "grp" @grp.finalize() def to_upper_csv(self, results): return ", ".join([result.upper() for result in results]) @grp.command() def cmd3(self): return "result3" @grp.command() def cmd4(self): return "result4"
from django_typer.management import Typer # use native Typer interface to achieve the same result def to_csv(results, **_): return ", ".join(results) def to_upper_csv(results, **_): return ", ".join([result.upper() for result in results]) app = Typer(result_callback=to_csv, chain=True) grp = Typer(result_callback=to_upper_csv, chain=True) app.add_typer(grp, name="grp") @app.command() def cmd1(): return "result1" @app.command() def cmd2(): return "result2" @grp.command() def cmd3(): return "result3" @grp.command() def cmd4(): return "result4"
from django_typer.management import Typer # Use extensions to the typer interface to improve clarity app = Typer(chain=True) @app.finalize() def to_csv(results): return ", ".join(results) @app.group(chain=True) def grp(): pass @grp.finalize() def to_upper_csv(results): return ", ".join([result.upper() for result in results]) @app.command() def cmd1(): return "result1" @app.command() def cmd2(): return "result2" @grp.command() def cmd3(): return "result3" @grp.command() def cmd4(): return "result4"
$> ./manage.py finalizer cmd1 cmd1 cmd2 grp cmd4 cmd3 result1, result2, result3, RESULT4, RESULT3
Tip
Finalizers can be overridden just like groups and initializers using the plugin pattern.
Call Commands from Code¶There are two options for invoking a TyperCommand
from code without spawning off a subprocess. The first is to use Django’s builtin call_command()
function. This function will work exactly as it does for normal BaseCommand
derived commands. django-typer however adds another mechanism that can be more efficient, especially if your options and arguments are already of the correct type and require no parsing:
Say we have this command, called mycommand
:
from django_typer.management import TyperCommand, command class Command(TyperCommand): def handle(self, count: int=5): return count
from django.core.management import call_command from django_typer.management import get_command # we can use use call_command like with any Django command call_command("mycommand", count=10) call_command("mycommand", '--count=10') # this will work too # or we can use the get_command function to get a command instance and invoke it directly mycommand = get_command("mycommand") mycommand(count=10) mycommand(10) # this will work too # return values are also available assert mycommand(10) == 10
The rule of thumb is this:
Use
call_command()
if your options and arguments need parsing.Use
get_command()
and invoke the command functions directly if your options and arguments are already of the correct type.
If the second argument is a type, static type checking will assume the return value of get_command to be of that type:
from django_typer.management import get_command from myapp.management.commands.math import Command as Math math = get_command("math", Math) math.add(10, 5) # type checkers will resolve add parameters correctly
You may also fetch a subcommand function directly by passing its path:
get_command("math", "add")(10, 5)
Tip
Also refer to the get_command()
docs and here and here for the nuances of calling commands when handle() is and is not implemented.
TyperCommand
classes preserve all of the functionality of BaseCommand
derivatives. This means that you can still use class members like suppressed_base_arguments
to suppress default options.
By default TyperCommand
suppresses --verbosity
. You can add it back by setting suppressed_base_arguments
to an empty list. If you want to use verbosity you can simply redefine it or use one of django-typer’s provided type hints for the default BaseCommand
options:
1from django_typer.management import TyperCommand 2from django_typer.types import Verbosity 3 4 5class Command(TyperCommand): 6 suppressed_base_arguments = ["--settings"] # remove the --settings option 7 8 def handle(self, verbosity: Verbosity = 1): ...
1from django_typer.management import Typer 2from django_typer.types import Verbosity 3 4app = Typer() 5 6# after the first Typer() call this module will have a Command class 7# and we can modify it directly to remove the --settings option 8assert app.django_command 9app.django_command.suppressed_base_arguments = ["--settings"] 10 11 12@app.command() 13def handle(verbosity: Verbosity = 1): ...Configure Typer Options¶
Typer apps can be configured using a number of parameters. These parameters are usually passed to the Typer
class constructor when the application is created. django-typer provides a way to pass these options upstream to Typer by supplying them as keyword arguments to the TyperCommand
class inheritance:
1from django_typer.management import TyperCommand, command 2 3 4# here we pass chain=True to typer telling it to allow invocation of 5# multiple subcommands 6class Command(TyperCommand, chain=True): 7 @command() 8 def cmd1(self): 9 self.stdout.write("cmd1") 10 11 @command() 12 def cmd2(self): 13 self.stdout.write("cmd2")
1from django_typer.management import Typer 2 3# here we pass chain=True to typer telling it to allow invocation of 4# multiple subcommands 5app = Typer(chain=True) 6 7 8@app.command() 9def cmd1(self): 10 self.stdout.write("cmd1") 11 12 13@app.command() 14def cmd2(self): 15 self.stdout.write("cmd2")
$> ./manage.py configure cmd1 cmd2 cmd1 cmd2 $> ./manage.py configure cmd2 cmd1 cmd2 cmd1Define Shell Tab Completions for Parameters¶
See the section on defining shell completions.
Debug Shell Tab Completers¶See the section on debugging shell completers.
Inherit/Override Commands¶You can extend typer commands by subclassing them. All of the normal inheritance rules apply. You can either subclass an existing command from an upstream app and leave its module the same name to override the command or you can subclass and rename the module to provide an adapted version of the upstream command with a different name. For example:
Say we have a command that looks like:
management/commands/upstream.py¶1from django_typer.management import TyperCommand, command, group, initialize 2 3 4class Command(TyperCommand): 5 @initialize() 6 def init(self): 7 return "upstream:init" 8 9 @command() 10 def sub1(self): 11 return "upstream:sub1" 12 13 @command() 14 def sub2(self): 15 return "upstream:sub2" 16 17 @group() 18 def grp1(self): 19 return "upstream:grp1" 20 21 @grp1.command() 22 def grp1_cmd1(self): 23 return "upstream:grp1_cmd1"
We can inherit and override or add additional commands and groups like so:
management/commands/downstream.py¶1from django_typer.management import command, initialize 2 3from .upstream import Command as Upstream 4 5 6# inherit directly from the upstream command class 7class Command(Upstream): 8 # override init 9 @initialize() 10 def init(self): 11 return "downstream:init" 12 13 # override sub1 14 @command() 15 def sub1(self): 16 return "downstream:sub1" 17 18 # add a 3rd top level command 19 @command() 20 def sub3(self): 21 return "downstream:sub3" 22 23 # add a new subcommand to grp1 24 @Upstream.grp1.command() 25 def grp1_cmd2(self): 26 return "downstream:grp1_cmd2"management/commands/upstream.py¶
1from django_typer.management import Typer 2 3app = Typer() 4 5 6@app.callback() 7def init(): 8 return "upstream:init" 9 10 11@app.command() 12def sub1(): 13 return "upstream:sub1" 14 15 16@app.command() 17def sub2(): 18 return "upstream:sub2" 19 20 21@app.group() 22def grp1(): 23 return "upstream:grp1" 24 25 26@app.grp1.command() 27def grp1_cmd1(): 28 return "upstream:grp1_cmd1"
We can inherit and override or add additional commands and groups like so:
management/commands/downstream.py¶1from django_typer.management import Typer 2 3from .upstream import app as upstream 4 5# pass the upstream app as the first positional argument to Typer 6app = Typer(upstream) 7 8 9# override init 10@app.callback() 11def init(): 12 return "downstream:init" 13 14 15# override sub1 16@app.command() 17def sub1(): 18 return "downstream:sub1" 19 20 21# add a 3rd top level command 22@app.command() 23def sub3(): 24 return "downstream:sub3" 25 26 27# add a new subcommand to grp1 28@app.grp1.command() 29def grp1_cmd2(): 30 return "downstream:grp1_cmd2"
Notice that if we are adding to a group from the parent class, we have to use the group directly (i.e. @ParentClass.group_name). Since we named our command downstream it does not override upstream. upstream is not affected and may be invoked in the same way as if downstream was not present.
Plugin to Existing Commands¶You may add additional subcommands and command groups to existing commands by using django-typer’s plugin pattern. This allows apps that do not know anything about each other to attach additional CLI behavior to an upstream command and can be convenient for grouping loosely related behavior into a single command namespace.
To use our example from above, lets add and override the same behavior of upstream we did in downstream using this pattern instead:
First in other_app we need to create a new package under management. It can be called anything, but for clarity lets call it plugins:
site/ ├── my_app/ │ ├── __init__.py │ ├── apps.py │ ├── management/ │ │ ├── __init__.py │ │ └── commands/ │ │ ├── __init__.py │ │ └── upstream.py └── other_app/ ├── __init__.py ├── apps.py └── management/ ├── __init__.py ├── plugins/ │ ├── __init__.py │ └── upstream.py <---- put your plugins to upstream here └── commands/ └── __init__.py
Now we need to make sure our plugins are loaded. We do this by using the provided register_command_plugins()
convenience function in our app’s ready() method:
from django.apps import AppConfig from django_typer.utils import register_command_plugins class OtherAppConfig(AppConfig): name = "other_app" def ready(self): from .management import plugins register_command_plugins(extensions)
Now we can add our plugins:
management/plugins/upstream.py¶1from my_app.management.commands.upstream import Command as Upstream 2 3# When plugging into an existing command we do not create 4# a new class, but instead work directly with the commands 5# and groups on the upstream class 6 7 8# override init 9@Upstream.initialize() 10def init(self): 11 return "plugin:init" 12 13 14# override sub1 15@Upstream.command() 16def sub1(self): 17 return "plugin:sub1" 18 19 20# add a 3rd top level command 21@Upstream.command() 22def sub3(self): 23 return "plugin:sub3" 24 25 26# add a new subcommand to grp1 27@Upstream.grp1.command() 28def grp1_cmd2(self): 29 return "plugin:grp1_cmd2"management/plugins/upstream.py¶
1from my_app.management.commands.upstream import app 2 3# do not create a new app as with inheritance - 4# instead work directly with the upstream app. 5 6 7# override init 8@app.callback() 9def init(): 10 return "plugin:init" 11 12 13# override sub1 14@app.command() 15def sub1(): 16 return "plugin:sub1" 17 18 19# add a 3rd top level command 20@app.command() 21def sub3(): 22 return "plugin:sub3" 23 24 25# add a new subcommand to grp1 26@app.grp1.command() 27def grp1_cmd2(): 28 return "plugin:grp1_cmd2"
The main difference here from normal inheritance is that we do not declare a new class, instead we use the classmethod decorators on the class of the command we are extending. These extension functions will also be added to the class. The self argument is always optional in django-typer and if it is not provided the function will be treated as a staticmethod.
Note
Conflicting extensions are resolved in INSTALLED_APPS
order. For a detailed discussion about the utility of this pattern, see the tutorial on Extending Commands.
Warning
Take care not to import any extension code during or before Django’s bootstrap procedure. This may result in conflict override behavior that does not honor INSTALLED_APPS
order.
When rich is installed it may be configured to display rendered stack traces for unhandled exceptions. These stack traces are information dense and can be very helpful for debugging. By default, if rich is installed django-typer will configure it to render stack traces. You can disable this behavior by setting the DT_RICH_TRACEBACK_CONFIG
config to False
. You may also set DT_RICH_TRACEBACK_CONFIG
to a dictionary holding the parameters to pass to rich.traceback.install()
.
This provides a common hook for configuring rich that you can control on a per-deployment basis:
# refer to the rich docs for details DT_RICH_TRACEBACK_CONFIG = { "console": rich.console.Console(), # create a custom Console object for rendering "width": 100, # default is 100 "extra_lines": 3, # default is 3 "theme": None, # predefined themes "word_wrap": False, # default is False "show_locals": True, # default is False "locals_max_length": 10 # default is 10 "locals_max_string": 80 # default is 80 "locals_hide_dunder": True, # default is True "locals_hide_sunder": False, # default is None "indent_guides": True, # default is True "suppress": [], # suppress frames from these module import paths "max_frames": 100 # default is 100 } # or turn off rich traceback rendering DT_RICH_TRACEBACK_CONFIG = False
Tip
There are traceback configuration options that can be supplied as configuration parameters to the Typer application. It is best to not set these and allow users to configure tracebacks via the DT_RICH_TRACEBACK_CONFIG
setting.
There are multiple places to add help text to your commands. There is however a precedence order, and while lazy translation is supported in help texts, if you use docstrings as the helps they will not be translated.
The precedence order, for a simple command is as follows:
1from django.utils.translation import gettext_lazy as _ 2 3from django_typer.management import TyperCommand, command 4 5 6class Command(TyperCommand, help=_("2")): 7 """ 8 5: Command class docstrings are the last resort for 9 the upper level command help string. 10 """ 11 12 help = _("3") 13 14 # if an initializer is present it's help will be used for the command 15 # level help 16 17 @command(help=_("1")) 18 def handle(self): 19 """ 20 4: Function docstring is last priority and is not subject to 21 translation. 22 """
1from django.utils.translation import gettext_lazy as _ 2 3from django_typer.management import Typer 4 5# typer style commands use the Typer help precedence 6app = Typer(help=_("2")) 7 8 9@app.command(help=_("1")) 10def handle(self): 11 """ 12 3: Function docstring is last priority and is not subject to translation. 13 """
The rule for how helps are resolved when inheriting from other commands is that higher precedence helps in base classes will be chosen over lower priority helps in deriving classes. However, if you would like to use a docstring as the help in a derived class instead of the high priority help in a base class you can set the equivalent priority help in the deriving class to a falsy value:
class Command(TyperCommand, help=_("High precedence help defined in base class.")): ... ... from upstream.management.commands.command import Command as BaseCommand class Command(BaseCommand, help=None): """ Docstring will be used as help. """Order Commands in Help Text¶
By default commands are listed in the order they appear in the class. You can override this by using a custom click group.
For example, to change the order of commands to be in alphabetical order you could define a custom group and override the list_commands
method. Custom group and command classes may be provided like below, but they must extend from django-typer’s classes:
import typing as t from django_typer.management import TyperCommand, DTGroup, command, group from click import Context class AlphabetizeCommands(DTGroup): def list_commands(self, ctx: Context) -> t.List[str]: return list(sorted(self.commands.keys())) class Command(TyperCommand, cls=AlphabetizeCommands): @command() def b(self): print("b") @command() def a(self): print("a") @group(cls=AlphabetizeCommands) def d(self): print("d") @d.command() def f(self): print("f") @d.command() def e(self): print("e") @command() def c(self): print("c")
from django_typer.management import Typer, DTGroup from click import Context import typing as t class AlphabetizeCommands(DTGroup): def list_commands(self, ctx: Context) -> t.List[str]: return list(sorted(self.commands.keys())) app = Typer(cls=AlphabetizeCommands) d_app = Typer(cls=AlphabetizeCommands) app.add_typer(d_app, name="d") @app.command() def b(): print("b") @app.command() def a(): print("a") @d_app.callback() def d(): print("d") @d_app.command() def f(): print("f") @d_app.command() def e(): print("e") @app.command() def c(): print("c")
order Usage: manage.py order [OPTIONS] COMMAND [ARGS]... ╭─ Options ─────────────────────────────────────────────────────────────────── ─╮ │ - -help Show this message and exit. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Django ──────────────────────────────────────────────────────────────────── ─╮ │ - -version Show program's version number and exit. │ │ - -settings TEXT The Python path to a settings module, e.g. │ │ "myproject.settings.main". If this isn't │ │ provided, the DJANGO_SETTINGS_MODULE environment │ │ variable will be used. │ │ - -pythonpath PATH A directory to add to the Python path, e.g. │ │ "/home/djangoprojects/myproject". │ │ - -traceback Raise on CommandError exceptions │ │ - -show -locals Print local variables in tracebacks. │ │ - -no -color Don't colorize the command output. │ │ - -force -color Force colorization of the command output. │ │ - -skip -checks Skip system checks. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ────────────────────────────────────────────────────────────────── ─╮ │ b │ │ a │ │ d │ │ c │ ╰──────────────────────────────────────────────────────────────────────────────╯
manage.py order d Usage: manage.py order d [OPTIONS] COMMAND [ARGS]... ╭─ Options ─────────────────────────────────────────────────────────────────── ─╮ │ - -help Show this message and exit. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ────────────────────────────────────────────────────────────────── ─╮ │ f │ │ e │ ╰──────────────────────────────────────────────────────────────────────────────╯
order Usage: manage.py order [OPTIONS] COMMAND [ARGS]... ╭─ Options ─────────────────────────────────────────────────────────────────── ─╮ │ - -help Show this message and exit. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Django ──────────────────────────────────────────────────────────────────── ─╮ │ - -version Show program's version number and exit. │ │ - -settings TEXT The Python path to a settings module, e.g. │ │ "myproject.settings.main". If this isn't │ │ provided, the DJANGO_SETTINGS_MODULE environment │ │ variable will be used. │ │ - -pythonpath PATH A directory to add to the Python path, e.g. │ │ "/home/djangoprojects/myproject". │ │ - -traceback Raise on CommandError exceptions │ │ - -show -locals Print local variables in tracebacks. │ │ - -no -color Don't colorize the command output. │ │ - -force -color Force colorization of the command output. │ │ - -skip -checks Skip system checks. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ────────────────────────────────────────────────────────────────── ─╮ │ a │ │ b │ │ c │ │ d │ ╰──────────────────────────────────────────────────────────────────────────────╯
manage.py order d Usage: manage.py order d [OPTIONS] COMMAND [ARGS]... ╭─ Options ─────────────────────────────────────────────────────────────────── ─╮ │ - -help Show this message and exit. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ────────────────────────────────────────────────────────────────── ─╮ │ e │ │ f │ ╰──────────────────────────────────────────────────────────────────────────────╯
Document Commands w/Sphinx¶sphinxcontrib-typer can be used to render your rich helps to Sphinx docs and is used extensively in this documentation.
For example, to document a TyperCommand
with sphinxcontrib-typer, you would do something like this:
.. typer:: django_typer.management.commands.shellcompletion.Command:typer_app :prog: ./manage.py shellcompletion :show-nested: :width: 80
The Typer application object is a property of the command class and is named typer_app. The typer directive simply needs to be given the fully qualified import path of the application object.
Or we could render the helps for individual subcommands as well:
.. typer:: django_typer.management.commands.shellcompletion.Command:typer_app:install :prog: ./manage.py shellcompletion :width: 80
You’ll also need to make sure that Django is bootstrapped in your conf.py file:
import django os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'path.to.your.settings') django.setup()print, self.stdout and typer.echo¶
There are no unbreakable rules about how you should print output from your commands. You could use loggers, normal print statements or the BaseCommand
stdout and stderr output wrappers. Django advises the use of self.stdout.write
because the stdout and stderr streams can be configured by calls to call_command()
or get_command()
which allows you to easily grab output from your commands for testing. Using the command’s configured stdout and stderr output wrappers also means output will respect the --force-color
and --no-color
parameters.
Typer and click provide echo and secho functions that automatically handle byte to string conversions and offer simple styling support. TyperCommand
provides echo()
and secho()
wrapper functions for the Typer echo/secho functions. If you wish to use Typer’s echo you should use these wrapper functions because they honor the command’s --force-color
and --no-color
flags and the configured stdout/stderr streams:
1import typer 2 3from django_typer.management import TyperCommand 4 5 6class Command(TyperCommand): 7 def handle(self): 8 self.echo("echo does not support styling") 9 self.secho("but secho does!", fg=typer.colors.GREEN)
1import typer 2 3from django_typer.management import Typer 4 5app = Typer() 6 7 8@app.command() 9def handle(self): 10 self.echo("echo does not support styling") 11 self.secho("but secho does!", fg=typer.colors.GREEN)Toggle on/off result printing¶
Django’s BaseCommand
will print any truthy values returned from the handle()
method. This may not always be desired behavior. By default TyperCommand
will do the same, but you may toggle this behavior off by setting the class field print_result
to False.
1from django_typer.management import TyperCommand 2 3 4class Command(TyperCommand): 5 print_result = False 6 7 def handle(self): 8 return "This will not be printed"
1from django_typer.management import Typer 2 3app = Typer() 4 5app.django_command.print_result = False 6 7 8@app.command() 9def handle(): 10 return "This will not be printed"
Warning
We may switch the default behavior to not print in the future, so if you want guaranteed forward compatible behavior you should set this field.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4