[{"data":1,"prerenderedAt":1858},["ShallowReactive",2],{"page-\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fwriting-a-plugin-for-an-existing-cli\u002F":3,"content-directory":1014},{"id":4,"title":5,"body":6,"date":1000,"description":1001,"difficulty":1002,"draft":1003,"extension":1004,"meta":1005,"navigation":167,"path":1006,"seo":1007,"stem":1008,"tags":1009,"updated":1000,"__hash__":1013},"content\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fwriting-a-plugin-for-an-existing-cli\u002Findex.md","Writing a Plugin for an Existing CLI",{"type":7,"value":8,"toc":979},"minimark",[9,13,18,68,72,79,83,87,202,410,417,421,424,431,493,500,504,507,510,520,534,544,547,551,554,567,577,580,584,593,602,608,614,618,625,856,863,867,870,874,879,890,894,897,901,904,908,911,915,918,922,925,929,935,939,975],[10,11,12],"p",{},"Most of the writing about plugin systems is aimed at the host. This is the other side: you have a\nCLI you do not own, it advertises an extension point, and you want to add commands to it without\nforking anything.",[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,30,38,49,56],"ul",{},[22,23,24,25,29],"li",{},"A plugin is an ",[26,27,28],"strong",{},"ordinary package"," whose metadata names the host's entry-point group.",[22,31,32,33,37],{},"Installing it ",[34,35,36],"em",{},"is"," the registration — the host reads installed metadata at run time.",[22,39,40,41,44,45,48],{},"Depend on the host with a ",[26,42,43],{},"version range",", and declare whatever ",[26,46,47],{},"API version"," it checks.",[22,50,51,52,55],{},"Use only ",[26,53,54],{},"documented seams","; importing the host's private modules is how plugins break.",[22,57,58,59,63,64,67],{},"Install it into the host's environment with ",[60,61,62],"code",{},"pipx inject"," or ",[60,65,66],{},"uv tool install --with",".",[14,69,71],{"id":70},"prerequisites","Prerequisites",[10,73,74,75,78],{},"A host CLI that documents an entry-point group (for example ",[60,76,77],{},"mytool.plugins","), and a package of\nyour own to put the commands in.",[14,80,82],{"id":81},"what-a-plugin-package-looks-like","What a plugin package looks like",[84,85],"inline-diagram",{"name":86},"plugin-package-layout",[88,89,94],"pre",{"className":90,"code":91,"language":92,"meta":93,"style":93},"language-toml shiki shiki-themes github-light github-dark","# pyproject.toml\n[project]\nname = \"mytool-deploy\"\nversion = \"0.1.0\"\nrequires-python = \">=3.11\"\ndependencies = [\"mytool>=1.4,\u003C2.0\"]      # the host, with a range\n\n[project.entry-points.\"mytool.plugins\"]\ndeploy = \"mytool_deploy.cli:plugin\"\n","toml","",[60,95,96,105,119,129,138,147,162,169,193],{"__ignoreMap":93},[97,98,101],"span",{"class":99,"line":100},"line",1,[97,102,104],{"class":103},"sJ8bj","# pyproject.toml\n",[97,106,108,112,116],{"class":99,"line":107},2,[97,109,111],{"class":110},"sVt8B","[",[97,113,115],{"class":114},"sScJk","project",[97,117,118],{"class":110},"]\n",[97,120,122,125],{"class":99,"line":121},3,[97,123,124],{"class":110},"name = ",[97,126,128],{"class":127},"sZZnC","\"mytool-deploy\"\n",[97,130,132,135],{"class":99,"line":131},4,[97,133,134],{"class":110},"version = ",[97,136,137],{"class":127},"\"0.1.0\"\n",[97,139,141,144],{"class":99,"line":140},5,[97,142,143],{"class":110},"requires-python = ",[97,145,146],{"class":127},"\">=3.11\"\n",[97,148,150,153,156,159],{"class":99,"line":149},6,[97,151,152],{"class":110},"dependencies = [",[97,154,155],{"class":127},"\"mytool>=1.4,\u003C2.0\"",[97,157,158],{"class":110},"]      ",[97,160,161],{"class":103},"# the host, with a range\n",[97,163,165],{"class":99,"line":164},7,[97,166,168],{"emptyLinePlaceholder":167},true,"\n",[97,170,172,174,176,178,181,183,186,188,191],{"class":99,"line":171},8,[97,173,111],{"class":110},[97,175,115],{"class":114},[97,177,67],{"class":110},[97,179,180],{"class":114},"entry-points",[97,182,67],{"class":110},[97,184,185],{"class":114},"\"mytool",[97,187,67],{"class":110},[97,189,190],{"class":114},"plugins\"",[97,192,118],{"class":110},[97,194,196,199],{"class":99,"line":195},9,[97,197,198],{"class":110},"deploy = ",[97,200,201],{"class":127},"\"mytool_deploy.cli:plugin\"\n",[88,203,207],{"className":204,"code":205,"language":206,"meta":93,"style":93},"language-python shiki shiki-themes github-light github-dark","# src\u002Fmytool_deploy\u002Fcli.py\nimport typer\n\nAPI_VERSION = 2            # the contract version the host checks\napp = typer.Typer(help=\"Deployment commands.\")\n\n@app.command()\ndef rollout(environment: str, replicas: int = 3) -> None:\n    \"\"\"Roll out the current build to ENVIRONMENT.\"\"\"\n    ...\n\nclass plugin:              # the object the entry point names\n    api_version = API_VERSION\n    name = \"deploy\"\n\n    @staticmethod\n    def register(host_app: typer.Typer) -> None:\n        host_app.add_typer(app, name=\"deploy\")\n","python",[60,208,209,214,223,227,242,265,269,277,311,316,322,327,342,353,364,369,378,394],{"__ignoreMap":93},[97,210,211],{"class":99,"line":100},[97,212,213],{"class":103},"# src\u002Fmytool_deploy\u002Fcli.py\n",[97,215,216,220],{"class":99,"line":107},[97,217,219],{"class":218},"szBVR","import",[97,221,222],{"class":110}," typer\n",[97,224,225],{"class":99,"line":121},[97,226,168],{"emptyLinePlaceholder":167},[97,228,229,233,236,239],{"class":99,"line":131},[97,230,232],{"class":231},"sj4cs","API_VERSION",[97,234,235],{"class":218}," =",[97,237,238],{"class":231}," 2",[97,240,241],{"class":103},"            # the contract version the host checks\n",[97,243,244,247,250,253,257,259,262],{"class":99,"line":140},[97,245,246],{"class":110},"app ",[97,248,249],{"class":218},"=",[97,251,252],{"class":110}," typer.Typer(",[97,254,256],{"class":255},"s4XuR","help",[97,258,249],{"class":218},[97,260,261],{"class":127},"\"Deployment commands.\"",[97,263,264],{"class":110},")\n",[97,266,267],{"class":99,"line":149},[97,268,168],{"emptyLinePlaceholder":167},[97,270,271,274],{"class":99,"line":164},[97,272,273],{"class":114},"@app.command",[97,275,276],{"class":110},"()\n",[97,278,279,282,285,288,291,294,297,299,302,305,308],{"class":99,"line":171},[97,280,281],{"class":218},"def",[97,283,284],{"class":114}," rollout",[97,286,287],{"class":110},"(environment: ",[97,289,290],{"class":231},"str",[97,292,293],{"class":110},", replicas: ",[97,295,296],{"class":231},"int",[97,298,235],{"class":218},[97,300,301],{"class":231}," 3",[97,303,304],{"class":110},") -> ",[97,306,307],{"class":231},"None",[97,309,310],{"class":110},":\n",[97,312,313],{"class":99,"line":195},[97,314,315],{"class":127},"    \"\"\"Roll out the current build to ENVIRONMENT.\"\"\"\n",[97,317,319],{"class":99,"line":318},10,[97,320,321],{"class":231},"    ...\n",[97,323,325],{"class":99,"line":324},11,[97,326,168],{"emptyLinePlaceholder":167},[97,328,330,333,336,339],{"class":99,"line":329},12,[97,331,332],{"class":218},"class",[97,334,335],{"class":114}," plugin",[97,337,338],{"class":110},":              ",[97,340,341],{"class":103},"# the object the entry point names\n",[97,343,345,348,350],{"class":99,"line":344},13,[97,346,347],{"class":110},"    api_version ",[97,349,249],{"class":218},[97,351,352],{"class":231}," API_VERSION\n",[97,354,356,359,361],{"class":99,"line":355},14,[97,357,358],{"class":110},"    name ",[97,360,249],{"class":218},[97,362,363],{"class":127}," \"deploy\"\n",[97,365,367],{"class":99,"line":366},15,[97,368,168],{"emptyLinePlaceholder":167},[97,370,372,375],{"class":99,"line":371},16,[97,373,374],{"class":114},"    @",[97,376,377],{"class":231},"staticmethod\n",[97,379,381,384,387,390,392],{"class":99,"line":380},17,[97,382,383],{"class":218},"    def",[97,385,386],{"class":114}," register",[97,388,389],{"class":110},"(host_app: typer.Typer) -> ",[97,391,307],{"class":231},[97,393,310],{"class":110},[97,395,397,400,403,405,408],{"class":99,"line":396},18,[97,398,399],{"class":110},"        host_app.add_typer(app, ",[97,401,402],{"class":255},"name",[97,404,249],{"class":218},[97,406,407],{"class":127},"\"deploy\"",[97,409,264],{"class":110},[10,411,412,413,416],{},"The exact shape of the object depends on the host's contract — some want a Typer app, some a\ncallable, some a class with a ",[60,414,415],{},"register"," method. What is universal is that the entry point names an\nimportable target and the host decides what to do with it.",[14,418,420],{"id":419},"installing-into-the-hosts-environment","Installing into the host's environment",[84,422],{"name":423},"plugin-lifecycle-sequence",[10,425,426,427,430],{},"A plugin must be installed ",[34,428,429],{},"where the host is",", which for an isolated install means injecting into\nthat environment rather than installing globally:",[88,432,436],{"className":433,"code":434,"language":435,"meta":93,"style":93},"language-bash shiki shiki-themes github-light github-dark","pipx inject mytool mytool-deploy               # into the host's own venv\nuv tool install mytool --with mytool-deploy    # or declare it at install time\npipx inject mytool -e .\u002Fmytool-deploy          # editable, while developing\n","bash",[60,437,438,455,476],{"__ignoreMap":93},[97,439,440,443,446,449,452],{"class":99,"line":100},[97,441,442],{"class":114},"pipx",[97,444,445],{"class":127}," inject",[97,447,448],{"class":127}," mytool",[97,450,451],{"class":127}," mytool-deploy",[97,453,454],{"class":103},"               # into the host's own venv\n",[97,456,457,460,463,466,468,471,473],{"class":99,"line":107},[97,458,459],{"class":114},"uv",[97,461,462],{"class":127}," tool",[97,464,465],{"class":127}," install",[97,467,448],{"class":127},[97,469,470],{"class":231}," --with",[97,472,451],{"class":127},[97,474,475],{"class":103},"    # or declare it at install time\n",[97,477,478,480,482,484,487,490],{"class":99,"line":121},[97,479,442],{"class":114},[97,481,445],{"class":127},[97,483,448],{"class":127},[97,485,486],{"class":231}," -e",[97,488,489],{"class":127}," .\u002Fmytool-deploy",[97,491,492],{"class":103},"          # editable, while developing\n",[10,494,495,496,499],{},"That last form is the development loop: an editable install means your edits take effect\nimmediately, and ",[60,497,498],{},"mytool --help"," shows your commands straight away. If the plugin does not appear,\nthe usual causes are installing it into the wrong environment, a typo in the group name, or a host\nthat caches its plugin list.",[14,501,503],{"id":502},"keeping-the-plugin-working","Keeping the plugin working",[84,505],{"name":506},"plugin-compat-checklist",[10,508,509],{},"Three habits decide whether your plugin survives the host's next release.",[10,511,512,515,516,519],{},[26,513,514],{},"Depend on a range, not a pin."," ",[60,517,518],{},"mytool>=1.4,\u003C2.0"," says what you actually support. A hard pin\nmakes your plugin uninstallable alongside a slightly newer host; no constraint at all means it\nbreaks silently when the host makes a major change.",[10,521,522,525,526,529,530,533],{},[26,523,524],{},"Declare the API version the host asks for."," If the host checks ",[60,527,528],{},"api_version",", that integer is\nthe contract. When it bumps, the host will skip your plugin with a message rather than half-loading\nit — which is exactly what you want, because a clear \"needs API v3\" is far better than an\n",[60,531,532],{},"AttributeError"," in a user's terminal.",[10,535,536,539,540,543],{},[26,537,538],{},"Use documented seams only."," Importing ",[60,541,542],{},"mytool.internal.registry"," works today and is not part of\nanything the host promised. The same applies to monkeypatching host functions at import time, which\nturns your plugin into something the host cannot refactor around.",[10,545,546],{},"When you genuinely need something the host does not expose, ask for it upstream. A documented\nextension point benefits every plugin author, including future-you.",[14,548,550],{"id":549},"designing-the-commands-you-add","Designing the commands you add",[10,552,553],{},"Two conventions make a plugin feel like part of the tool rather than bolted on.",[10,555,556,515,559,562,563,566],{},[26,557,558],{},"Namespace your commands unless the host says otherwise.",[60,560,561],{},"mytool deploy rollout"," is unambiguous;\na top-level ",[60,564,565],{},"mytool rollout"," competes with the host's own naming and with every other plugin. Most\nhosts that document a plugin group also document where plugin commands should live.",[10,568,569,572,573,576],{},[26,570,571],{},"Follow the host's conventions"," for output, exit codes and configuration. If the host emits JSON\nunder ",[60,574,575],{},"--json"," with a versioned envelope, your commands should too; if it uses exit code 78 for a\nconfiguration problem, use the same. Users do not care which package a command came from, and\ninconsistency reads as a bug in the tool.",[10,578,579],{},"Read configuration through whatever the host provides — usually a settings object on the context —\nrather than loading your own file. Two configuration systems in one CLI is a support burden that\nlands on the host's maintainer.",[14,581,583],{"id":582},"ux-considerations","UX considerations",[10,585,586,515,589,592],{},[26,587,588],{},"Name the package after the host.",[60,590,591],{},"mytool-deploy"," tells a user immediately what it extends and\ngroups your plugin with others in search results.",[10,594,595,601],{},[26,596,597,598,67],{},"Document the injection command, not ",[60,599,600],{},"pip install"," A user who installs your plugin globally\nwhile the host is in a pipx environment gets nothing, and the failure is silent — no error, just\nmissing commands.",[10,603,604,607],{},[26,605,606],{},"Say which host versions you support"," in the README, and keep it accurate. Users debug \"my plugin\ndisappeared\" far faster when the answer is a version range they can check.",[10,609,610,613],{},[26,611,612],{},"Fail helpfully."," If your commands need credentials or a service the host does not, produce a\nclear error naming what is missing, exactly as the host would.",[14,615,617],{"id":616},"testing-the-behaviour","Testing the behaviour",[10,619,620,621,624],{},"The valuable test invokes the ",[34,622,623],{},"host"," and asserts your commands are reachable, because that\nexercises discovery rather than mocking it:",[88,626,628],{"className":204,"code":627,"language":206,"meta":93,"style":93},"from typer.testing import CliRunner\nfrom mytool.cli import app          # the host application\n\nrunner = CliRunner()\n\ndef test_plugin_commands_are_registered():\n    result = runner.invoke(app, [\"--help\"])\n    assert result.exit_code == 0\n    assert \"deploy\" in result.stdout\n\ndef test_rollout_runs_through_the_host(monkeypatch):\n    seen = {}\n    monkeypatch.setattr(\"mytool_deploy.cli.core.rollout\", lambda **kw: seen.update(kw))\n\n    result = runner.invoke(app, [\"deploy\", \"rollout\", \"prod\", \"--replicas\", \"5\"])\n\n    assert result.exit_code == 0\n    assert seen == {\"environment\": \"prod\", \"replicas\": 5}\n",[60,629,630,643,658,662,672,676,686,702,716,729,733,743,753,773,777,809,813,823],{"__ignoreMap":93},[97,631,632,635,638,640],{"class":99,"line":100},[97,633,634],{"class":218},"from",[97,636,637],{"class":110}," typer.testing ",[97,639,219],{"class":218},[97,641,642],{"class":110}," CliRunner\n",[97,644,645,647,650,652,655],{"class":99,"line":107},[97,646,634],{"class":218},[97,648,649],{"class":110}," mytool.cli ",[97,651,219],{"class":218},[97,653,654],{"class":110}," app          ",[97,656,657],{"class":103},"# the host application\n",[97,659,660],{"class":99,"line":121},[97,661,168],{"emptyLinePlaceholder":167},[97,663,664,667,669],{"class":99,"line":131},[97,665,666],{"class":110},"runner ",[97,668,249],{"class":218},[97,670,671],{"class":110}," CliRunner()\n",[97,673,674],{"class":99,"line":140},[97,675,168],{"emptyLinePlaceholder":167},[97,677,678,680,683],{"class":99,"line":149},[97,679,281],{"class":218},[97,681,682],{"class":114}," test_plugin_commands_are_registered",[97,684,685],{"class":110},"():\n",[97,687,688,691,693,696,699],{"class":99,"line":164},[97,689,690],{"class":110},"    result ",[97,692,249],{"class":218},[97,694,695],{"class":110}," runner.invoke(app, [",[97,697,698],{"class":127},"\"--help\"",[97,700,701],{"class":110},"])\n",[97,703,704,707,710,713],{"class":99,"line":171},[97,705,706],{"class":218},"    assert",[97,708,709],{"class":110}," result.exit_code ",[97,711,712],{"class":218},"==",[97,714,715],{"class":231}," 0\n",[97,717,718,720,723,726],{"class":99,"line":195},[97,719,706],{"class":218},[97,721,722],{"class":127}," \"deploy\"",[97,724,725],{"class":218}," in",[97,727,728],{"class":110}," result.stdout\n",[97,730,731],{"class":99,"line":318},[97,732,168],{"emptyLinePlaceholder":167},[97,734,735,737,740],{"class":99,"line":324},[97,736,281],{"class":218},[97,738,739],{"class":114}," test_rollout_runs_through_the_host",[97,741,742],{"class":110},"(monkeypatch):\n",[97,744,745,748,750],{"class":99,"line":329},[97,746,747],{"class":110},"    seen ",[97,749,249],{"class":218},[97,751,752],{"class":110}," {}\n",[97,754,755,758,761,764,767,770],{"class":99,"line":344},[97,756,757],{"class":110},"    monkeypatch.setattr(",[97,759,760],{"class":127},"\"mytool_deploy.cli.core.rollout\"",[97,762,763],{"class":110},", ",[97,765,766],{"class":218},"lambda",[97,768,769],{"class":218}," **",[97,771,772],{"class":110},"kw: seen.update(kw))\n",[97,774,775],{"class":99,"line":355},[97,776,168],{"emptyLinePlaceholder":167},[97,778,779,781,783,785,787,789,792,794,797,799,802,804,807],{"class":99,"line":366},[97,780,690],{"class":110},[97,782,249],{"class":218},[97,784,695],{"class":110},[97,786,407],{"class":127},[97,788,763],{"class":110},[97,790,791],{"class":127},"\"rollout\"",[97,793,763],{"class":110},[97,795,796],{"class":127},"\"prod\"",[97,798,763],{"class":110},[97,800,801],{"class":127},"\"--replicas\"",[97,803,763],{"class":110},[97,805,806],{"class":127},"\"5\"",[97,808,701],{"class":110},[97,810,811],{"class":99,"line":371},[97,812,168],{"emptyLinePlaceholder":167},[97,814,815,817,819,821],{"class":99,"line":380},[97,816,706],{"class":218},[97,818,709],{"class":110},[97,820,712],{"class":218},[97,822,715],{"class":231},[97,824,825,827,830,832,835,838,841,843,845,848,850,853],{"class":99,"line":396},[97,826,706],{"class":218},[97,828,829],{"class":110}," seen ",[97,831,712],{"class":218},[97,833,834],{"class":110}," {",[97,836,837],{"class":127},"\"environment\"",[97,839,840],{"class":110},": ",[97,842,796],{"class":127},[97,844,763],{"class":110},[97,846,847],{"class":127},"\"replicas\"",[97,849,840],{"class":110},[97,851,852],{"class":231},"5",[97,854,855],{"class":110},"}\n",[10,857,858,859,862],{},"For that to work, the test environment needs both packages installed — the host as a dependency and\nyour plugin editable, which ",[60,860,861],{},"uv sync"," gives you when the plugin is the project. Add the oldest\nsupported host version to your CI matrix; that is the test that catches a contract change you would\notherwise meet in a user's bug report.",[14,864,866],{"id":865},"conclusion","Conclusion",[10,868,869],{},"A plugin is a small package with one line of metadata. The work is not in the mechanism but in the\ndiscipline around it: a version range, a declared API version, documented seams only, and\nconventions borrowed from the host. Do those four and your plugin keeps working through releases\nyou were not part of.",[14,871,873],{"id":872},"frequently-asked-questions","Frequently asked questions",[875,876,878],"h3",{"id":877},"why-does-the-host-not-see-my-plugin","Why does the host not see my plugin?",[10,880,881,882,63,884,886,887,67],{},"Almost always the environment. An isolated host install has its own virtual environment, and a\nplugin installed elsewhere is invisible to it — use ",[60,883,62],{},[60,885,66],{},". After\nthat, check the group name character by character, and confirm the entry point resolves with\n",[60,888,889],{},"python -c \"from mytool_deploy.cli import plugin\"",[875,891,893],{"id":892},"can-a-plugin-override-a-built-in-command","Can a plugin override a built-in command?",[10,895,896],{},"Most hosts refuse, and rightly. If yours allows it, do not: users expect a documented command to do\nwhat the documentation says, and a plugin silently replacing it is the hardest kind of bug to\ndiagnose. Add a distinct command instead.",[875,898,900],{"id":899},"should-the-plugin-depend-on-the-host-at-all","Should the plugin depend on the host at all?",[10,902,903],{},"Yes, with a range. You import its types and extension points, so the dependency is real, and\ndeclaring it means the resolver refuses an incompatible combination rather than letting a user\ndiscover it at run time.",[875,905,907],{"id":906},"how-do-i-develop-against-an-unreleased-host-version","How do I develop against an unreleased host version?",[10,909,910],{},"Install both editable in one environment: the host from a checkout, your plugin from yours. That is\nalso the arrangement for contributing an extension point upstream — you can develop both sides in\none loop and split the changes into two pull requests afterwards.",[875,912,914],{"id":913},"what-if-the-host-has-no-plugin-system","What if the host has no plugin system?",[10,916,917],{},"Then your options are a wrapper CLI that shells out, or a pull request adding an extension point.\nThe wrapper is quick and fragile — it depends on the host's output format and flags — so if the\nhost is maintained, proposing an entry-point group is usually the better investment.",[875,919,921],{"id":920},"how-should-a-plugin-be-versioned-relative-to-the-host","How should a plugin be versioned relative to the host?",[10,923,924],{},"Independently. Your plugin has its own release cycle and its own semantic version; the relationship\nto the host is expressed by the dependency range, not by matching numbers. Matching versions looks\ntidy for one release and becomes a lie the moment either side needs a patch the other does not.",[875,926,928],{"id":927},"can-two-plugins-depend-on-incompatible-versions-of-a-shared-library","Can two plugins depend on incompatible versions of a shared library?",[10,930,931,932,934],{},"Yes, and it is the failure mode isolated installs make visible: ",[60,933,62],{}," resolves both into\none environment, so a genuine conflict surfaces at install time rather than at run time. That is\nthe right place for it — but it means keeping your own dependency ranges wide enough to coexist\nwith other plugins is part of being a good citizen.",[14,936,938],{"id":937},"related","Related",[19,940,941,949,956,962,969],{},[22,942,943,944],{},"Up: ",[945,946,948],"a",{"href":947},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002F","Plugin architectures for extensible CLIs",[22,950,951,952],{},"Sideways: ",[945,953,955],{"href":954},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points\u002F","Best practices for Python CLI entry points",[22,957,951,958],{},[945,959,961],{"href":960},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands\u002F","Dependency injection patterns for CLI commands",[22,963,964,965],{},"Related: ",[945,966,968],{"href":967},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx\u002F","Installing and distributing CLIs with pipx",[22,970,964,971],{},[945,972,974],{"href":973},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002F","Packaging Python CLIs for distribution",[976,977,978],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":93,"searchDepth":107,"depth":107,"links":980},[981,982,983,984,985,986,987,988,989,990,999],{"id":16,"depth":107,"text":17},{"id":70,"depth":107,"text":71},{"id":81,"depth":107,"text":82},{"id":419,"depth":107,"text":420},{"id":502,"depth":107,"text":503},{"id":549,"depth":107,"text":550},{"id":582,"depth":107,"text":583},{"id":616,"depth":107,"text":617},{"id":865,"depth":107,"text":866},{"id":872,"depth":107,"text":873,"children":991},[992,993,994,995,996,997,998],{"id":877,"depth":121,"text":878},{"id":892,"depth":121,"text":893},{"id":899,"depth":121,"text":900},{"id":906,"depth":121,"text":907},{"id":913,"depth":121,"text":914},{"id":920,"depth":121,"text":921},{"id":927,"depth":121,"text":928},{"id":937,"depth":107,"text":938},"2026-08-01","Extend someone else’s Python CLI - declare an entry point, add commands from your own package, version the contract, and test against the host.","advanced",false,"md",{},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fwriting-a-plugin-for-an-existing-cli",{"title":5,"description":1001},"modern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fwriting-a-plugin-for-an-existing-cli\u002Findex",[1010,180,1011,1012],"plugins","packaging","extensibility","u1ROvPPw_0LFHuPtJ17ymmUsmehyQZ_5EewnqeqTXMM",[1015,1018,1021,1024,1027,1030,1033,1036,1039,1042,1045,1048,1051,1054,1057,1060,1063,1066,1069,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1138,1141,1144,1147,1150,1153,1156,1159,1162,1165,1168,1171,1174,1177,1180,1183,1186,1189,1192,1195,1198,1201,1204,1207,1210,1213,1216,1219,1222,1225,1228,1231,1234,1237,1240,1243,1246,1249,1252,1255,1258,1261,1264,1267,1270,1273,1276,1279,1282,1285,1288,1291,1294,1297,1300,1303,1306,1309,1312,1315,1318,1321,1324,1327,1330,1333,1336,1339,1342,1345,1348,1351,1354,1357,1360,1363,1366,1369,1372,1375,1378,1381,1384,1387,1390,1393,1396,1399,1402,1405,1408,1411,1414,1417,1420,1423,1426,1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1538,1540,1543,1546,1549,1552,1555,1558,1561,1564,1567,1570,1573,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1705,1708,1711,1714,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855],{"path":1016,"title":1017},"\u002Fabout","About Python CLI Toolcraft",{"path":1019,"title":1020},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1022,"title":1023},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1025,"title":1026},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dates-and-durations-in-cli-arguments","Validating Dates and Durations in Python CLI Arguments",{"path":1028,"title":1029},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-dependent-and-conflicting-options","Validating Dependent and Conflicting CLI Options",{"path":1031,"title":1032},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1034,"title":1035},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-urls-hosts-and-ports","Validating URLs, Hosts and Ports in Python CLI Arguments",{"path":1037,"title":1038},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fwriting-custom-click-parameter-types","Writing Custom Click Parameter Types",{"path":1040,"title":1041},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbrowsing-records-with-a-textual-datatable","Browsing Records with a Textual DataTable Picker",{"path":1043,"title":1044},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fbuilding-your-first-textual-app","Building Your First Textual App for a Python CLI",{"path":1046,"title":1047},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fchoosing-between-a-cli-a-prompt-flow-and-a-tui","Choosing Between a CLI, a Prompt Flow and a TUI",{"path":1049,"title":1050},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual","Building Terminal UIs with Textual for Python CLIs",{"path":1052,"title":1053},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Frunning-background-work-in-textual-with-workers","Running Background Work in Textual with Workers",{"path":1055,"title":1056},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Fstyling-textual-apps-with-tcss","Styling Textual Apps with TCSS",{"path":1058,"title":1059},"\u002Fadvanced-input-parsing-user-experience\u002Fbuilding-terminal-uis-with-textual\u002Ftesting-textual-apps-with-pilot","Testing Textual Apps with Pilot",{"path":1061,"title":1062},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fadding-examples-and-epilogs-to-help-output","Adding Examples and Epilogs to Help Output",{"path":1064,"title":1065},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fdocumenting-environment-variables-in-help","Documenting Environment Variables in CLI Help",{"path":1067,"title":1068},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fgenerating-man-pages-and-docs-from-a-cli","Generating Man Pages and Docs from a CLI",{"path":1070,"title":1071},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1073,"title":1074},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Frich-formatted-help-with-rich-click","Rich-Formatted Help for Click CLIs with rich-click",{"path":1076,"title":1077},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1079,"title":1080},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1082,"title":1083},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fadapting-output-to-terminal-width","Adapting Python CLI Output to Terminal Width",{"path":1085,"title":1086},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fdetecting-ci-environments-and-non-interactive-shells","Detecting CI Environments and Non-Interactive Shells",{"path":1088,"title":1089},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Ffixing-unicode-and-encoding-errors-on-windows","Fixing Unicode and Encoding Errors on Windows in Python CLIs",{"path":1091,"title":1092},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fhandling-ansi-escape-codes-on-windows-consoles","Handling ANSI Escape Codes on Windows Consoles",{"path":1094,"title":1095},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility","Cross-Platform Terminal Compatibility for Python CLIs",{"path":1097,"title":1098},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Frespecting-no-color-and-force-color","Respecting NO_COLOR and FORCE_COLOR in Python CLIs",{"path":1100,"title":1101},"\u002Fadvanced-input-parsing-user-experience\u002Fcross-platform-terminal-compatibility\u002Fsupporting-dumb-terminals-and-screen-readers","Supporting Dumb Terminals and Screen Readers in a Python CLI",{"path":1103,"title":1104},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":1106,"title":1107},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fdesigning-an-exception-hierarchy-for-a-cli","Designing an Exception Hierarchy for a Python CLI",{"path":1109,"title":1110},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fdid-you-mean-suggestions-for-mistyped-input","Did You Mean…? Suggestions for Mistyped CLI Input",{"path":1112,"title":1113},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1115,"title":1116},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1118,"title":1119},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1121,"title":1122},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Freporting-machine-readable-errors-in-json-mode","Reporting Machine-Readable Errors in JSON Mode",{"path":1124,"title":1125},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fwriting-crash-reports-users-can-send","Writing Crash Reports Users Can Send",{"path":1127,"title":1128},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1130,"title":1131},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fdiscovering-project-config-files-by-walking-up-directories","Discovering Project Config Files by Walking Up Directories",{"path":1133,"title":1134},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1136,"title":1137},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps","Loading YAML configs safely in CLI apps",{"path":1139,"title":1140},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Freading-toml-config-with-tomllib","Reading TOML Config with tomllib in Python CLIs",{"path":1142,"title":1143},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Ftyped-settings-with-pydantic-settings","Typed Settings with pydantic-settings in Python CLIs",{"path":1145,"title":1146},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fvalidating-config-files-with-json-schema","Validating Config Files with JSON Schema in a Python CLI",{"path":1148,"title":1149},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fwriting-a-config-init-and-edit-command","Writing a Config Init and Edit Command for a Python CLI",{"path":1151,"title":1152},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1154,"title":1155},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fadding-progress-bars-and-spinners-to-python-clis","Progress Bars and Spinners for Python CLIs",{"path":1157,"title":1158},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-interactive-prompts-and-menus","Building Interactive Prompts and Menus in Python CLIs",{"path":1160,"title":1161},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fbuilding-tree-views-with-rich","Building Tree Views with Rich in a Python CLI",{"path":1163,"title":1164},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1166,"title":1167},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Flive-dashboards-with-rich-live","Live Dashboards with Rich Live in Python CLIs",{"path":1169,"title":1170},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-markdown-and-syntax-highlighting-with-rich","Rendering Markdown and Syntax Highlighting with Rich",{"path":1172,"title":1173},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1175,"title":1176},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Ftheming-rich-output-consistently","Theming Rich Output Consistently in Python CLIs",{"path":1178,"title":1179},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fadding-a-format-flag-for-table-json-and-csv","Adding a Format Flag for Table, JSON and CSV Output",{"path":1181,"title":1182},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fcustom-output-templates-with-a-format-string","Custom Output Templates with a Format String in Python CLIs",{"path":1184,"title":1185},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fexporting-cli-results-to-files","Exporting CLI Results to Files from a Python CLI",{"path":1187,"title":1188},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis","Output Formats for Data-Heavy Python CLIs",{"path":1190,"title":1191},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fselecting-fields-and-columns-from-cli-output","Selecting Fields and Columns from Python CLI Output",{"path":1193,"title":1194},"\u002Fadvanced-input-parsing-user-experience\u002Foutput-formats-for-data-clis\u002Fwriting-csv-and-tsv-output-correctly","Writing CSV and TSV Output Correctly from a Python CLI",{"path":1196,"title":1197},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fargcomplete-for-argparse-clis","Tab Completion for argparse CLIs with argcomplete",{"path":1199,"title":1200},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fdynamic-completion-values-from-apis-and-files","Dynamic Completion Values from APIs and Files",{"path":1202,"title":1203},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fenabling-tab-completion-in-click-and-typer","Enabling Tab Completion in Click and Typer",{"path":1205,"title":1206},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1208,"title":1209},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Finstalling-shell-completion-for-bash-zsh-fish","Installing Shell Completion for bash, zsh, fish",{"path":1211,"title":1212},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fshipping-completion-scripts-with-packages","Shipping Shell Completion Scripts with a Python CLI",{"path":1214,"title":1215},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Ftesting-shell-completion-in-python-clis","Testing Shell Completion in Python CLIs",{"path":1217,"title":1218},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-trace-ids-and-context-to-cli-logs","Adding Trace IDs and Context to Python CLI Logs",{"path":1220,"title":1221},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1223,"title":1224},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1226,"title":1227},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Flogging-with-structlog-in-a-cli","Logging with structlog in a Python CLI",{"path":1229,"title":1230},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fseparating-logs-from-program-output","Separating Logs from Program Output in a Python CLI",{"path":1232,"title":1233},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1235,"title":1236},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fwriting-rotating-log-files-from-a-cli","Writing Rotating Log Files from a Python CLI",{"path":1238,"title":1239},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1241,"title":1242},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1244,"title":1245},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1247,"title":1248},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1250,"title":1251},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fnull-delimited-input-and-xargs-compatibility","Null-Delimited Input and xargs Compatibility in Python CLIs",{"path":1253,"title":1254},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fprocessing-large-files-and-ndjson-streams","Processing Large Files and NDJSON Streams in Python CLIs",{"path":1256,"title":1257},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":1259,"title":1260},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fbuilding-an-api-client-cli-with-httpx","Building an API Client CLI with httpx",{"path":1262,"title":1263},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fdownloading-files-with-progress-in-python","Downloading Files with Progress in Python CLIs",{"path":1265,"title":1266},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis","Calling HTTP APIs from Python CLIs",{"path":1268,"title":1269},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fmocking-http-in-cli-tests-with-respx","Mocking HTTP in Python CLI Tests with respx",{"path":1271,"title":1272},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Foauth-device-flow-login-for-clis","OAuth Device Flow Login for Python CLIs",{"path":1274,"title":1275},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fpaginating-api-results-in-a-cli","Paginating API Results in a Python CLI",{"path":1277,"title":1278},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fretries-and-backoff-for-cli-http-calls","Retries and Backoff for CLI HTTP Calls",{"path":1280,"title":1281},"\u002Fcli-runtime-systems-integration\u002Fcalling-http-apis-from-python-clis\u002Fuploading-files-with-multipart-and-progress","Uploading Files with Multipart and Progress in a Python CLI",{"path":1283,"title":1284},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fcancelling-async-tasks-on-ctrl-c","Cancelling Async Tasks on Ctrl+C in Python CLIs",{"path":1286,"title":1287},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis","Concurrency and Async in Python CLIs",{"path":1289,"title":1290},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fmultiprocessing-for-cpu-bound-cli-tasks","Multiprocessing for CPU-Bound CLI Tasks",{"path":1292,"title":1293},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fparallelising-cli-work-with-thread-pools","Parallelising CLI Work with Thread Pools",{"path":1295,"title":1296},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frate-limiting-concurrent-requests-in-clis","Rate-Limiting Concurrent Requests in Python CLIs",{"path":1298,"title":1299},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Frunning-async-code-in-typer-and-click","Running Async Code in Typer and Click",{"path":1301,"title":1302},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fshowing-progress-for-concurrent-tasks","Showing Progress for Concurrent Tasks in a Python CLI",{"path":1304,"title":1305},"\u002Fcli-runtime-systems-integration\u002Fconcurrency-and-async-in-python-clis\u002Fstructured-concurrency-with-taskgroups-in-clis","Structured Concurrency with TaskGroups in Python CLIs",{"path":1307,"title":1308},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fcross-platform-paths-with-pathlib","Cross-Platform Paths with pathlib in CLIs",{"path":1310,"title":1311},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Ffile-locking-for-concurrent-cli-runs","File Locking for Concurrent CLI Runs in Python",{"path":1313,"title":1314},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fhandling-file-permissions-and-umask-in-clis","Handling File Permissions and umask in Python CLIs",{"path":1316,"title":1317},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes","Filesystem Paths and Atomic Writes for CLIs",{"path":1319,"title":1320},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fsafe-temporary-files-and-directories","Safe Temporary Files and Directories in CLIs",{"path":1322,"title":1323},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fstoring-app-data-with-platformdirs","Storing CLI App Data with platformdirs",{"path":1325,"title":1326},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwalking-directory-trees-with-ignore-rules","Walking Directory Trees with Ignore Rules in a Python CLI",{"path":1328,"title":1329},"\u002Fcli-runtime-systems-integration\u002Ffilesystem-paths-and-atomic-writes\u002Fwriting-files-atomically-in-python-clis","Writing Files Atomically in Python CLIs",{"path":1331,"title":1332},"\u002Fcli-runtime-systems-integration","CLI Runtime & Systems Integration for Python",{"path":1334,"title":1335},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fcaching-http-responses-on-disk-in-a-cli","Caching HTTP Responses on Disk in a Python CLI",{"path":1337,"title":1338},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis","Local State and SQLite in Python CLIs",{"path":1340,"title":1341},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fmigrating-a-cli-sqlite-schema","Migrating a CLI’s SQLite Schema Between Releases",{"path":1343,"title":1344},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Frecording-and-querying-cli-run-history","Recording and Querying CLI Run History in SQLite",{"path":1346,"title":1347},"\u002Fcli-runtime-systems-integration\u002Flocal-state-and-sqlite-in-python-clis\u002Fstoring-cli-state-in-sqlite","Storing CLI State in SQLite with a Small Repository Class",{"path":1349,"title":1350},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fbuilding-a-watch-mode-with-watchfiles","Building a Watch Mode with watchfiles in Python",{"path":1352,"title":1353},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhandling-sigterm-and-graceful-shutdown","Handling SIGTERM and Graceful Shutdown in CLIs",{"path":1355,"title":1356},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fhealth-checks-and-heartbeats-for-long-running-clis","Health Checks and Heartbeats for Long-Running CLIs",{"path":1358,"title":1359},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis","Long-Running and Watch-Mode Python CLIs",{"path":1361,"title":1362},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Freloading-config-on-sighup","Reloading Configuration on SIGHUP in a Python CLI",{"path":1364,"title":1365},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Frunning-a-cli-as-a-systemd-service","Running a Python CLI as a systemd Service",{"path":1367,"title":1368},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Frunning-a-cli-on-a-schedule-with-cron-and-systemd","Running a Python CLI on a Schedule with cron and systemd",{"path":1370,"title":1371},"\u002Fcli-runtime-systems-integration\u002Flong-running-and-watch-mode-clis\u002Fsending-cli-logs-to-journald-and-syslog","Sending Python CLI Logs to journald and syslog",{"path":1373,"title":1374},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Favoiding-shell-injection-in-python-clis","Avoiding Shell Injection in Python CLIs",{"path":1376,"title":1377},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fcalling-external-commands-safely-with-subprocess","Calling External Commands Safely with subprocess",{"path":1379,"title":1380},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fhandling-subprocess-timeouts-and-exit-codes","Handling Subprocess Timeouts and Exit Codes",{"path":1382,"title":1383},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis","Running Subprocesses from Python CLIs",{"path":1385,"title":1386},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Flaunching-the-users-editor-from-a-cli","Launching the User’s Editor from a Python CLI",{"path":1388,"title":1389},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fpiping-between-subprocesses-in-python","Piping Between Subprocesses in Python Without a Shell",{"path":1391,"title":1392},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fstreaming-subprocess-output-in-real-time","Streaming Subprocess Output in Real Time",{"path":1394,"title":1395},"\u002Fcli-runtime-systems-integration\u002Frunning-subprocesses-from-python-clis\u002Fwrapping-git-and-other-tools-from-a-python-cli","Wrapping git and Other Tools from a Python CLI",{"path":1397,"title":1398},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis","Secrets and Credentials in Python CLIs",{"path":1400,"title":1401},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fprompting-for-passwords-securely","Prompting for Passwords Securely in Python CLIs",{"path":1403,"title":1404},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Freading-secrets-from-env-and-files","Reading Secrets from Env Vars and Files in CLIs",{"path":1406,"title":1407},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fredacting-secrets-from-cli-output-and-logs","Redacting Secrets from CLI Output and Logs",{"path":1409,"title":1410},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Frefreshing-expired-tokens-automatically","Refreshing Expired Tokens Automatically in a Python CLI",{"path":1412,"title":1413},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fstoring-tokens-with-keyring","Storing CLI Tokens Securely with keyring",{"path":1415,"title":1416},"\u002Fcli-runtime-systems-integration\u002Fsecrets-and-credentials-in-python-clis\u002Fsupporting-multiple-profiles-and-accounts","Supporting Multiple Profiles and Accounts in CLIs",{"path":1418,"title":1419},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fchecking-pypi-for-a-newer-version","Checking PyPI for a Newer Version of Your Python CLI",{"path":1421,"title":1422},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis","Update Checks, Upgrade Commands and Telemetry for Python CLIs",{"path":1424,"title":1425},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fopt-in-usage-telemetry-for-python-clis","Opt-In Usage Telemetry for Python CLIs Done Responsibly",{"path":1427,"title":1428},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fself-upgrading-a-cli-installed-with-pipx-or-uv","Self-Upgrading a Python CLI Installed with pipx or uv",{"path":1430,"title":1431},"\u002Fcli-runtime-systems-integration\u002Fupdate-checks-and-self-updating-clis\u002Fshowing-non-blocking-update-notices","Showing Non-Blocking Update Notices in a Python CLI",{"path":1433,"title":1434},"\u002F","Python CLI Toolcraft",{"path":1436,"title":1437},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cleo","Building a Python CLI with Cleo Command Classes",{"path":1439,"title":1440},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fbuilding-a-cli-with-cyclopts","Building a Type-Hinted Python CLI with Cyclopts",{"path":1442,"title":1443},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks","Beyond Click and Typer: Alternative Python CLI Frameworks",{"path":1445,"title":1446},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fquick-clis-from-functions-with-python-fire","Quick CLIs from Functions with Python Fire",{"path":1448,"title":1449},"\u002Fmodern-python-cli-frameworks-architecture\u002Falternative-python-cli-frameworks\u002Fusage-string-driven-clis-with-docopt-ng","Usage-String Driven Python CLIs with docopt-ng",{"path":1451,"title":1452},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Favoiding-import-time-side-effects","Avoiding Import-Time Side Effects in a Python CLI",{"path":1454,"title":1455},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fcaching-expensive-work-between-cli-runs","Caching Expensive Work Between Python CLI Runs",{"path":1457,"title":1458},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fguarding-startup-with-import-tests","Guarding CLI Startup with Import Tests",{"path":1460,"title":1461},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1463,"title":1464},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1466,"title":1467},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1469,"title":1470},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1472,"title":1473},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1475,"title":1476},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1478,"title":1479},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargument-groups-and-help-formatting-in-argparse","Argument Groups and Help Formatting in argparse",{"path":1481,"title":1482},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1484,"title":1485},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1487,"title":1488},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmutually-exclusive-options-in-argparse","Mutually Exclusive Options in argparse",{"path":1490,"title":1491},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Freading-arguments-from-files-with-fromfile-prefix-chars","Reading Arguments from Files with argparse’s fromfile_prefix_chars",{"path":1493,"title":1494},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fwriting-custom-argparse-actions","Writing Custom argparse Actions in Python",{"path":1496,"title":1497},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fadding-dry-run-and-confirmation-to-destructive-commands","Adding Dry-Run and Confirmation to Destructive Commands",{"path":1499,"title":1500},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fdesigning-idempotent-commands","Designing Idempotent Commands in a Python CLI",{"path":1502,"title":1503},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Ffollowing-posix-and-gnu-argument-conventions","Following POSIX and GNU Argument Conventions in Python",{"path":1505,"title":1506},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fglobal-options-vs-per-command-options","Global Options vs Per-Command Options in Python CLIs",{"path":1508,"title":1509},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions","Designing CLI Interfaces and Conventions in Python",{"path":1511,"title":1512},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fnaming-commands-and-flags-consistently","Naming Commands and Flags Consistently in Python CLIs",{"path":1514,"title":1515},"\u002Fmodern-python-cli-frameworks-architecture\u002Fdesigning-cli-interfaces-and-conventions\u002Fpositional-arguments-vs-options","Positional Arguments vs Options: Designing a CLI Signature",{"path":1517,"title":1518},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1520,"title":1521},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fdiscovering-plugins-with-entry-points","Discovering Plugins with Entry Points in Python CLIs",{"path":1523,"title":1524},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fhook-based-plugins-with-pluggy","Hook-Based Plugins for Python CLIs with pluggy",{"path":1526,"title":1527},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1529,"title":1530},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fisolating-plugin-failures","Isolating Plugin Failures in an Extensible Python CLI",{"path":1532,"title":1533},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Ftesting-plugins-against-the-host-cli","Testing Plugins Against the Host CLI",{"path":1535,"title":1536},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fversioning-a-plugin-api","Versioning a Plugin API for a Python CLI",{"path":1006,"title":5},{"path":1539,"title":955},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points",{"path":1541,"title":1542},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1544,"title":1545},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fhow-to-structure-a-large-python-cli-project","Structuring a Large Python CLI Project",{"path":1547,"title":1548},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1550,"title":1551},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Foffering-a-python-api-alongside-your-cli","Offering a Python API Alongside Your CLI",{"path":1553,"title":1554},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fregistering-commands-from-modules-automatically","Registering CLI Commands from Modules Automatically",{"path":1556,"title":1557},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-common-options-across-commands","Sharing Common Options Across Python CLI Commands",{"path":1559,"title":1560},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1562,"title":1563},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fend-to-end-testing-an-installed-cli","End-to-End Testing an Installed Python CLI",{"path":1565,"title":1566},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1568,"title":1569},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1571,"title":1572},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmocking-filesystem-and-network-in-cli-tests","Mocking the Filesystem and Network in CLI Tests",{"path":1574,"title":1575},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fproperty-based-testing-cli-arguments-with-hypothesis","Property-Based Testing CLI Arguments with Hypothesis",{"path":1577,"title":1578},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1580,"title":1581},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1583,"title":1584},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-commands-that-call-subprocesses","Testing Python CLI Commands That Call Subprocesses",{"path":1586,"title":1587},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1589,"title":1590},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftranscript-tests-for-cli-commands","Transcript Tests for Python CLI Commands",{"path":1592,"title":1593},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-a-cli-with-subcommands-in-click","Building a CLI with subcommands in Click",{"path":1595,"title":1596},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-dynamic-commands-in-click","Building Dynamic Commands in Click",{"path":1598,"title":1599},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fchoices-and-enums-in-typer-and-click","Choices and Enums in Typer and Click Options",{"path":1601,"title":1602},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fclick-option-callbacks-and-eager-options","Click Option Callbacks and Eager Options Explained",{"path":1604,"title":1605},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fconverting-a-click-app-to-typer","Converting a Click App to Typer",{"path":1607,"title":1608},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1610,"title":1611},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Frich-markup-and-help-panels-in-typer","Rich Markup and Help Panels in Typer",{"path":1613,"title":1614},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1616,"title":1617},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fusing-annotated-options-in-typer","Using Annotated Options in Typer",{"path":1619,"title":1620},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fautomating-releases-from-git-tags","Automating Python CLI Releases from Git Tags",{"path":1622,"title":1623},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fcaching-uv-dependencies-in-ci","Caching uv Dependencies in CI for Python CLIs",{"path":1625,"title":1626},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis","CI\u002FCD Pipelines for Python CLIs",{"path":1628,"title":1629},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fpublishing-a-cli-docker-image-from-ci","Publishing a Python CLI as a Docker Image from CI",{"path":1631,"title":1632},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fpublishing-to-pypi-with-trusted-publishing","Publishing a CLI to PyPI with Trusted Publishing",{"path":1634,"title":1635},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Frunning-cli-tests-on-windows-and-macos-runners","Running Python CLI Tests on Windows and macOS Runners",{"path":1637,"title":1638},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Fsmoke-testing-the-built-wheel-in-ci","Smoke-Testing the Built Wheel of a Python CLI in CI",{"path":1640,"title":1641},"\u002Fproject-setup-dependency-management\u002Fci-cd-pipelines-for-python-clis\u002Ftesting-a-cli-across-python-versions-with-github-actions","Testing a CLI Across Python Versions in GitHub Actions",{"path":1643,"title":1644},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fbuilding-a-cookiecutter-template-for-typer-clis","Building a Cookiecutter Template for Typer CLIs",{"path":1646,"title":1647},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1649,"title":1650},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1652,"title":1653},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fpost-generation-hooks-in-cli-templates","Post-Generation Hooks in Python CLI Templates",{"path":1655,"title":1656},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftemplate-variables-and-conditional-files","Template Variables and Conditional Files in CLI Templates",{"path":1658,"title":1659},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Ftesting-a-project-template-with-pytest","Testing a CLI Project Template with pytest",{"path":1661,"title":1662},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fupdating-generated-projects-with-copier-update","Updating Generated CLI Projects with copier update",{"path":1664,"title":1665},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1667,"title":1668},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1670,"title":1671},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fcode-signing-and-notarizing-cli-binaries","Code Signing and Notarizing Python CLI Binaries",{"path":1673,"title":1674},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1676,"title":1677},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1679,"title":1680},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fnuitka-vs-pyinstaller-for-python-clis","Nuitka vs PyInstaller for Python CLI Binaries",{"path":1682,"title":1683},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Freducing-pyinstaller-binary-size","Reducing the Size of a PyInstaller CLI Binary",{"path":1685,"title":1686},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fshipping-a-cli-as-a-zipapp-with-shiv","Shipping a CLI as a Zipapp with shiv",{"path":1688,"title":1689},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1691,"title":1692},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fconfiguring-ruff-for-a-cli-project","Configuring Ruff for a Python CLI Project",{"path":1694,"title":1695},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Fenforcing-import-boundaries-in-a-cli-codebase","Enforcing Import Boundaries in a Python CLI Codebase",{"path":1697,"title":1698},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Ffinding-unused-code-and-dependencies-with-vulture-and-deptry","Finding Unused Code and Dependencies with vulture and deptry",{"path":1700,"title":1701},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code","Linting and Type-Checking Python CLI Code",{"path":1703,"title":1704},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Frunning-pyright-in-strict-mode-on-a-cli","Running Pyright in Strict Mode on a Python CLI",{"path":1706,"title":1707},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Frunning-ruff-and-mypy-in-ci-with-annotations","Running Ruff and mypy in CI with Inline Annotations",{"path":1709,"title":1710},"\u002Fproject-setup-dependency-management\u002Flinting-and-type-checking-cli-code\u002Ftype-checking-click-and-typer-code-with-mypy","Type-Checking Click and Typer Code with mypy",{"path":1712,"title":1713},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1715,"title":1716},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fbumping-versions-consistently-across-a-cli-project","Bumping Versions Consistently Across a Python CLI Project",{"path":1718,"title":1719},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fderiving-versions-from-git-tags-with-hatch-vcs","Deriving CLI Versions from Git Tags with hatch-vcs",{"path":1721,"title":1722},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1724,"title":1725},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1727,"title":1728},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fsemantic-versioning-policy-for-cli-tools","A Semantic Versioning Policy for CLI Tools",{"path":1730,"title":1731},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fshipping-pre-releases-and-release-candidates","Shipping Pre-Releases and Release Candidates of a Python CLI",{"path":1733,"title":1734},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1736,"title":1737},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbundling-data-files-with-importlib-resources","Bundling Data Files with importlib.resources in CLIs",{"path":1739,"title":1740},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fchoosing-a-build-backend-for-a-python-cli","Choosing a Build Backend for a Python CLI",{"path":1742,"title":1743},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1745,"title":1746},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1748,"title":1749},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Foptional-dependencies-and-extras-for-clis","Optional Dependencies and Extras for Python CLIs",{"path":1751,"title":1752},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1754,"title":1755},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fwriting-pyproject-toml-metadata-for-a-cli","Writing pyproject.toml Metadata for a Python CLI",{"path":1757,"title":1758},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fdynamic-versioning-with-poetry-plugins","Dynamic Versioning for a Poetry CLI from Git Tags",{"path":1760,"title":1761},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1763,"title":1764},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmanaging-poetry-lock-files-for-cli-tools","Managing Poetry Lock Files for CLI Tools",{"path":1766,"title":1767},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fmigrating-a-cli-from-poetry-to-uv","Migrating a Python CLI from Poetry to uv",{"path":1769,"title":1770},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-dependency-groups-for-cli-tooling","Poetry Dependency Groups for CLI Tooling",{"path":1772,"title":1773},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1775,"title":1776},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpublishing-a-cli-with-poetry","Building and Publishing a Python CLI with Poetry",{"path":1778,"title":1779},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1781,"title":1782},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fkeeping-hook-versions-current-with-autoupdate","Keeping pre-commit Hook Versions Current with autoupdate",{"path":1784,"title":1785},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Frunning-pre-commit-in-ci","Running pre-commit in CI for a Python CLI Repository",{"path":1787,"title":1788},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fsetting-up-pre-commit-for-python-cli-repos","Setting up pre-commit for Python CLI repos",{"path":1790,"title":1791},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fshipping-your-cli-as-a-pre-commit-hook","Shipping Your Python CLI as a pre-commit Hook",{"path":1793,"title":1794},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fspeeding-up-slow-pre-commit-hooks","Speeding Up Slow pre-commit Hooks in a CLI Repository",{"path":1796,"title":1797},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fwriting-local-pre-commit-hooks-in-python","Writing Local pre-commit Hooks in Python",{"path":1799,"title":1800},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fauditing-dependencies-with-pip-audit","Auditing a Python CLI’s Dependencies with pip-audit",{"path":1802,"title":1803},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fgenerating-an-sbom-for-a-python-cli","Generating an SBOM for a Python CLI Release",{"path":1805,"title":1806},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain","Securing the Supply Chain of a Python CLI",{"path":1808,"title":1809},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpinning-dependencies-with-hashes","Pinning a Python CLI’s Dependencies with Hashes",{"path":1811,"title":1812},"\u002Fproject-setup-dependency-management\u002Fsecuring-a-python-cli-supply-chain\u002Fpublishing-attestations-and-verifying-releases","Publishing Attestations and Verifying Python CLI Releases",{"path":1814,"title":1815},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fbuilding-and-publishing-a-cli-with-uv","Building and Publishing a Python CLI with uv",{"path":1817,"title":1818},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1820,"title":1821},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Flocking-and-syncing-cli-dependencies-with-uv","Locking and Syncing a Python CLI’s Dependencies with uv",{"path":1823,"title":1824},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Frunning-one-off-cli-scripts-with-uv-run","Running One-Off CLI Scripts with uv run and PEP 723",{"path":1826,"title":1827},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fusing-private-package-indexes-with-uv","Using Private Package Indexes with uv for Internal CLIs",{"path":1829,"title":1830},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-init-vs-poetry-init-for-cli-tools","uv init vs poetry init for CLI tools",{"path":1832,"title":1833},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis","uv tool install vs pipx for CLIs",{"path":1835,"title":1836},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-workspaces-for-multi-package-clis","uv Workspaces for Multi-Package Python CLIs",{"path":1838,"title":1839},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fdebugging-wrong-python-and-wrong-venv-problems","Debugging Wrong-Python and Wrong-Venv Problems in CLIs",{"path":1841,"title":1842},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fexternally-managed-environments-and-pep-668","PEP 668 and Python CLIs: the externally-managed-environment Error",{"path":1844,"title":1845},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1847,"title":1848},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fisolating-cli-tools-from-project-dependencies","Isolating CLI Tools from Your Project’s Dependencies",{"path":1850,"title":1851},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":1853,"title":1854},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",{"path":1856,"title":1857},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fsupporting-multiple-python-versions-with-nox","Supporting Multiple Python Versions with nox",1790967541375]