- ID da verificação
- 11c1c76b-1699-49af-a053-1d4abc8d9749Concluído
- URL enviado:
- https://pydevtools.com/blog/effective-python-developer-tooling-in-december-2024/
- Relatório concluído:
Links · 21 encontrado(s)
Os links de saída identificados na página
Link | Texto |
---|---|
https://github.com/sponsors/python-developer-tooling-handbook | Support Me |
https://github.com/python-developer-tooling-handbook | Github |
https://github.com/tdhopper/python-developer-tooling-handbook/edit/main/content/blog/effective-python-developer-tooling-in-december-2024.md | Edit this page on GitHub → |
https://xkcd.com/1987/ | fragmentation |
https://peps.python.org/pep-0723/ | PEP 723 |
https://astral.sh/ruff | ruff |
https://cookiecutter.readthedocs.io | Cookiecutter |
https://pytest.org | pytest |
https://tdhopper.com/blog/no-silver-bullet/ | The hardest part of building software is figuring out what to build |
https://astral.sh | Astral |
Variáveis JavaScript · 11 encontrada(s)
Variáveis JavaScript globais carregadas no objeto janela de uma página são variáveis declaradas fora das funções e acessíveis de qualquer lugar no código dentro do escopo atual
Nome | Tipo |
---|---|
0 | object |
onbeforetoggle | object |
documentPictureInPicture | object |
onscrollend | object |
__mobxInstanceCount | number |
__mobxGlobals | object |
GoTrue | function |
netlifyIdentity | object |
scrollUp | function |
_factory | function |
Mensagens de registro do console · 0 encontrada(s)
Mensagens registradas no console web
HTML
O corpo HTML bruto da página
<!DOCTYPE html><html lang="en" style="color-scheme: light;"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="index, follow"><link rel="icon shortcut" href="/favicon.ico" sizes="32x32"><link rel="icon" href="/favicon.svg" type="image/svg+xml"><link rel="icon" href="/favicon-dark.svg" type="image/svg+xml" media="(prefers-color-scheme: dark)"><link rel="icon" href="/favicon-16x16.png" type="image/png" sizes="16x16"><link rel="icon" href="/favicon-32x32.png" type="image/png" sizes="32x32"><link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180"><link fetchpriority="low" href="/site.webmanifest" rel="manifest"><title>Effective Python Developer Tooling in December 2024 – Python Developer Tooling Handbook</title><meta name="description" content="I have been writing Python for 14 years next month. When I started, people were still using easy_install to install egg-based packages for Python 2.7 and nobody had heard about Conda yet, much less uv. Needless to say, the Python tool ecosystem has changed and developed since. Many people are rightfully confused by the fragmentation in the ecosystem; at the same time, we unequivocally have better tooling for Python today than we’ve ever had before."><meta property="og:title" content="Effective Python Developer Tooling in December 2024"><meta property="og:description" content="I have been writing Python for 14 years next month. When I started, people were still using easy_install to install egg-based packages for Python 2.7 and nobody had heard about Conda yet, much less uv. Needless to say, the Python tool ecosystem has changed and developed since. Many people are rightfully confused by the fragmentation in the ecosystem; at the same time, we unequivocally have better tooling for Python today than we’ve ever had before."><meta property="og:type" content="article"><meta property="og:url" content="https://pydevtools.com/blog/effective-python-developer-tooling-in-december-2024/"><meta property="og:image" content="https://pydevtools.com/images/logo-wide.png"><meta property="article:section" content="blog"><meta property="article:published_time" content="2024-12-20T19:25:00+00:00"><meta property="article:modified_time" content="2024-12-21T00:28:50+00:00"><meta itemprop="name" content="Effective Python Developer Tooling in December 2024"><meta itemprop="description" content="I have been writing Python for 14 years next month. When I started, people were still using easy_install to install egg-based packages for Python 2.7 and nobody had heard about Conda yet, much less uv. Needless to say, the Python tool ecosystem has changed and developed since. Many people are rightfully confused by the fragmentation in the ecosystem; at the same time, we unequivocally have better tooling for Python today than we’ve ever had before."><meta itemprop="datePublished" content="2024-12-20T19:25:00+00:00"><meta itemprop="dateModified" content="2024-12-21T00:28:50+00:00"><meta itemprop="wordCount" content="1605"><meta itemprop="image" content="https://pydevtools.com/images/logo-wide.png"><meta itemprop="keywords" content=""><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="https://pydevtools.com/images/logo-wide.png"><meta name="twitter:title" content="Effective Python Developer Tooling in December 2024"><meta name="twitter:description" content="I have been writing Python for 14 years next month. When I started, people were still using easy_install to install egg-based packages for Python 2.7 and nobody had heard about Conda yet, much less uv. Needless to say, the Python tool ecosystem has changed and developed since. Many people are rightfully confused by the fragmentation in the ecosystem; at the same time, we unequivocally have better tooling for Python today than we’ve ever had before."><link rel="preload" href="/css/compiled/main.min.687686a6b75962ba62d1f5c5d2931a3eb7b0f0efd3ce62dcd2c297d654dcf94f.css" as="style" integrity="sha256-aHaGprdZYrpi0fXF0pMaPrew8O/TzmLc0sKX1lTc+U8="><link href="/css/compiled/main.min.687686a6b75962ba62d1f5c5d2931a3eb7b0f0efd3ce62dcd2c297d654dcf94f.css" rel="stylesheet" integrity="sha256-aHaGprdZYrpi0fXF0pMaPrew8O/TzmLc0sKX1lTc+U8="><link href="/css/custom.min.e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css" rel="stylesheet" integrity="sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="><script>const defaultTheme="system",setDarkTheme=()=>{document.documentElement.classList.add("dark"),document.documentElement.style.colorScheme="dark"},setLightTheme=()=>{document.documentElement.classList.remove("dark"),document.documentElement.style.colorScheme="light"};"color-theme"in localStorage?localStorage.getItem("color-theme")==="dark"?setDarkTheme():setLightTheme():(defaultTheme==="dark"?setDarkTheme():setLightTheme(),defaultTheme==="system"&&(window.matchMedia("(prefers-color-scheme: dark)").matches?setDarkTheme():setLightTheme()))</script><link rel="stylesheet" href="/css/custom.css"><link rel="alternate" type="application/rss+xml" title="Python Developer Tooling Handbook" href="https://pydevtools.com/index.xml"><script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script></head><body dir="ltr"><div class="nav-container sticky top-0 z-20 w-full bg-transparent print:hidden"><div class="nav-container-blur pointer-events-none absolute z-[-1] h-full w-full bg-white dark:bg-dark shadow-[0_2px_4px_rgba(0,0,0,.02),0_1px_0_rgba(0,0,0,.06)] contrast-more:shadow-[0_0_0_1px_#000] dark:shadow-[0_-1px_0_rgba(255,255,255,.1)_inset] contrast-more:dark:shadow-[0_0_0_1px_#fff]"></div><nav class="mx-auto flex items-center justify-end gap-2 h-16 px-6 max-w-[90rem]"><a class="flex items-center hover:opacity-75 ltr:mr-auto rtl:ml-auto" href="/"><img class="block dark:hidden" src="/images/logo.png" alt="Python Developer Tooling Handbook" height="40" width="40">
<img class="hidden dark:block" src="/images/logo.png" alt="Python Developer Tooling Handbook" height="40" width="40">
<span class="mx-2 font-extrabold inline select-none" title="Python Developer Tooling Handbook">Python Developer Tooling Handbook</span>
</a><a title="Handbook" href="/handbook" class="text-sm contrast-more:text-gray-700 contrast-more:dark:text-gray-100 relative -ml-2 hidden whitespace-nowrap p-2 md:inline-block text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"><span class="text-center">Handbook</span>
</a><a title="Blog" href="/blog" class="text-sm contrast-more:text-gray-700 contrast-more:dark:text-gray-100 relative -ml-2 hidden whitespace-nowrap p-2 md:inline-block font-medium"><span class="text-center">Blog</span>
</a><a title="Contact" href="/feedback" class="text-sm contrast-more:text-gray-700 contrast-more:dark:text-gray-100 relative -ml-2 hidden whitespace-nowrap p-2 md:inline-block text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"><span class="text-center">Contact</span>
</a><a title="Support Me" href="https://github.com/sponsors/python-developer-tooling-handbook" target="_blank" rel="noreferer" class="text-sm contrast-more:text-gray-700 contrast-more:dark:text-gray-100 relative -ml-2 hidden whitespace-nowrap p-2 md:inline-block text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"><span class="text-center">Support Me</span>
</a><a class="p-2 text-current" target="_blank" rel="noreferer" href="https://github.com/python-developer-tooling-handbook" title="Github"><svg height="24" fill="currentcolor" viewBox="3 3 18 18"><path d="M12 3C7.0275 3 3 7.12937 3 12.2276c0 4.0833 2.57625 7.5321 6.15374 8.7548C9.60374 21.0631 9.77249 20.7863 9.77249 20.5441 9.77249 20.3249 9.76125 19.5982 9.76125 18.8254 7.5 19.2522 6.915 18.2602 6.735 17.7412 6.63375 17.4759 6.19499 16.6569 5.8125 16.4378 5.4975 16.2647 5.0475 15.838 5.80124 15.8264 6.51 15.8149 7.01625 16.4954 7.18499 16.7723 7.99499 18.1679 9.28875 17.7758 9.80625 17.5335 9.885 16.9337 10.1212 16.53 10.38 16.2993 8.3775 16.0687 6.285 15.2728 6.285 11.7432c0-1.0035.34875-1.834.92249-2.47994C7.1175 9.03257 6.8025 8.08674 7.2975 6.81794c0 0 .753749999999999-.24223 2.47499.94583.72001-.20762 1.48501-.31143 2.25001-.31143C12.7875 7.45234 13.5525 7.55615 14.2725 7.76377c1.7212-1.19959 2.475-.94583 2.475-.94583C17.2424 8.08674 16.9275 9.03257 16.8375 9.26326 17.4113 9.9092 17.76 10.7281 17.76 11.7432c0 3.5411-2.1037 4.3255-4.1063 4.5561C13.98 16.5877 14.2613 17.1414 14.2613 18.0065 14.2613 19.2407 14.25 20.2326 14.25 20.5441 14.25 20.7863 14.4188 21.0746 14.8688 20.9824 16.6554 20.364 18.2079 19.1866 19.3078 17.6162c1.0999-1.5705 1.6917-3.4551 1.6922-5.3886C21 7.12937 16.9725 3 12 3z"></path></svg><span class="sr-only">Github</span></a><div class="search-wrapper relative md:w-64"><div class="relative flex items-center text-gray-900 contrast-more:text-gray-800 dark:text-gray-300 contrast-more:dark:text-gray-300"><input placeholder="Search..." class="search-input block w-full appearance-none rounded-lg px-3 py-2 transition-colors text-base leading-tight md:text-sm bg-black/[.05] dark:bg-gray-50/10 focus:bg-white dark:focus:bg-dark placeholder:text-gray-500 dark:placeholder:text-gray-400 contrast-more:border contrast-more:border-current" type="search" spellcheck="false">
<kbd class="absolute my-1.5 select-none ltr:right-1.5 rtl:left-1.5 h-5 rounded bg-white px-1.5 font-mono text-[10px] font-medium text-gray-500 border dark:border-gray-100/20 dark:bg-dark/50 contrast-more:border-current contrast-more:text-current contrast-more:dark:border-current items-center gap-1 transition-opacity pointer-events-none hidden sm:flex">CTRL K</kbd></div><div><ul class="search-results hextra-scrollbar hidden border border-gray-200 bg-white text-gray-100 dark:border-neutral-800 dark:bg-neutral-900 absolute top-full z-20 mt-2 overflow-auto overscroll-contain rounded-xl py-2.5 shadow-xl max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] inset-x-0 ltr:md:left-auto rtl:md:right-auto contrast-more:border contrast-more:border-gray-900 contrast-more:dark:border-gray-50 w-screen min-h-[100px] max-w-[min(calc(100vw-2rem),calc(100%+20rem))]" style="transition:max-height .2s ease 0s"></ul></div></div><button type="button" aria-label="Menu" class="hamburger-menu -mr-2 rounded p-2 active:bg-gray-400/20 md:hidden"><svg height="24" fill="none" viewBox="0 0 24 24" stroke="currentcolor"><g><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8H20"></path></g><g><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16H20"></path></g></svg></button></nav></div><div class="mx-auto flex max-w-screen-xl"><div class="mobile-menu-overlay [transition:background-color_1.5s_ease] bg-transparent"></div><aside class="sidebar-container flex flex-col print:hidden md:top-16 md:shrink-0 md:w-64 md:self-start max-md:[transform:translate3d(0,-100%,0)] md:hidden xl:block"><div class="px-4 pt-4 md:hidden"><div class="search-wrapper relative md:w-64"><div class="relative flex items-center text-gray-900 contrast-more:text-gray-800 dark:text-gray-300 contrast-more:dark:text-gray-300"><input placeholder="Search..." class="search-input block w-full appearance-none rounded-lg px-3 py-2 transition-colors text-base leading-tight md:text-sm bg-black/[.05] dark:bg-gray-50/10 focus:bg-white dark:focus:bg-dark placeholder:text-gray-500 dark:placeholder:text-gray-400 contrast-more:border contrast-more:border-current" type="search" spellcheck="false">
<kbd class="absolute my-1.5 select-none ltr:right-1.5 rtl:left-1.5 h-5 rounded bg-white px-1.5 font-mono text-[10px] font-medium text-gray-500 border dark:border-gray-100/20 dark:bg-dark/50 contrast-more:border-current contrast-more:text-current contrast-more:dark:border-current items-center gap-1 transition-opacity pointer-events-none hidden sm:flex">CTRL K</kbd></div><div><ul class="search-results hextra-scrollbar hidden border border-gray-200 bg-white text-gray-100 dark:border-neutral-800 dark:bg-neutral-900 absolute top-full z-20 mt-2 overflow-auto overscroll-contain rounded-xl py-2.5 shadow-xl max-h-[min(calc(50vh-11rem-env(safe-area-inset-bottom)),400px)] md:max-h-[min(calc(100vh-5rem-env(safe-area-inset-bottom)),400px)] inset-x-0 ltr:md:left-auto rtl:md:right-auto contrast-more:border contrast-more:border-gray-900 contrast-more:dark:border-gray-50 w-screen min-h-[100px] max-w-[min(calc(100vw-2rem),calc(100%+20rem))]" style="transition:max-height .2s ease 0s"></ul></div></div></div><div class="hextra-scrollbar overflow-y-auto overflow-x-hidden p-4 grow md:h-[calc(100vh-var(--navbar-height)-var(--menu-height))]"><ul class="flex flex-col gap-1 md:hidden"><li class="open"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/">Introduction to the Python Developer Tooling Handbook
<span class="hextra-sidebar-collapsible-button"><svg fill="none" viewBox="0 0 24 24" stroke="currentcolor" class="h-[18px] min-w-[18px] rounded-sm p-0.5 hover:bg-gray-800/5 dark:hover:bg-gray-100/5"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" class="origin-center transition-transform rtl:-rotate-180"></path></svg></span></a><div class="ltr:pr-0 overflow-hidden"><ul class="relative flex flex-col gap-1 before:absolute before:inset-y-1 before:w-px before:bg-gray-200 before:content-[""] ltr:ml-3 ltr:pl-3 ltr:before:left-0 rtl:mr-3 rtl:pr-3 rtl:before:right-0 dark:before:bg-neutral-800"><li class="flex flex-col open"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
sidebar-active-item bg-primary-100 font-semibold text-primary-800 contrast-more:border contrast-more:border-primary-500 dark:bg-primary-400/10 dark:text-primary-600 contrast-more:dark:border-primary-500" href="/blog/effective-python-developer-tooling-in-december-2024/">Effective Python Developer Tooling in December 2024</a><ul class="flex flex-col gap-1 relative before:absolute before:inset-y-1 before:w-px before:bg-gray-200 before:content-[""] dark:before:bg-neutral-800 ltr:pl-3 ltr:before:left-0 rtl:pr-3 rtl:before:right-0 ltr:ml-3 rtl:mr-3"><li><a href="#principles" class="flex rounded px-2 py-1.5 text-sm transition-colors [word-break:break-word] cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] contrast-more:border flex gap-2 before:opacity-25 before:content-['#'] text-gray-500 hover:bg-gray-100 hover:text-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:text-gray-900 contrast-more:dark:text-gray-50 contrast-more:border-transparent contrast-more:hover:border-gray-900 contrast-more:dark:hover:border-gray-50">Principles</a></li><li><a href="#anti-patterns" class="flex rounded px-2 py-1.5 text-sm transition-colors [word-break:break-word] cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] contrast-more:border flex gap-2 before:opacity-25 before:content-['#'] text-gray-500 hover:bg-gray-100 hover:text-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:text-gray-900 contrast-more:dark:text-gray-50 contrast-more:border-transparent contrast-more:hover:border-gray-900 contrast-more:dark:hover:border-gray-50">Anti-Patterns</a></li><li><a href="#some-best-practices" class="flex rounded px-2 py-1.5 text-sm transition-colors [word-break:break-word] cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] contrast-more:border flex gap-2 before:opacity-25 before:content-['#'] text-gray-500 hover:bg-gray-100 hover:text-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:text-gray-900 contrast-more:dark:text-gray-50 contrast-more:border-transparent contrast-more:hover:border-gray-900 contrast-more:dark:hover:border-gray-50">Some &ldquo;Best&rdquo; Practices</a></li><li><a href="#what-developer-tooling-cant-do" class="flex rounded px-2 py-1.5 text-sm transition-colors [word-break:break-word] cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] contrast-more:border flex gap-2 before:opacity-25 before:content-['#'] text-gray-500 hover:bg-gray-100 hover:text-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:text-gray-900 contrast-more:dark:text-gray-50 contrast-more:border-transparent contrast-more:hover:border-gray-900 contrast-more:dark:hover:border-gray-50">What Developer Tooling Can&rsquo;t Do</a></li><li><a href="#python-tools-i-like-in-december-2024" class="flex rounded px-2 py-1.5 text-sm transition-colors [word-break:break-word] cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] contrast-more:border flex gap-2 before:opacity-25 before:content-['#'] text-gray-500 hover:bg-gray-100 hover:text-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:text-gray-900 contrast-more:dark:text-gray-50 contrast-more:border-transparent contrast-more:hover:border-gray-900 contrast-more:dark:hover:border-gray-50">Python Tools I Like in December 2024</a></li><li><a href="#some-conclusions" class="flex rounded px-2 py-1.5 text-sm transition-colors [word-break:break-word] cursor-pointer [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] contrast-more:border flex gap-2 before:opacity-25 before:content-['#'] text-gray-500 hover:bg-gray-100 hover:text-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:text-gray-900 contrast-more:dark:text-gray-50 contrast-more:border-transparent contrast-more:hover:border-gray-900 contrast-more:dark:hover:border-gray-50">Some Conclusions</a></li></ul></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/uv-0-3-one-command-line-to-rule-them-all-almost/">uv 0.3: One command line to rule them all (almost)</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/require-pip-to-install-in-virtual-environments/">Require pip to install packages in virtual environment</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/uv-got-to-keep-up-a-new-installer-announced/">uv got to keep up: a new installer announced!</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/pip-and-poetry-and-hatch-oh-my/">Pip and Poetry and Hatch, Oh My!</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/sponsor-the-python-developer-tooling-handbook/">Sponsor the Python Developer Tooling Handbook!</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/scientific-python-library-development-guide/">Scientific Python Library Development Guide</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/packaging-tool-examples/">Python Packaging Tool Examples</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/python-quickstart/">Quick start guide for Python development on a Mac</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/introduction-to-rye/">Introduction to Rye</a></li><li class="flex flex-col"><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/blog/python-packaging-user-guide/">Python Packaging User Guide</a></li></ul></div></li><li><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/about/">About</a></li><li><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/feedback/">Feedback</a></li><li><a class="flex items-center justify-between gap-2 cursor-pointer rounded px-2 py-1.5 text-sm transition-colors [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]
text-gray-500 hover:bg-gray-100 hover:text-gray-900 contrast-more:border contrast-more:border-transparent contrast-more:text-gray-900 contrast-more:hover:border-gray-900 dark:text-neutral-400 dark:hover:bg-primary-100/5 dark:hover:text-gray-50 contrast-more:dark:text-gray-50 contrast-more:dark:hover:border-gray-50" href="/coming-soon/">Python Developer Tooling Handbook</a></li></ul><div class="max-xl:hidden h-0 w-64 shrink-0"></div></div><div class="md:hidden sticky bottom-0 bg-white dark:bg-dark mx-4 py-4 shadow-[0_-12px_16px_#fff] flex items-center gap-2 dark:border-neutral-800 dark:shadow-[0_-12px_16px_#111] contrast-more:border-neutral-400 contrast-more:shadow-none contrast-more:dark:shadow-none border-t" data-toggle-animation="show"><div class="flex grow flex-col"><button title="Change theme" data-theme="light" class="theme-toggle group h-7 rounded-md px-2 text-left text-xs font-medium text-gray-600 transition-colors dark:text-gray-400 hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-primary-100/5 dark:hover:text-gray-50" type="button" aria-label="Change theme"><div class="flex items-center gap-2 capitalize"><svg height="12" class="group-data-[theme=light]:hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364-.707-.707M6.343 6.343l-.707-.707m12.728.0-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path></svg><span class="group-data-[theme=light]:hidden">Light</span><svg height="12" class="group-data-[theme=dark]:hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003.0 0012 21a9.003 9.003.0 008.354-5.646z"></path></svg><span class="group-data-[theme=dark]:hidden">Dark</span></div></button></div></div></aside><nav class="hextra-toc order-last hidden w-64 shrink-0 xl:block print:hidden px-4" aria-label="table of contents"><div class="hextra-scrollbar sticky top-16 overflow-y-auto pr-4 pt-6 text-sm [hyphens:auto] max-h-[calc(100vh-var(--navbar-height)-env(safe-area-inset-bottom))] ltr:-mr-4 rtl:-ml-4"><p class="mb-4 font-semibold tracking-tight">On this page</p><ul><li class="my-2 scroll-my-6 scroll-py-6"><a class="font-semibold inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#principles">Principles</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="font-semibold inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#anti-patterns">Anti-Patterns</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="font-semibold inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#some-best-practices">Some “Best” Practices</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="font-semibold inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#what-developer-tooling-cant-do">What Developer Tooling Can’t Do</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="font-semibold inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#python-tools-i-like-in-december-2024">Python Tools I Like in December 2024</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#uv">uv</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#ruff">Ruff</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#mypy">Mypy</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#pytest">Pytest</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#cookiecutter-and-cruft">Cookiecutter and Cruft</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#ipython">IPython</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#vs-code">VS Code</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#pre-commit">pre-commit</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="ltr:pl-4 rtl:pr-4 inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#direnv">direnv</a></li><li class="my-2 scroll-my-6 scroll-py-6"><a class="font-semibold inline-block text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 contrast-more:text-gray-900 contrast-more:underline contrast-more:dark:text-gray-50 w-full break-words" href="#some-conclusions">Some Conclusions</a></li></ul><div class="mt-8 border-t bg-white pt-8 shadow-[0_-12px_16px_white] dark:bg-dark dark:shadow-[0_-12px_16px_#111] sticky bottom-0 flex flex-col items-start gap-2 pb-8 dark:border-neutral-800 contrast-more:border-t contrast-more:border-neutral-400 contrast-more:shadow-none contrast-more:dark:border-neutral-400"><a class="text-xs font-medium text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100 contrast-more:text-gray-800 contrast-more:dark:text-gray-50" href="https://github.com/tdhopper/python-developer-tooling-handbook/edit/main/content/blog/effective-python-developer-tooling-in-december-2024.md" target="_blank" rel="noreferer">Edit this page on GitHub →</a>
<button aria-hidden="true" id="backToTop" onclick="scrollUp()" class="transition-all transition duration-75 opacity-0 text-xs font-medium text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100 contrast-more:text-gray-800 contrast-more:dark:text-gray-50">
<span>Scroll to top</span><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentcolor" class="inline ml-1 h-3.5 w-3.5 border rounded-full border-gray-500 hover:border-gray-900 dark:border-gray-400 dark:hover:border-gray-100 contrast-more:border-gray-800 contrast-more:dark:border-gray-50"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5"></path></svg></button></div></div></nav><article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]"><main class="w-full min-w-0 max-w-6xl px-6 pt-4 md:px-12"><div class="mt-1.5 flex items-center gap-1 overflow-hidden text-sm text-gray-500 dark:text-gray-400 contrast-more:text-current"><div class="whitespace-nowrap transition-colors min-w-[24px] overflow-hidden text-ellipsis hover:text-gray-900 dark:hover:text-gray-100"><a href="https://pydevtools.com/blog/">Introduction to the Python Developer Tooling Handbook</a></div><svg class="w-3.5 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"></path></svg><div class="whitespace-nowrap transition-colors font-medium text-gray-700 contrast-more:font-bold contrast-more:text-current dark:text-gray-100 contrast-more:dark:text-current">Effective Python Developer Tooling in December 2024</div></div><h1 class="mt-2 text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">Effective Python Developer Tooling in December 2024</h1><div class="mt-2 mb-4 text-gray-500 text-sm flex items-center flex-wrap gap-y-2"><span class="mr-1">December 20, 2024</span></div><div class="content"><p>I have been writing Python for 14 years next month. When I started, people were still using easy_install to install egg-based packages for Python 2.7 and nobody had heard about Conda yet, much less uv. Needless to say, the Python tool ecosystem has changed and developed since. Many people are rightfully confused by the <a href="https://xkcd.com/1987/" target="_blank" rel="noopener">fragmentation</a> in the ecosystem; at the same time, we unequivocally have better tooling for Python today than we’ve ever had before.</p><p>A friend recently invited me to give a talk to his team about Python tooling. In the presentation, I shared some principles of Python tooling and developer efficiency, some anti-patterns I’ve observed, recommended practices, and an opinionated list of tools I like in December 2024. What follows is a summary of that talk.</p><h2>Principles<span class="absolute -mt-20" id="principles"></span>
<a href="#principles" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ol><li><strong>Python is a great programming language.</strong> I love Python and am privileged to work with it every day. It’s expressive, readable, and powerful. The open-source community has built a wealth of libraries and tools that make Python an excellent choice for many tasks (as many have observed, Python is the second-best language for everything).</li><li><strong>Python doesn’t enforce (or necessarily encourage) good practices.</strong> It is a language that allows you to write bad code quickly. It’s easy to write code that works but is hard to read, maintain, and test. I think this goes hand in hand with the beautiful simplicity that makes Python easy to get started with.</li><li><strong>Many of us came to Python without software engineering experience.</strong> Particularly through the data science revolution, many folks have come to Python from math, physics, and other sciences and have not been exposed to the lessons of software engineering learned over the last 50 years.</li><li><strong>The Python developer tooling ecosystem is fragmented.</strong> Many tools are available for managing Python packages, formatting code, linting code, testing code, and more. This fragmentation can be confusing and frustrating.</li><li><strong>Python developer experience can get better.</strong> Python developers and teams can be more productive and happier with the right tools and practices.</li><li><strong>Python standards have moved forward.</strong> The Python community has made progress on standards, especially around packaging (e.g., <a href="https://peps.python.org/pep-0723/" target="_blank" rel="noopener">PEP 723</a>).</li><li><strong>Automation and consistency will make us happier and more productive.</strong> Automating repetitive tasks and enforcing consistency will make us happier and more productive. It will make our codebases more maintainable and our processes more reliable.</li><li><strong>The things people complain about (e.g. installing Python and packaging) are mostly solved problems.</strong> Some edge cases are still hard, but for most people, installing Python and managing packages can be easier than ever. The hard part is knowing what tools to use.</li></ol><h2>Anti-Patterns<span class="absolute -mt-20" id="anti-patterns"></span>
<a href="#anti-patterns" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I’ve observed these anti-patterns in Python code bases, especially those that can be improved by adopting better tools and practices.</p><ol><li><strong>Code cruft:</strong> For example, dead code, commented out code, and unused imports. These slow down development and can slow down code execution.</li><li><strong>Dumb team arguments:</strong> For example, arguing about <code>f</code> strings vs <code>.format</code>. This is a minor point and a waste of time for your team to debate. Pick one and move on.</li><li><strong>Inconsistent code style:</strong> Inconsistent style makes code harder to read and maintain.</li><li><strong>Unnecessary complexity:</strong> Unnecessary complexity makes code harder to read and maintain.</li><li><strong>Ignoring Python standards:</strong> Despite the tooling fragmentation, standards like use of pyproject.toml files for configuration and dependency management are becoming more common. Don’t neglect these.</li><li><strong>Lack of consistency:</strong> Consistency is key to maintainability. Particularly when a team has multiple Python projects, seek consistency around building, testing, deploying, and organizing code.</li><li><strong>Lack of automation:</strong> Automation is key to productivity. Automate (especially with your continuous integration system) repetitive tasks like formatting, linting, testing, and building.</li><li><strong>Lack of internal packages:</strong> Many companies lack the infrastructure to build and share internal Python packages. This leads to copy-and-paste, sharing code via S3 or other blob storage, and other inefficiencies.</li><li><strong>Lack of reproducibility:</strong> Developers should be able to check out a project and run it without needing to ask questions.</li></ol><h2>Some “Best” Practices<span class="absolute -mt-20" id="some-best-practices"></span>
<a href="#some-best-practices" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I dislike the term best practice but use of the term seems to be a best practice. In any case, here are some practices that I recommend:</p><ol><li><strong>Make it easy to make packages (and make them):</strong> Packaging is a solved problem. Make it easy to create packages and share them on an internal repository.</li><li><strong>Auto format your code:</strong> Use <a href="https://astral.sh/ruff" target="_blank" rel="noopener">ruff</a> to automatically format your code. This will (usually) make your code more readable and maintainable, and it will remove cognative load from your team in writing and reading code.</li><li><strong>Lint your code:</strong> Use <a href="https://astral.sh/ruff" target="_blank" rel="noopener">ruff</a> to lint your code. This will help you catch errors and syntax issues before they become bugs. Review the Ruff rules and adopt a list that makes sense for your organization; don’t bikeshed further.</li><li><strong>Set up your editor for formatting and linting:</strong> Enable editor integration for automatic formatting and linting. This will make it easy for your team to write code that conforms to your standards.</li><li><strong>Automate your checks:</strong> Use continuous integration to automate your checks (linters, type checkers, etc). This will ensure that your shared code is consistently formatted, linted, and tested.</li><li><strong>Make Python projects consistent:</strong> Use a project template like <a href="https://cookiecutter.readthedocs.io" target="_blank" rel="noopener">Cookiecutter</a> to standardize your project structure.</li><li><strong>Make it easy to write tests (and write them):</strong> Use <a href="https://pytest.org" target="_blank" rel="noopener">pytest</a> to write tests. Organize your projects so that tests are easily added and can be run with a call to <code>pytest</code>.</li><li><strong>Make it easy to write docs (and write them):</strong> Build documentation generation and publishing into your project template. Write documentation as you go.</li></ol><h2>What Developer Tooling Can’t Do<span class="absolute -mt-20" id="what-developer-tooling-cant-do"></span>
<a href="#what-developer-tooling-cant-do" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ol><li><strong>Tell you what to build:</strong> <a href="https://tdhopper.com/blog/no-silver-bullet/" target="_blank" rel="noopener">The hardest part of building software is figuring out what to build</a> and developer tooling can’t solve this.</li><li><strong>Improve overall architecture of your code:</strong> Developer tooling can help you write better code, but it mostly can’t help you design better systems.</li><li><strong>Guarantee a better product:</strong> Developer tooling can help you catch bugs and write better code, but it can’t guarantee that your product will be successful. A great product doesn’t <em>require</em> a great codebase.</li><li><strong>Replace foundational knowledge and common sense:</strong> I <em>think</em> even LLMs don’t replace these. <code>ruff</code> and <code>mypy</code> definitely don’t!</li></ol><h2>Python Tools I Like in December 2024<span class="absolute -mt-20" id="python-tools-i-like-in-december-2024"></span>
<a href="#python-tools-i-like-in-december-2024" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Here is a list of tools I like in December 2024. This list is opinionated, personal, and not exhaustive.</p><p>A remarkable thing about what follows is how different it is from a list one or two years ago. The team at <a href="https://astral.sh" target="_blank" rel="noopener">Astral</a> has been doing groundbreaking work in the Python tooling space with the release of <a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener">uv</a> and <a href="https://docs.astral.sh/ruff/" target="_blank" rel="noopener">ruff</a>. These tools have made my Python development experience better, and I’m sincerely excited about them.</p><h3>uv<span class="absolute -mt-20" id="uv"></span>
<a href="#uv" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener">uv</a> is a Python package and project manager that replaces pip, pip-tools, pipx, poetry, pyenv, twine, and virtualenv (at least). It is blazing fast, doesn’t depend on Python to be installed, and is rapidly getting new features. I use it to manage dependencies, build packages, and run scripts. Because uv can be installed <em>without</em> Python, it is going to be a game-changer for folks getting started with Python or just running a one-off script.</p><h3>Ruff<span class="absolute -mt-20" id="ruff"></span>
<a href="#ruff" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://docs.astral.sh/ruff" target="_blank" rel="noopener">Ruff</a> is a linter and formatter that replaces black, flake8 (mostly), isort. It is also blazing fast and can automatically fix many linter issues (with the <code>--fix</code> flag). I recommend enabling editor integration for automatic formatting and using CI to enforce conformity.</p><h3>Mypy<span class="absolute -mt-20" id="mypy"></span>
<a href="#mypy" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://mypy.readthedocs.io" target="_blank" rel="noopener">Mypy</a> is a static type checker for Python. It helps catch type-related errors before runtime, improves code quality and documentation, and enhances IDE support and code completion.</p><p>Mypy can be slow for large projects and difficult to configure, especially with 3rd party libraries. Like many others, I am eagerly looking forward to an alternative type checker from Astral in the near future.</p><h3>Pytest<span class="absolute -mt-20" id="pytest"></span>
<a href="#pytest" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://pytest.org" target="_blank" rel="noopener">Pytest</a> is a testing framework that is easy to get started with (you just need a file and function prefixed with <code>test_</code>). With parameterized testing, fixtures, and plugins, Pytest enables robust testing of your code.</p><h3>Cookiecutter and Cruft<span class="absolute -mt-20" id="cookiecutter-and-cruft"></span>
<a href="#cookiecutter-and-cruft" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://cookiecutter.readthedocs.io" target="_blank" rel="noopener">Cookiecutter</a> is a project templating tool that generates new Python projects quickly and standardizes project structure. I love using it to automate project setup and ensure that my projects have CI integration, package building and deployment, a Makefile for common commands, pre-commit hooks, documentation generation, and linter configurations.</p><p><a href="https://cruft.github.io/cruft/" target="_blank" rel="noopener">Cruft</a> is a tool that wraps Cookiecutter and allows updating existing projects with a modifications to the template.</p><p>Building and maintaining a robust template requires considerable effort. Evaluate your team’s return on investment.</p><h3>IPython<span class="absolute -mt-20" id="ipython"></span>
<a href="#ipython" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://ipython.org" target="_blank" rel="noopener">IPython</a> is an interactive Python shell that has rich features for development and exploration. Most Python developers have used it, but many only at a basic level. Take some time to read the documentation and learn its command line arguments, magic commands, and other features.</p><h3>VS Code<span class="absolute -mt-20" id="vs-code"></span>
<a href="#vs-code" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://code.visualstudio.com" target="_blank" rel="noopener">VS Code</a> is a powerful Python IDE with an extensive extension ecosystem. I recommend using it for Python development.</p><h3>pre-commit<span class="absolute -mt-20" id="pre-commit"></span>
<a href="#pre-commit" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://pre-commit.com" target="_blank" rel="noopener">pre-commit</a> is a tool that manages git pre-commit hooks. I recommend using it to automate code quality checks. At a minimum, I recommend running ruff and <a href="https://github.com/adrienverge/yamllint/blob/master/.pre-commit-hooks.yaml" target="_blank" rel="noopener">yamllint</a> (if your project has yaml, which of course it does).</p><h3>direnv<span class="absolute -mt-20" id="direnv"></span>
<a href="#direnv" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://direnv.net" target="_blank" rel="noopener">direnv</a> is a tool that automatically activates and deactivates project environments, including Python virtualenvs. It handles environment variables and project-specific settings. I recommend using it to manage your project environments. It does not yet have native support for uv, but I’m <a href="https://github.com/direnv/direnv/pull/1329" target="_blank" rel="noopener">hopeful that it will soon</a>.</p><h2>Some Conclusions<span class="absolute -mt-20" id="some-conclusions"></span>
<a href="#some-conclusions" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ol><li>Python tooling is improving rapidly. Although fragmentation can be frustrating, it points to the desire and effort of many to improve Python development.</li><li>Teams need to agree on tools and practices. Automation and consistency are key to team productivity and happiness.</li><li>Read the docs and learn your tools.</li><li>Don’t be afraid to try new tools.</li></ol></div><div class="mt-12 mb-8 block text-xs text-gray-500 ltr:text-right rtl:text-left dark:text-gray-400">Last updated on <time datetime="2024-12-21T00:28:50.000Z">December 21, 2024</time></div><div class="mb-8 flex items-center border-t pt-8 dark:border-neutral-800 contrast-more:border-neutral-400 dark:contrast-more:border-neutral-400 print:hidden"><a href="/blog/uv-0-3-one-command-line-to-rule-them-all-almost/" title="uv 0.3: One command line to rule them all (almost)" class="flex max-w-[50%] items-center gap-1 py-4 text-base font-medium text-gray-600 transition-colors [word-break:break-word] hover:text-primary-600 dark:text-gray-300 md:text-lg ltr:pr-4 rtl:pl-4"><svg class="inline h-5 shrink-0 ltr:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"></path></svg>uv 0.3: One command line to rule them all (almost)</a></div><details class="last-of-type:mb-0 rounded-lg bg-neutral-50 dark:bg-neutral-800 p-2 mt-4 group"><summary class="flex items-center cursor-pointer select-none list-none p-1 rounded transition-colors hover:bg-gray-100 dark:hover:bg-neutral-800 before:mr-1 before:inline-block before:transition-transform before:content-[''] dark:before:invert rtl:before:rotate-180 group-open:before:rotate-90"><p class="text">Please submit corrections and feedback...</p></summary><style></style><div class="p-2 overflow-hidden form-container"><form method="post" name="Effective Python Developer Tooling in December 2024 feedback"><input type="hidden" name="form-name" value="Effective Python Developer Tooling in December 2024 feedback"><div><input type="text" name="name" placeholder="Name"></div><div><input type="email" name="email" placeholder="Email Address"></div><input type="hidden" name="page" value="https://pydevtools.com/blog/effective-python-developer-tooling-in-december-2024/"><div><textarea name="message" placeholder="Feedback" rows="4"></textarea></div><button type="submit">Send</button></form></div></details></main></article></div><footer class="hextra-footer bg-gray-100 pb-[env(safe-area-inset-bottom)] dark:bg-neutral-900 print:bg-transparent"><div class="mx-auto flex gap-2 py-2 px-4 max-w-screen-xl"><button title="Change theme" data-theme="light" class="theme-toggle group h-7 rounded-md px-2 text-left text-xs font-medium text-gray-600 transition-colors dark:text-gray-400 hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-primary-100/5 dark:hover:text-gray-50" type="button" aria-label="Change theme"><div class="flex items-center gap-2 capitalize"><svg height="12" class="group-data-[theme=light]:hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364-.707-.707M6.343 6.343l-.707-.707m12.728.0-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path></svg><span class="group-data-[theme=light]:hidden">Light</span><svg height="12" class="group-data-[theme=dark]:hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003.0 0012 21a9.003 9.003.0 008.354-5.646z"></path></svg><span class="group-data-[theme=dark]:hidden">Dark</span></div></button></div><hr class="dark:border-neutral-800"><div class="max-w-screen-xl mx-auto flex justify-center py-12 pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)] text-gray-600 dark:text-gray-400 md:justify-start"><div class="flex w-full flex-col items-center sm:items-start"></div></div></footer><script>
if (window.netlifyIdentity) {
window.netlifyIdentity.on("init", user => {
if (!user) {
window.netlifyIdentity.on("login", () => {
document.location.href = "/admin/";
});
}
});
}
</script><script defer="" src="/js/main.min.b6a2a5de84a8502fae54c981012bd27f4a4da6bcaeb8d3b05dd350ebd52d91c1.js" integrity="sha256-tqKl3oSoUC+uVMmBASvSf0pNpryuuNOwXdNQ69UtkcE="></script>
<script defer="" src="/lib/flexsearch/flexsearch.bundle.min.0425860527cc9968f9f049421c7a56b39327d475e2e3a8f550416be3a9134327.js" integrity="sha256-BCWGBSfMmWj58ElCHHpWs5Mn1HXi46j1UEFr46kTQyc="></script>
<script defer="" src="/en.search.min.9afdc7c586c6f971dd94df10b989f10faaf38e5702571fd8cfc9ff9135c2d495.js" integrity="sha256-mv3HxYbG+XHdlN8QuYnxD6rzjlcCVx/Yz8n/kTXC1JU="></script><iframe id="netlify-identity-widget" title="Netlify identity widget" style="position: fixed; top: 0; left: 0; border: none; width: 100%; height: 100%; overflow: visible; background: transparent; display: none; z-index: 99; " src="about:blank"></iframe></body></html>