[{"data":1,"prerenderedAt":879},["ShallowReactive",2],{"page-\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates\u002F":3,"content-directory":632},{"id":4,"title":5,"body":6,"date":618,"description":619,"difficulty":620,"draft":621,"extension":622,"meta":623,"navigation":172,"path":624,"seo":625,"stem":626,"tags":627,"updated":618,"__hash__":631},"content\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates\u002Findex.md","Copier vs Cookiecutter for CLI Templates",{"type":7,"value":8,"toc":598},"minimark",[9,13,18,49,53,70,74,78,81,91,95,98,139,188,191,197,203,207,210,216,222,228,232,238,244,253,262,272,276,279,285,291,301,307,311,314,451,461,468,472,475,491,494,497,501,504,508,513,524,528,531,535,538,542,545,549,556,560,594],[10,11,12],"p",{},"Both tools render a directory of Jinja templates into a new project from a set of answers. The\ndifference that matters is what happens six months later, when the template gains a CI step that\nevery generated project should also have.",[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,29,40,43,46],"ul",{},[22,23,24,28],"li",{},[25,26,27],"strong",{},"Cookiecutter"," renders once and forgets: simple, ubiquitous, enormous ecosystem.",[22,30,31,34,35,39],{},[25,32,33],{},"Copier"," records the template version and answers in the project, so ",[36,37,38],"code",{},"copier update"," can\nreplay later template changes.",[22,41,42],{},"Choose by one question: will generated projects need to receive template updates?",[22,44,45],{},"For CLI projects, which usually diverge immediately, Cookiecutter is often enough.",[22,47,48],{},"Whichever you pick, give the template CI that generates a project and runs its tests.",[14,50,52],{"id":51},"prerequisites","Prerequisites",[10,54,55,56,59,60,63,64,69],{},"A project layout worth templating — the ",[36,57,58],{},"src\u002F"," tree, ",[36,61,62],{},"pyproject.toml",", pre-commit configuration and\nCI workflow described in\n",[65,66,68],"a",{"href":67},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002F","CLI project scaffolding",".",[14,71,73],{"id":72},"the-difference-in-one-diagram","The difference in one diagram",[75,76],"inline-diagram",{"name":77},"template-update-flow",[10,79,80],{},"Cookiecutter's model is a stamp: answers go in, files come out, and the relationship ends there.\nThe generated project has no record of which template made it or which answers were given.",[10,82,83,84,87,88,90],{},"Copier writes a ",[36,85,86],{},".copier-answers.yml"," into the project containing the template URL, the version tag\nand every answer. That file is what makes ",[36,89,38],{}," possible: it re-renders the old version,\nre-renders the new one, and applies the difference to your project as a merge — conflicts included,\nexactly like a rebase.",[14,92,94],{"id":93},"side-by-side","Side by side",[75,96],{"name":97},"template-tool-matrix",[99,100,105],"pre",{"className":101,"code":102,"language":103,"meta":104,"style":104},"language-bash shiki shiki-themes github-light github-dark","# Cookiecutter\ncookiecutter gh:yourorg\u002Fpython-cli-template\n# project_name [My CLI]: deploy-tool\n# package_name [deploy_tool]:\n","bash","",[36,106,107,116,127,133],{"__ignoreMap":104},[108,109,112],"span",{"class":110,"line":111},"line",1,[108,113,115],{"class":114},"sJ8bj","# Cookiecutter\n",[108,117,119,123],{"class":110,"line":118},2,[108,120,122],{"class":121},"sScJk","cookiecutter",[108,124,126],{"class":125},"sZZnC"," gh:yourorg\u002Fpython-cli-template\n",[108,128,130],{"class":110,"line":129},3,[108,131,132],{"class":114},"# project_name [My CLI]: deploy-tool\n",[108,134,136],{"class":110,"line":135},4,[108,137,138],{"class":114},"# package_name [deploy_tool]:\n",[99,140,142],{"className":101,"code":141,"language":103,"meta":104,"style":104},"# Copier\ncopier copy gh:yourorg\u002Fpython-cli-template .\u002Fdeploy-tool\n# … same questions, plus .copier-answers.yml written into the project\n\n# a year later, in that project:\ncopier update\n",[36,143,144,149,163,168,174,180],{"__ignoreMap":104},[108,145,146],{"class":110,"line":111},[108,147,148],{"class":114},"# Copier\n",[108,150,151,154,157,160],{"class":110,"line":118},[108,152,153],{"class":121},"copier",[108,155,156],{"class":125}," copy",[108,158,159],{"class":125}," gh:yourorg\u002Fpython-cli-template",[108,161,162],{"class":125}," .\u002Fdeploy-tool\n",[108,164,165],{"class":110,"line":129},[108,166,167],{"class":114},"# … same questions, plus .copier-answers.yml written into the project\n",[108,169,170],{"class":110,"line":135},[108,171,173],{"emptyLinePlaceholder":172},true,"\n",[108,175,177],{"class":110,"line":176},5,[108,178,179],{"class":114},"# a year later, in that project:\n",[108,181,183,185],{"class":110,"line":182},6,[108,184,153],{"class":121},[108,186,187],{"class":125}," update\n",[10,189,190],{},"Two more practical differences follow from the design.",[10,192,193,196],{},[25,194,195],{},"Conditional files."," Cookiecutter renders everything and expects a post-generation hook to delete\nwhat was not wanted. Copier declares exclusions in the template itself, which keeps the logic\nvisible rather than hidden in a Python hook.",[10,198,199,202],{},[25,200,201],{},"Migrations."," Copier templates can declare migration scripts that run when updating across\nversions — useful when a change is not a simple file diff, such as renaming a configuration key in\nevery generated project.",[14,204,206],{"id":205},"which-to-choose","Which to choose",[75,208],{"name":209},"template-choice-decision",[10,211,212,215],{},[25,213,214],{},"Choose Copier"," when you maintain a fleet: five services from the same template, where a change to\nthe CI workflow or the linting configuration should reach all of them. The update mechanism is\ngenuinely valuable there, and it is worth writing templates with merges in mind.",[10,217,218,221],{},[25,219,220],{},"Choose Cookiecutter"," when generated projects diverge immediately, which is the normal case for\nCLI tools. Two tools generated from the same template look nothing alike after a month, so replaying\ntemplate changes into them is a merge conflict rather than a convenience — and Cookiecutter's larger\necosystem means more existing templates to start from.",[10,223,224,227],{},[25,225,226],{},"Choose neither"," if you start one project a year. A well-maintained example repository and ten\nminutes of copying is less machinery to own than a template nobody exercises.",[14,229,231],{"id":230},"writing-a-template-that-can-be-updated","Writing a template that can be updated",[10,233,234,235,237],{},"If you pick Copier, a few habits make ",[36,236,38],{}," pleasant rather than a merge nightmare.",[10,239,240,243],{},[25,241,242],{},"Keep generated files small and stable."," A four-hundred-line workflow file regenerated with one\nchanged line merges cleanly; a file that the project is expected to edit heavily does not.",[10,245,246,249,250,69],{},[25,247,248],{},"Mark files the project owns."," Anything a developer will rewrite immediately — the README body,\nthe main module — is better generated once and excluded from updates, which Copier supports with\n",[36,251,252],{},"skip_if_exists",[10,254,255,258,259,261],{},[25,256,257],{},"Version the template with tags."," ",[36,260,38],{}," moves between tags, so releases give you\nsomething to move between and something to describe in a changelog.",[10,263,264,267,268,271],{},[25,265,266],{},"Test the update path",", not just generation. A CI job that generates from the previous tag,\nupdates to ",[36,269,270],{},"HEAD"," and asserts the result still builds is what stops the update mechanism quietly\nbreaking.",[14,273,275],{"id":274},"ux-considerations","UX considerations",[10,277,278],{},"The people who use your template are colleagues on their first day of a project, which sets the bar\nfor the experience.",[10,280,281,284],{},[25,282,283],{},"Keep the questions few."," Every prompt is a branch in the output that somebody has to test. Three\nor four — name, package name, Python version, maybe a licence — cover most templates. Anything else\nshould have a sensible default and be editable afterwards.",[10,286,287,290],{},[25,288,289],{},"Validate answers before rendering."," A package name that is not a valid identifier produces a\nproject that cannot be imported. Both tools support validation; a pre-generation check that fails\nfast is much kinder than a half-made directory.",[10,292,293,296,297,300],{},[25,294,295],{},"Generate a first commit."," A post-generation hook running ",[36,298,299],{},"git init"," and committing the skeleton\nmeans the first real change shows up as a reviewable diff against a known baseline.",[10,302,303,306],{},[25,304,305],{},"Say what to do next."," A three-line message at the end — install, run the tests, run the CLI —\nturns a generated directory into something the reader can act on.",[14,308,310],{"id":309},"testing-the-behaviour","Testing the behaviour",[10,312,313],{},"Both tools deserve the same CI treatment: generate a project on every push and put it through the\nsteps a real one would face.",[99,315,319],{"className":316,"code":317,"language":318,"meta":104,"style":104},"language-yaml shiki shiki-themes github-light github-dark","jobs:\n  generate:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - run: pipx install copier          # or cookiecutter\n      - run: copier copy --defaults --trust . \u002Ftmp\u002Fout\n      - working-directory: \u002Ftmp\u002Fout\n        run: |\n          pipx install uv\n          uv sync\n          uv run pytest -q\n          uv build\n          uv run --isolated --with dist\u002F*.whl my-cli --version\n","yaml",[36,320,321,331,338,349,356,369,384,396,409,421,427,433,439,445],{"__ignoreMap":104},[108,322,323,327],{"class":110,"line":111},[108,324,326],{"class":325},"s9eBZ","jobs",[108,328,330],{"class":329},"sVt8B",":\n",[108,332,333,336],{"class":110,"line":118},[108,334,335],{"class":325},"  generate",[108,337,330],{"class":329},[108,339,340,343,346],{"class":110,"line":129},[108,341,342],{"class":325},"    runs-on",[108,344,345],{"class":329},": ",[108,347,348],{"class":125},"ubuntu-latest\n",[108,350,351,354],{"class":110,"line":135},[108,352,353],{"class":325},"    steps",[108,355,330],{"class":329},[108,357,358,361,364,366],{"class":110,"line":176},[108,359,360],{"class":329},"      - ",[108,362,363],{"class":325},"uses",[108,365,345],{"class":329},[108,367,368],{"class":125},"actions\u002Fcheckout@v4\n",[108,370,371,373,376,378,381],{"class":110,"line":182},[108,372,360],{"class":329},[108,374,375],{"class":325},"run",[108,377,345],{"class":329},[108,379,380],{"class":125},"pipx install copier",[108,382,383],{"class":114},"          # or cookiecutter\n",[108,385,387,389,391,393],{"class":110,"line":386},7,[108,388,360],{"class":329},[108,390,375],{"class":325},[108,392,345],{"class":329},[108,394,395],{"class":125},"copier copy --defaults --trust . \u002Ftmp\u002Fout\n",[108,397,399,401,404,406],{"class":110,"line":398},8,[108,400,360],{"class":329},[108,402,403],{"class":325},"working-directory",[108,405,345],{"class":329},[108,407,408],{"class":125},"\u002Ftmp\u002Fout\n",[108,410,412,415,417],{"class":110,"line":411},9,[108,413,414],{"class":325},"        run",[108,416,345],{"class":329},[108,418,420],{"class":419},"szBVR","|\n",[108,422,424],{"class":110,"line":423},10,[108,425,426],{"class":125},"          pipx install uv\n",[108,428,430],{"class":110,"line":429},11,[108,431,432],{"class":125},"          uv sync\n",[108,434,436],{"class":110,"line":435},12,[108,437,438],{"class":125},"          uv run pytest -q\n",[108,440,442],{"class":110,"line":441},13,[108,443,444],{"class":125},"          uv build\n",[108,446,448],{"class":110,"line":447},14,[108,449,450],{"class":125},"          uv run --isolated --with dist\u002F*.whl my-cli --version\n",[10,452,453,456,457,460],{},[36,454,455],{},"--defaults"," (Cookiecutter: ",[36,458,459],{},"--no-input",") forces the declared defaults to be genuinely valid rather\nthan placeholders somebody was meant to replace. The final line is the check that matters most: the\ngenerated project builds a wheel whose entry point actually runs.",[10,462,463,464,467],{},"For Copier, add the update path as a second job — generate from the previous tag, ",[36,465,466],{},"copier update --defaults",", then run the same steps.",[14,469,471],{"id":470},"what-belongs-in-a-cli-template","What belongs in a CLI template",[10,473,474],{},"Whichever tool renders it, the content is the same, and the useful principle is to include what\npeople forget rather than everything imaginable.",[10,476,477,478,480,481,483,484,487,488,69],{},"Worth generating: the ",[36,479,58],{}," layout with a working package and entry point; a ",[36,482,62],{}," with\nthe build backend, metadata, dependencies and ",[36,485,486],{},"[project.scripts]","; a dev dependency group; a\npre-commit configuration with pinned revisions; a test that imports the package and one that\ninvokes the CLI through the runner; a CI workflow that installs from the lockfile, runs the tests\nand smoke-tests the built wheel; a README with install and usage sections; and a ",[36,489,490],{},".gitignore",[10,492,493],{},"Worth leaving out: a Dockerfile nobody asked for, documentation scaffolding for a site that may\nnever exist, a changelog full of placeholder text, and licence headers in every file. Each is\nsomething the first developer deletes, and a template that requires deletion trains people to\ndistrust it.",[10,495,496],{},"The judgement call is dependency choices. A template that hard-codes Typer and Rich is opinionated\nin a way that saves an argument on day one and constrains a project that needed Click. Making that\na question is reasonable; making it five questions is not.",[14,498,500],{"id":499},"conclusion","Conclusion",[10,502,503],{},"Cookiecutter renders; Copier renders and remembers. If generated projects need to track the\ntemplate, that memory is worth the extra ceremony; if they diverge the moment they exist — which\nmost CLI projects do — the simpler tool is the right one. Either way, the template needs its own CI,\nbecause a template that is never generated is a template that is already broken.",[14,505,507],{"id":506},"frequently-asked-questions","Frequently asked questions",[509,510,512],"h3",{"id":511},"can-i-migrate-an-existing-cookiecutter-template-to-copier","Can I migrate an existing Cookiecutter template to Copier?",[10,514,515,516,519,520,523],{},"Yes, and it is mostly mechanical: ",[36,517,518],{},"cookiecutter.json"," becomes ",[36,521,522],{},"copier.yml",", the templated directory\nkeeps its Jinja syntax, and hooks become tasks. What needs thought is which files should be excluded\nfrom future updates, since that concept has no Cookiecutter equivalent.",[509,525,527],{"id":526},"does-copier-work-with-existing-cookiecutter-templates","Does Copier work with existing Cookiecutter templates?",[10,529,530],{},"Partially — Copier can consume Cookiecutter-style templates, but you lose the update capability\nunless the answers file is present, which older templates do not write. For anything you plan to\nmaintain, converting properly is the better path.",[509,532,534],{"id":533},"how-do-i-stop-a-template-going-stale","How do I stop a template going stale?",[10,536,537],{},"Generate from it in CI on every push and run the generated project's tests. It is the only reliable\nmechanism: a template exercised weekly fails in its own repository on a Tuesday, while one nobody\ngenerates fails on someone's first day.",[509,539,541],{"id":540},"should-the-template-pin-dependency-versions","Should the template pin dependency versions?",[10,543,544],{},"Pin the tooling it configures — pre-commit hook revisions, action versions, the minimum Python — and\nleave application dependency ranges open. Generate the lockfile in a post-generation hook rather\nthan committing a stale one into the template.",[509,546,548],{"id":547},"are-there-good-public-templates-to-start-from","Are there good public templates to start from?",[10,550,551,552,69],{},"Several, and the useful exercise is generating from two or three and comparing what they include.\nTreat them as a source of ideas rather than something to adopt wholesale: a template encodes its\nauthor's conventions, and the value comes from those conventions being ",[553,554,555],"em",{},"yours",[14,557,559],{"id":558},"related","Related",[19,561,562,568,575,581,588],{},[22,563,564,565],{},"Up: ",[65,566,567],{"href":67},"CLI project scaffolding with Cookiecutter",[22,569,570,571],{},"Sideways: ",[65,572,574],{"href":573},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002F","Pre-commit hooks for CLI projects",[22,576,570,577],{},[65,578,580],{"href":579},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002F","uv for Python CLI dependency management",[22,582,583,584],{},"Related: ",[65,585,587],{"href":586},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fhow-to-structure-a-large-python-cli-project\u002F","How to structure a large Python CLI project",[22,589,583,590],{},[65,591,593],{"href":592},"\u002Fproject-setup-dependency-management\u002F","Project Setup & Dependency Management",[595,596,597],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":104,"searchDepth":118,"depth":118,"links":599},[600,601,602,603,604,605,606,607,608,609,610,617],{"id":16,"depth":118,"text":17},{"id":51,"depth":118,"text":52},{"id":72,"depth":118,"text":73},{"id":93,"depth":118,"text":94},{"id":205,"depth":118,"text":206},{"id":230,"depth":118,"text":231},{"id":274,"depth":118,"text":275},{"id":309,"depth":118,"text":310},{"id":470,"depth":118,"text":471},{"id":499,"depth":118,"text":500},{"id":506,"depth":118,"text":507,"children":611},[612,613,614,615,616],{"id":511,"depth":129,"text":512},{"id":526,"depth":129,"text":527},{"id":533,"depth":129,"text":534},{"id":540,"depth":129,"text":541},{"id":547,"depth":129,"text":548},{"id":558,"depth":118,"text":559},"2026-08-01","Choose a Python project template tool - Cookiecutter renders once, Copier can update generated projects later, and one question decides which you need.","intermediate",false,"md",{},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates",{"title":5,"description":619},"project-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates\u002Findex",[122,153,628,629,630],"templates","scaffolding","tooling","YX-g8_rYJVPW8zDR-vJwFGZDooR57jOmTpmt8xI2WlM",[633,636,639,642,645,648,651,654,657,660,663,666,669,672,675,678,681,684,687,690,693,696,699,702,705,708,711,714,717,720,723,726,729,732,735,738,741,744,747,750,753,756,759,762,765,768,771,774,777,780,783,786,789,792,795,798,801,804,807,808,811,814,817,820,823,826,828,831,834,837,840,843,846,849,852,855,858,861,864,867,870,873,876],{"path":634,"title":635},"\u002Fabout","About Python CLI Toolcraft",{"path":637,"title":638},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":640,"title":641},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":643,"title":644},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":646,"title":647},"\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":649,"title":650},"\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":652,"title":653},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":655,"title":656},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":658,"title":659},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":661,"title":662},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools","Choosing Exit Codes for CLI Tools",{"path":664,"title":665},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":667,"title":668},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":670,"title":671},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":673,"title":674},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":676,"title":677},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":679,"title":680},"\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":682,"title":683},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":685,"title":686},"\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":688,"title":689},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":691,"title":692},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":694,"title":695},"\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":697,"title":698},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":700,"title":701},"\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":703,"title":704},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":706,"title":707},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":709,"title":710},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":712,"title":713},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":715,"title":716},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":718,"title":719},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":721,"title":722},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":724,"title":725},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":727,"title":728},"\u002F","Python CLI Toolcraft",{"path":730,"title":731},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":733,"title":734},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":736,"title":737},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":739,"title":740},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":742,"title":743},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":745,"title":746},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":748,"title":749},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":751,"title":752},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":754,"title":755},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":757,"title":758},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":760,"title":761},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fwriting-a-plugin-for-an-existing-cli","Writing a Plugin for an Existing CLI",{"path":763,"title":764},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points","Best practices for Python CLI entry points",{"path":766,"title":767},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":769,"title":770},"\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":772,"title":773},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":775,"title":776},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":778,"title":779},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":781,"title":782},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":784,"title":785},"\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":787,"title":788},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":790,"title":791},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":793,"title":794},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":796,"title":797},"\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":799,"title":800},"\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":802,"title":803},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":805,"title":806},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":624,"title":5},{"path":809,"title":810},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":812,"title":813},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":815,"title":816},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":818,"title":819},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":821,"title":822},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":824,"title":825},"\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":827,"title":593},"\u002Fproject-setup-dependency-management",{"path":829,"title":830},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":832,"title":833},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":835,"title":836},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":838,"title":839},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":841,"title":842},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":844,"title":845},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":847,"title":848},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":850,"title":851},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":853,"title":854},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":856,"title":857},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":859,"title":860},"\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":862,"title":863},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":865,"title":866},"\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":868,"title":869},"\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":871,"title":872},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":874,"title":875},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":877,"title":878},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",1785614690034]