Codebeez

Een reproduceerbare, terminal-first Python-ontwikkelomgeving

Waar moderne applicaties kunnen bestaan als een set verbonden docker-containers en daarmee reproduceerbaar zijn, ben ik ouderwets in de zin dat ik graag mijn eigen (aangepaste) laptop gebruik om op te ontwikkelen.

Het proces van packages installeren, git-repo’s clonen en installeren is echter lang en vermoeiend, en ik vergeet al snel welke stappen ik heb gezet om mijn omgeving werkend te krijgen zoals ik wil. Mijn lokale systeem als infrastructure-as-code vastleggen in een ansible-playbook is een zeer nauwgezette manier van aantekeningen maken, waarmee mijn laptop-setup net zo reproduceerbaar wordt als een docker-image.

Daarnaast ben ik een groot voorstander van de muis zo min mogelijk gebruiken. Ik doe het meeste van mijn werk in, of dicht bij, de terminal en vermijd alles waarbij ik moet wijzen en klikken. Hoewel dit een prima manier is om frictie te minimaliseren tijdens lange code-stamping sessies, komt een omgeving die geoptimaliseerd is voor keyboard-heavy development niet gratis. De prijs die je betaalt is veel extra tijd aan de setup. Een hoop componenten die goed met elkaar moeten samenwerken voordat ik storingsvrij kan coderen.

Dus nu wil ik ingaan op hoe ik deze ontwikkelomgeving opzet, maar ook op wat erin zit. Concreet behandelt dit de volgende onderwerpen:

  • Het ansible-playbook dat ik gebruik om mijn omgeving voor te bereiden.
  • Waarom ik liever in de terminal werk en de tools die ik daarvoor gebruik.
  • tools die ik specifiek voor python-development gebruik.

Ansible

Ansible is een voor de hand liggende keuze voor automatisering omdat het al lang bestaat, uitstekende community-ondersteuning heeft en op vrijwel elk type guest-machine kan draaien. In dit geval wilde ik een playbook maken dat ik kon draaien op mijn nieuwe (en alle volgende) laptops, evenals op een VM voor prototyping.

Ik heb mijn playbook ontworpen in de veronderstelling dat ik Ubuntu 22.04 met de standaard Gnome zou gebruiken, maar met vagrant is het eenvoudig om de setup op andere versies te testen.

De taken die ik uiteindelijk heb geautomatiseerd zijn:

  • apt-packages installeren
  • gewenste packages van het web halen (bijv. github) en installeren
  • de user-shell instellen
  • de services instellen die bij het opstarten draaien
  • python-development tools installeren
  • de snap-versie van firefox vervangen door het .deb-package
  • snap-packages installeren
  • Mijn configuraties toepassen door dotfiles te stowen
  • Keybinds, keyboards en configuraties instellen via dconf

Het ansible-playbook dat ik gebruik is hier te zien

Ik gebruik ook graag Vagrant om mijn Ansible-playbook te prototypen. Met Vagrant kan ik via de command line een VM opstarten waarin ik kan ssh’en, wat goed past bij mijn terminal-gebaseerde aanpak. Er is ook het commando vagrant provision dat, mits mijn Vagrantfile correct was opgezet, het ansible-playbook met mijn volledige setup opnieuw draait.

Je zou kunnen aanvoeren om een Docker-image te gebruiken en het playbook in plaats daarvan op de container te draaien. Maar ik vind dat een Vagrant-VM dichter in de buurt komt van hoe het uiteindelijke doelsysteem eruit gaat zien, namelijk mijn lokale machine. Vagrant heeft ook een betere integratie met ansible-provisioning.

Terminal-gebaseerd ontwikkelen

Er wordt gezegd dat leren om de muis te vermijden de efficiëntie van een developer verhoogt. De theorie is dat het verplaatsen van je hand van de home row naar de muis te lang duurt, dus als je point-and-click-interactie kunt vervangen door efficiënte keyboard shortcuts, word je efficiënter dankzij de minuscule tijdwinst tussen het schrijven van een regel code en het navigeren naar de volgende regel.

Ik weet niet of dat klopt. En als het klopt, duurt het even voordat deze kleine winsten de tijd compenseren die nodig is om jezelf te hertrainen en aan deze manier van navigeren te wennen. Mijn persoonlijke aantrekking tot keyboard-first development is veel minder meetbaar en heeft te maken met plezier en het vermogen om te focussen. Naar de muis grijpen voelt voor mij als een micro-afleiding, en breekt mijn concentratie net even. Maar ik heb in de loop der tijd zoveel commando’s en keybinds geleerd dat werken met alleen het toetsenbord een tweede natuur is geworden. Bovendien geniet ik er gewoon van, omdat het het bereiken en vasthouden van een flow-state faciliteert, zodat ik me beter kan concentreren en langer kan werken.

De volgende secties gaan over de tools die ik gebruik om me in deze workflow te ondersteunen.

kitty terminal

De terminal zelf voorbereiden is de eerste stap.

Kitty is een zeer aanpasbare terminal die ontworpen is om volledig met het toetsenbord bediend te worden. Vrijwel elke keybinding is aanpasbaar; de configuratie wordt als plaintext bewaard, zodat het makkelijk bij de rest van mijn dotfiles blijft. Deze filosofie strekt zich uit tot customization, zodat ook kleurenschema’s en lettertypekeuzes in één enkel plaintext-bestand kunnen worden opgegeven. Er is uiteraard ondersteuning voor tabs en splits. Het splitsen van vensters in kitty is voor mij feature-compleet genoeg dat ik nooit de behoefte voelde om er nog een terminal-multiplexer bovenop te zetten, maar YMMV.

Eén ding aan kitty ten opzichte van de ‘standaard’ terminal is dat de eerste geen scrollbar heeft. Dat kan onhandig zijn bij het ver terugscrollen in een hele lange terminal-ticker. De kitty-manier om dit te doen is een scrollback-pager gebruiken (voor mij is dat vim), die je kunt aanroepen met de instructie show_scrollback (ctrl+shift+h bij mij).

fish shell

De fish-shell is een van de opties voor wie al een tijdje bash gebruikt en wat extra gemakfeatures nodig heeft. Het komt ook met een eigen scripttaal, waar je even aan moet wennen. Maar eerlijk gezegd, wanneer ik substantieel moet scripten, gebruik ik gewoon python.

Ik moet toegeven dat de belangrijkste killerfeature die me deed overstappen de autosuggestions waren. Deze feature is weinig meer dan een automatische reverse-i-search (oftewel CTRL+R in bash), maar dat die automatisch verschijnt is een enorme verbetering. fish-autosuggestions heeft het vaak bij het juiste eind, vooral omdat het directory-aware is, vaak wanneer ik al vergeten ben hoe een specifieke cli ook alweer aangeroepen moest worden.

fish-autosuggest

Autocompletion in fish is ook een stuk intelligenter en kan bestanden completen op basis van elke substring-match in de bestandsnaam (niet alleen vanaf het begin van de bestandsnaam). In git-repositories kan het ook completen vanuit substrings die overeenkomen met bestanden in elke subdirectory van de huidige repo.

fish is niet POSIX-compatibel. Zoals gezegd heeft het een eigen scripttaal, die sommige dingen anders doet dan bash. Het gevolg hiervan is dat shellscripts die je van het internet plukt niet werken wanneer je ze sourcet. Als je dat irriteert, kun je ofwel zsh gebruiken (naar verluidt ook niet POSIX-compatibel maar werkt meestal wel) of de gewoonte aannemen om bash -c ervoor te zetten (waarmee je een enkel commando met bash draait).

command line tools

De afgelopen jaren is er ook een golf geweest om vertrouwde command line-executables te vervangen door modernere, handigere alternatieven. Deze tools, vaak in rust geschreven, bouwen voort op alomtegenwoordige shell-programma’s om ze makkelijker in gebruik te maken. Enkele die ik echt graag gebruik zijn:

  • fzf. In vrijwel elke moderne web-app heeft fuzzy searching het conventionele zoeken vervangen, maar in de terminal blijft het gedrag leunen op start-of-string-matching om bestanden op te geven. fzf brengt het fuzzy-search-gedrag als executable naar de terminal. Toen ik het eenmaal gewend was om het als onderdeel van een commando aan te roepen met pipes en subshells, verbeterde het sterk mijn vermogen om snel bestanden die meerdere subdirectories diep zitten op te geven. Het integreert ook in Vim via plugins.
  • zoxide. Deze tool wordt op de markt gebracht als een cd-vervanger, maar dat vat de functionaliteit niet goed samen (zo gebruik ik cd er nog steeds naast). zoxide, of het z-commando, is een alternatieve manier om tussen directories te springen. In plaats van de huidige directory en de bijbehorende directory-tree te gebruiken, benut z echter je eerdere browsegedrag op basis van een frecency (frequency + recency)-algoritme. Het matcht directories die je eerder hebt bezocht aan een zoekstring. Bij meerdere resultaten kiest het echter degene die je het meest recent hebt gebruikt of het vaakst nodig hebt. Dat is opmerkelijk efficiënt. z heeft het bij mij meestal goed, maar wanneer je het installeert heeft het wel wat “training data” nodig om op gang te komen.
  • fd: Het lukt me nooit om find de eerste keer correct te gebruiken. Ik moet steeds opnieuw de argumenten opzoeken waarmee ik mijn meest voorkomende use case uitvoer. Niet zo met fdfind. Met fdfind {search string} doe je een recursieve zoekopdracht vanaf de huidige directory naar bestandsnamen die overeenkomen met het gewenste patroon, wat is wat ik meestal wil. Resultaten pipen ook netjes door naar fzf Deze tool is beschikbaar via apt op Ubuntu 22.04. Het is aan te raden om het te aliassen naar fd.
  • exa: In wezen gewoon een betere ls. Het voegt basale QoL-features toe zoals color-coding en iconen aan de resultaten, evenals optionele git-status in de long-form-resultaten. Hoewel het een minimaal voordeel lijkt, maakt het feit dat ls mijn meestgebruikte commando is het optimaliseren van de resultaten een mooi voordeel. Er is ook een fisher-plugin om automatisch een reeks aliassen aan te maken die exa als ls instellen, maar je kunt de aliassen ook zelf opzetten.

VIM

Vim is een teksteditor die op vrijwel elk GNU/Linux-systeem beschikbaar is. Dat is een van de sterke punten. Als je gewend bent aan Vim, kan werken op een haperende box ergens in de cloud een stuk makkelijker worden. Gebouwd op software (vi, ex) die ouder is dan de muis, is het een ideaal gereedschap voor keyboard-heavy gebruikers. Het is echter geen IDE, en het op het niveau van een fatsoenlijke code-editor krijgen vereist veel configuratie.

Vim als code-editor laten werken zou een heel artikel (of artikelreeks) op zichzelf zijn. Als je hiermee aan de slag zou willen, raad ik aan om neovim te gebruiken, dat verstandigere defaults heeft en een goed plugin-ecosysteem voor nieuwe gebruikers die willen beginnen.

Wie voor het eerst begint, kan een van de volgende opties overwegen:

  • vscode-neovim. Als je momenteel vscode gebruikt, is dit de manier. Veel editors bieden “vim keybindings”, wat meestal verwijst naar een subset van vim-features zoals modaal bewerken met Normal mode ingesteld met veelgebruikte Vim-keybinds. Dat is meestal onvoldoende. Deze extensie daarentegen is daadwerkelijk neovim ingebed (en geïntegreerd) in VSCode. Dit is een goede manier om vertrouwd te raken met VIM-keybindings.
  • kickstart.nvim. Dit is een voorgebakken configuratie waarop je kunt voortbouwen. Het is gericht op een neovim code-editor-setup.
  • LazyVim, een neovim-distributie die een volledige setup bundelt voor gebruikers die iets willen dat ‘gewoon werkt’.

Wat je ook kiest, je ontkomt er niet aan om het werk te doen van wennen aan een nieuwe manier van code/tekst bewerken. Vim is oud. Het bouwt voort op software die ouder is dan de muis. Het doet alles anders dan je gewend bent. Hoewel je het kunt configureren om zich aan jouw manier van werken aan te passen, is het beter om je manier van werken (en je manier van denken) aan te passen aan Vim, wat consistent en elegant is, zij het vreemd en onbekend. Deze post gaat in op de VIM-manier van denken. Als je net begint, zal die niet erg nuttig voor je zijn. Het is echter een goede post om nu te bookmarken en later op terug te komen.

Het is goed om op te merken dat een Vim-gebruiker zijn niet noodzakelijk betekent dat je IntelliSense code-completion moet opgeven. Dezelfde extensies voor het completen van (python-)code die in VSCode beschikbaar zijn, kunnen naar Vim worden geport. Het language server protocol (LSP) is ontwikkeld door Microsoft en VSCode is de meestgebruikte editor die het implementeert. Maar andere editors hebben ook LSP-ondersteuning. Voor Vim gebeurt dit via de plugin coc.nvim De Vim-refactor neovim heeft native ondersteuning, maar vereist nog steeds een of meer plugins om providers voor de gewenste programmeertalen te configureren. Een nadeel voor python is dat pylance, de LSP-implementatie van VSCode, closed-source is; het dichtstbijzijnde alternatief is het open-source pyright waarop pylance is gebouwd.

vim-pyright

python development tools

UPDATE: Veel van deze informatie is nog steeds van toepassing, maar wordt achterhaald door nieuwe tools. Ik raad nu aan om uv te gebruiken als vervanger voor alle onderstaande tools, zoals besproken hier

Er zijn voor de hand liggende tools die elke python-developer nodig heeft, zoals linters en code-formatters. Daarom zie ik geen noodzaak om die hier te behandelen. De tools die ik persoonlijk heb gebruikt en die mijn dagelijkse development plezieriger hebben gemaakt, zijn minder alomtegenwoordig. Bij het opzetten van mijn ontwikkelomgeving heb ik ervoor gezorgd dat deze tools automatisch worden meegenomen

pyenv

pyenv is een lichtgewicht manier om verschillende python-versies voor verschillende projecten aan te bieden. Dit kan handig zijn als je, zoals ik, verwacht vaak tussen verschillende projecten te wisselen, en de python-versies voor die projecten verschillend kunnen zijn.

De verschillende python-executables die pyenv biedt zijn makkelijk te integreren met virtual environments. Zo biedt pyenv bijvoorbeeld pyenv-virtualenv, dat virtual environments beheert, elk gekoppeld aan een bepaalde python-versie. Maar je kunt pyenv-python-versies ook koppelen aan poetry-projecten die een heel specifieke python-versie eisen. Dit gebeurt via een .python-version-bestand, opgezet met pyenv local. Hieronder staat een manier om dit te doen.

Pyenv is na de setup opmerkelijk naadloos in gebruik en bevrijdt developers ervan om te onthouden waar een specifieke python-executable of virtual environment zich bevindt.

pipx

pipx biedt één plek om python-executables te bewaren die je vanaf de command line wilt draaien. Ik installeer python-packages meestal om een van twee redenen: software waaraan ik werk heeft het als dependency, of ik heb een soort python command-line tool nodig (bijv. een linter). Wanneer ik tussen virtual environments wissel, wil ik dat die CLI-tools meegaan. Ook wil ik over het algemeen dat mijn venv alleen de dependencies bevat die specifiek zijn voor de code waaraan ik werk (mocht ik ooit ergens pip freeze voor nodig hebben). pipx is hiervoor de ideale tool. Het houdt CLI-tools bij in virtual environments die het zelf beheert, en maakt ze vervolgens ‘globaal’ beschikbaar vanaf de command line. Het gebruik is rechttoe rechtaan en lijkt op pip. pipx install {package} doet het werk. Elke install maakt echter een nieuwe virtual environment aan. Vaak wil je meerdere packages in één pipx-venv laten leven. Daarvoor is pipx inject het tweede belangrijke commando dat je moet kennen om deze tool succesvol te gebruiken.

poetry

poetry is een tool voor python-packaging en dependencybeheer. Het maakt het makkelijk om consistente dependency-versies te garanderen voor verschillende gebruikers die hetzelfde project draaien, maar het vereist wel dat het project met poetry in gedachten is geconfigureerd. Dat wil zeggen, het moet gebundeld zijn met de bestanden pyproject.toml en poetry.lock.

De installatie en het gebruik van poetry sluiten goed aan op de twee utilities die ik hierboven heb genoemd. Je kunt namelijk pipx gebruiken om poetry te installeren, en pyenv om poetry-projecten aan versie-specifieke python-executables te koppelen.

Om de nieuwste versie van poetry te installeren (die in de apt-repository is waarschijnlijk verouderd), draai je het volgende:

pipx install poetry
poetry -V # confirm the shell can find it and you have the latest version

Een bestaand python-project kan heel specifieke eisen aan een python-versie stellen. Die worden gespecificeerd in het pyproject.toml-bestand. We kunnen pyenv gebruiken om ervoor te zorgen dat aan deze eisen wordt voldaan.

Dit is te zien in het onderstaande voorbeeld, dat versie 3.10.9 gebruikt.

pyenv install 3.10.9  # do this only once
cd /path/to/python/project/containing/pyproject.toml
pyenv local 3.10.9  # this generates a .python-version file
poetry env use python
poetry shell  # ensures the poetry virtual environment is used by the shell

Dit werkt omdat de python-executable door pyenv wordt geleverd op het moment dat poetry env use python wordt ingevoerd. Poetry zal deze python-executable erkennen bij het aanmaken van zijn virtual environment wanneer het toetst tegen de python-versie die door pyproject.toml wordt vereist.

Het commando poetry shell moet één keer per shell-instantie opnieuw worden gedraaid; al het andere hoeft maar één keer per project te worden gedraaid. Het commando pyenv install hoeft uiteraard maar één keer per python-versie te worden gebruikt.

pyflyby

In interactieve notebooks kan pyflyby helpen als je altijd je import-statements vergeet. Die er steeds in moeten zetten kan tijdrovend zijn, vooral als je ze voor elke cel moet toevoegen omdat je niet je hele notebook telkens op volgorde wilt aanroepen. pyflyby ‘raadt’ welke library je probeert aan te roepen en importeert ze automatisch. Het kan ook worden geconfigureerd om import-aliassen te leveren en te herkennen, bijv. import numpy as np

pyflyby exporteert zijn functionaliteit ook naar de command line. ipython gebruiken in plaats van de standaard interactieve interpreter is sowieso een goed idee, aangezien die laatste de meeste features mist die je misschien als vanzelfsprekend beschouwt. pyflyby tilt het echt naar een hoger niveau door auto-imports te bieden. De libraries die het importeert kunnen naar behoefte worden geconfigureerd, maar standaard biedt het ten minste de python-standaardlibraries en enkele populaire third-party packages. Het eindresultaat hiervan is dat ik naadloos een python-REPL in mijn command kan inbouwen

mijn favoriete voorbeeld is py 'uuid.UUID4())', een snelle manier om een willekeurig gegenereerde UUID te produceren. Je kunt ook py 'random.random()' overwegen als je, net als ik, altijd vergeet hoe /dev/random ook alweer werkt.

Conclusie

Er zijn nog andere dingen die ik regelmatig gebruik en die ik hier niet heb genoemd. Maar de kernboodschap is simpelweg dit: een ontwikkelomgeving heeft veel onderdelen, maar als je voor je werk codeert, betaalt het zich dubbel en dwars terug om die onderdelen precies goed te krijgen.

Wat ik hier heb besproken zijn enkele van die onderdelen die ik ontdekte, vaak terwijl ik blogs van anderen las, en in mijn workflow opnam, en het ontwikkelwerk dat ik deed verliep daardoor soepeler en met meer plezier. Dus nu hoop ik die kennis door te geven. Als je dit allemaal hebt gelezen, dank je wel, en ik hoop dat je ten minste één ding hebt gevonden dat je overweegt op te nemen.

Blog