<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Gael Espinosa</title><link href="https://picklecillo.xyz/" rel="alternate"/><link href="https://picklecillo.xyz/feeds/all.atom.xml" rel="self"/><id>https://picklecillo.xyz/</id><updated>2026-07-10T00:00:00-04:00</updated><subtitle>Full Stack Engineer</subtitle><entry><title>Boring technology, on purpose</title><link href="https://picklecillo.xyz/blog/boring-technology-on-purpose/" rel="alternate"/><published>2026-07-10T00:00:00-04:00</published><updated>2026-07-10T00:00:00-04:00</updated><author><name>Gael Espinosa</name></author><id>tag:picklecillo.xyz,2026-07-10:/blog/boring-technology-on-purpose/</id><summary type="html">&lt;p&gt;Everything I ship is Django, server-rendered, HTMX where it needs to feel alive. Not because I can't do the other thing — because nothing I build needs it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Everything I ship lately is Django, server-rendered, HTMX where it needs to
feel alive. No SPA, no build step, no client state to sync.&lt;/p&gt;
&lt;p&gt;Not a limitation. A choice.&lt;/p&gt;
&lt;h2 id="the-receipts"&gt;The receipts&lt;/h2&gt;
&lt;p&gt;&lt;a href="/projects/cvora-ai-powered-career-toolkit/"&gt;CVora&lt;/a&gt; went from first commit to
production in three days. Part of that is the stack having no moving parts I
don't fully understand: Django views, HTMX partial swaps, one database,
&lt;code&gt;fly deploy&lt;/code&gt;. Every AI interaction on that site is a server round-trip that
swaps a chunk of HTML. Nobody notices. Nobody cares. It ships.&lt;/p&gt;
&lt;p&gt;This site: markdown files and &lt;a href="/projects/this-blog-the-site-youre-looking-at/"&gt;Pelican&lt;/a&gt;.
Builds in half a second. Hosting is free. A portfolio is text that changes a
few times a year — a React framework here would be decoration.&lt;/p&gt;
&lt;h2 id="the-rule"&gt;The rule&lt;/h2&gt;
&lt;p&gt;New tech has to name the specific problem it solves for me, today, in this
product. "We might need it later" doesn't count.&lt;/p&gt;
&lt;p&gt;The part people miss: when &lt;em&gt;later&lt;/em&gt; actually arrives, migrating away from the
simple thing is easy — precisely because it's simple. Migrating away from the
complicated thing you adopted early is the expensive move.&lt;/p&gt;
&lt;h2 id="not-a-religion"&gt;Not a religion&lt;/h2&gt;
&lt;p&gt;Sometimes the exciting tool wins. It just has to earn the spot with a problem
I can point at, not a feeling that I'm falling behind.&lt;/p&gt;
&lt;p&gt;The stack is not the product. Shipping is the product.&lt;/p&gt;</content><category term="blog"/><category term="engineering"/><category term="opinion"/><category term="django"/><category term="htmx"/></entry><entry><title>this blog — the site you're looking at</title><link href="https://picklecillo.xyz/projects/this-blog-the-site-youre-looking-at/" rel="alternate"/><published>2026-07-10T00:00:00-04:00</published><updated>2026-07-10T00:00:00-04:00</updated><author><name>Gael Espinosa</name></author><id>tag:picklecillo.xyz,2026-07-10:/projects/this-blog-the-site-youre-looking-at/</id><summary type="html">&lt;p&gt;This portfolio itself. A markdown-driven static site built with Pelican, Tailwind CSS v4, and daisyUI 5, wearing a dark neubrutalist theme and deployed to GitHub Pages on every push.&lt;/p&gt;</summary><content type="html">&lt;h2 id="what-it-is"&gt;What it is&lt;/h2&gt;
&lt;p&gt;The site you're reading right now. Everything on it — the experience timeline,
the project cards, this very page — is generated from plain markdown files and
one Python config.&lt;/p&gt;
&lt;h2 id="the-stack"&gt;The stack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://getpelican.com"&gt;Pelican&lt;/a&gt;&lt;/strong&gt; — Python static site generator. Blog
  posts and project writeups are markdown files; the folder they live in
  (&lt;code&gt;content/blog/&lt;/code&gt; or &lt;code&gt;content/projects/&lt;/code&gt;) decides which section they land in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://tailwindcss.com"&gt;Tailwind CSS v4&lt;/a&gt; + &lt;a href="https://daisyui.com"&gt;daisyUI 5&lt;/a&gt;&lt;/strong&gt;
  — all styling config lives in a single CSS file, no &lt;code&gt;tailwind.config.js&lt;/code&gt;.
  The theme is one custom daisyUI theme: charcoal base, cream ink, hard green
  and loud yellow, zero border radius.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions → GitHub Pages&lt;/strong&gt; — every push to &lt;code&gt;main&lt;/code&gt; builds the CSS and
  the site, then deploys. No servers, nothing to maintain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://formspree.io"&gt;Formspree&lt;/a&gt;&lt;/strong&gt; — the contact form on a static site,
  without publishing an email address anywhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://ghchart.rshah.org"&gt;ghchart&lt;/a&gt;&lt;/strong&gt; — the contribution graph, flipped
  to dark mode with a CSS &lt;code&gt;invert&lt;/code&gt; + &lt;code&gt;hue-rotate&lt;/code&gt; trick.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="design-notes"&gt;Design notes&lt;/h2&gt;
&lt;p&gt;The look is deliberate neubrutalism: 2px borders, hard offset shadows with no
blur, cards that physically press down on hover, Archivo Black shouting the
headlines while IBM Plex Mono handles the labels. One theme, executed hard —
no light/dark toggle to hedge with.&lt;/p&gt;
&lt;h2 id="why-a-static-site-in-2026"&gt;Why a static site in 2026&lt;/h2&gt;
&lt;p&gt;Because a portfolio is mostly text that changes a few times a year. Markdown
in git, a build that takes half a second, free hosting, and nothing to patch
on a Tuesday night. Boring technology, chosen on purpose.&lt;/p&gt;</content><category term="projects"/><category term="python"/><category term="pelican"/><category term="tailwind"/><category term="daisyui"/><category term="github.io"/></entry><entry><title>Súbete Perra — local event discovery for Chile</title><link href="https://picklecillo.xyz/projects/subete-perra-local-event-discovery-for-chile/" rel="alternate"/><published>2026-06-01T00:00:00-04:00</published><updated>2026-06-01T00:00:00-04:00</updated><author><name>Gael Espinosa</name></author><id>tag:picklecillo.xyz,2026-06-01:/projects/subete-perra-local-event-discovery-for-chile/</id><summary type="html">&lt;p&gt;A free event discovery platform for Chile — find what's happening near you, or publish your own event with zero friction. Django 6 on Python 3.13, PostgreSQL, deployed on Fly.io.&lt;/p&gt;</summary><content type="html">&lt;h2 id="what-it-is"&gt;What it is&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://subeteperra.cl"&gt;subeteperra.cl&lt;/a&gt; helps people discover events happening
around them — music, arts, sports, tech, LGBTQIA+, food, and more — and lets
organizers publish events without fees or gatekeeping. Browse by category,
filter free events, reserve a spot, show up.&lt;/p&gt;
&lt;p&gt;Fully bilingual (Spanish/English) via Django's i18n.&lt;/p&gt;
&lt;h2 id="the-stack"&gt;The stack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Django 6.0 on Python 3.13&lt;/strong&gt;, dependencies managed with uv, everything
  running in Docker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL 17&lt;/strong&gt; behind the scenes, &lt;strong&gt;Tailwind&lt;/strong&gt; on the front&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fly.io&lt;/strong&gt; for hosting, WhiteNoise for static files, Sentry for errors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;geopy&lt;/strong&gt; powers the location-based discovery, &lt;strong&gt;icalendar&lt;/strong&gt; exports events
  straight to your calendar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude API&lt;/strong&gt; in the mix for AI-assisted features&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;django-axes&lt;/strong&gt; rate-limits login attempts; &lt;strong&gt;MessageBird&lt;/strong&gt; handles SMS&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="engineering-notes"&gt;Engineering notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tested end-to-end&lt;/strong&gt; — pytest + factory-boy for the unit/integration layer,
  Playwright driving a real browser for the flows that matter (browse →
  register → attend).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A companion Chrome extension&lt;/strong&gt; lives in the same repo for capturing events
  from around the web.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boring, deployable architecture&lt;/strong&gt; — one Django app, one database, one
  &lt;code&gt;fly deploy&lt;/code&gt;. No microservices for a product that fits in one head.&lt;/li&gt;
&lt;/ul&gt;</content><category term="projects"/><category term="django"/><category term="postgresql"/><category term="tailwind"/><category term="fly.io"/></entry><entry><title>ClickDocs — export ClickUp docs as markdown</title><link href="https://picklecillo.xyz/projects/clickdocs-export-clickup-docs-as-markdown/" rel="alternate"/><published>2026-05-01T00:00:00-04:00</published><updated>2026-05-01T00:00:00-04:00</updated><author><name>Gael Espinosa</name></author><id>tag:picklecillo.xyz,2026-05-01:/projects/clickdocs-export-clickup-docs-as-markdown/</id><summary type="html">&lt;p&gt;A freemium web tool that exports ClickUp workspace docs as portable markdown files. Python library + CLI at the core, Django web UI on top, deployed on Fly.io.&lt;/p&gt;</summary><content type="html">&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;Teams write years of documentation into ClickUp, and then it lives there —
locked behind the app, unsearchable from the terminal, invisible to git, and
awkward to migrate. ClickDocs pulls it out: point it at a workspace and get
clean markdown files back.&lt;/p&gt;
&lt;h2 id="how-its-structured"&gt;How it's structured&lt;/h2&gt;
&lt;p&gt;Two components in a UV workspace:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;clickup-exporter&lt;/code&gt;&lt;/strong&gt; — a standalone Python library and CLI that walks the
  ClickUp API (docs, nested pages) and writes them out as markdown. Small
  dependency footprint: &lt;code&gt;requests&lt;/code&gt; for the API, &lt;code&gt;mdutils&lt;/code&gt; for generation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;export-site&lt;/code&gt;&lt;/strong&gt; — a Django + Tailwind web UI wrapping the library at
  &lt;a href="https://clickdocs.xyz"&gt;clickdocs.xyz&lt;/a&gt;: register, connect your workspace,
  export. Freemium — the first 10 pages are free.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deployed on Fly.io with Docker.&lt;/p&gt;
&lt;h2 id="interesting-bits"&gt;Interesting bits&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nested page trees&lt;/strong&gt; — ClickUp docs are hierarchical; mapping that onto a
  sane directory structure with correct relative links is most of the actual
  work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API testing without the API&lt;/strong&gt; — the test suite uses &lt;code&gt;pytest-vcr&lt;/code&gt; to record
  real ClickUp API responses once and replay them forever, so tests are fast
  and deterministic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Library-first design&lt;/strong&gt; — the web product is a thin wrapper; anyone who'd
  rather stay in the terminal can use the CLI directly against their own API
  token.&lt;/li&gt;
&lt;/ul&gt;</content><category term="projects"/><category term="python"/><category term="django"/><category term="tailwind"/><category term="fly.io"/></entry><entry><title>CVora — AI-powered career toolkit</title><link href="https://picklecillo.xyz/projects/cvora-ai-powered-career-toolkit/" rel="alternate"/><published>2026-03-01T00:00:00-03:00</published><updated>2026-03-01T00:00:00-03:00</updated><author><name>Gael Espinosa</name></author><id>tag:picklecillo.xyz,2026-03-01:/projects/cvora-ai-powered-career-toolkit/</id><summary type="html">&lt;p&gt;Full-stack AI career toolkit built and deployed in 3 days. ATS scoring against job descriptions, batch multi-JD comparison, interactive per-role resume coaching, and resume export as structured RenderCV YAML with PDF download.&lt;/p&gt;</summary><content type="html">&lt;h2 id="what-it-does"&gt;What it does&lt;/h2&gt;
&lt;p&gt;CVora takes your resume and puts it to work against real job descriptions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ATS scoring&lt;/strong&gt; — paste a job description, get a match score with a breakdown
  of what lands and what's missing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch comparison&lt;/strong&gt; — score one resume against multiple job descriptions at
  once and see where you're strongest.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-role coaching&lt;/strong&gt; — an interactive session that walks through your resume
  in the context of a specific role and suggests concrete improvements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured export&lt;/strong&gt; — resumes live as &lt;a href="https://rendercv.com"&gt;RenderCV&lt;/a&gt;
  YAML, so output is versionable and renders to a clean PDF for download.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-its-built"&gt;How it's built&lt;/h2&gt;
&lt;p&gt;Django + HTMX on the backend-driven-UI end, the Claude API for the scoring and
coaching intelligence, deployed on Fly.io. From first commit to production in
three days.&lt;/p&gt;
&lt;p&gt;The HTMX approach keeps the whole thing server-rendered: no SPA build step, no
client state to sync — every AI interaction is a partial swap.&lt;/p&gt;</content><category term="projects"/><category term="django"/><category term="htmx"/><category term="claude-api"/><category term="fly.io"/></entry><entry><title>ink+ — create Inkdrop notes from Alfred</title><link href="https://picklecillo.xyz/projects/ink-create-inkdrop-notes-from-alfred/" rel="alternate"/><published>2024-09-26T00:00:00-03:00</published><updated>2024-09-26T00:00:00-03:00</updated><author><name>Gael Espinosa</name></author><id>tag:picklecillo.xyz,2024-09-26:/projects/ink-create-inkdrop-notes-from-alfred/</id><summary type="html">&lt;p&gt;Open-source contribution to the official Inkdrop Alfred workflow — a new ink+ command that creates notes straight from the Alfred bar, with configurable default notebook and tags.&lt;/p&gt;</summary><content type="html">&lt;h2 id="the-itch"&gt;The itch&lt;/h2&gt;
&lt;p&gt;The official &lt;a href="https://www.inkdrop.app"&gt;Inkdrop&lt;/a&gt; Alfred workflow could only
&lt;em&gt;search&lt;/em&gt; notes. Capturing a thought still meant switching to the app, finding
the right notebook, making a note — enough friction to lose the thought.&lt;/p&gt;
&lt;h2 id="the-contribution"&gt;The contribution&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/inkdropapp/inkdrop-alfred-workflow/pull/4"&gt;Merged PR #4&lt;/a&gt;
adds an &lt;code&gt;ink+&lt;/code&gt; command: type &lt;code&gt;ink+ title: contents&lt;/code&gt; and the note lands in
Inkdrop without ever leaving the keyboard.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Alfred bar showing ink+ command creating a new Inkdrop note" src="https://picklecillo.xyz/images/inkdrop-alfred-ink-plus.png"&gt;&lt;/p&gt;
&lt;p&gt;What went in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ink+ &amp;lt;title&amp;gt;:&amp;lt;contents&amp;gt;&lt;/code&gt;&lt;/strong&gt; — new command hitting Inkdrop's local server
  API to create the note.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;defaultNotebook&lt;/code&gt; and &lt;code&gt;defaultTags&lt;/code&gt;&lt;/strong&gt; workflow environment variables, so
  captured notes are filed and tagged automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refactor&lt;/strong&gt; — &lt;code&gt;search.php&lt;/code&gt; became &lt;code&gt;inkdrop.php&lt;/code&gt;, since the workflow was no
  longer just search.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failure feedback&lt;/strong&gt; — a macOS notification when the local server errors
  out, instead of silently eating the note.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="why-its-on-this-list"&gt;Why it's on this list&lt;/h2&gt;
&lt;p&gt;Small diff, daily payoff — this is the kind of tooling contribution I enjoy:
find the friction in a tool you actually use, remove it upstream so everyone
gets the fix.&lt;/p&gt;</content><category term="projects"/><category term="php"/><category term="alfred"/><category term="open-source"/><category term="contribution"/></entry><entry><title>Inkdrop linking plugins — note-from-selection + search-notes</title><link href="https://picklecillo.xyz/projects/inkdrop-linking-plugins-note-from-selection-search-notes/" rel="alternate"/><published>2023-05-13T00:00:00-04:00</published><updated>2023-05-13T00:00:00-04:00</updated><author><name>Gael Espinosa</name></author><id>tag:picklecillo.xyz,2023-05-13:/projects/inkdrop-linking-plugins-note-from-selection-search-notes/</id><summary type="html">&lt;p&gt;Two community plugins that bring wiki-style linking to Inkdrop — split selected text into a new linked note, or search and link to an existing one — nearly 700 installs combined.&lt;/p&gt;</summary><content type="html">&lt;h2 id="one-workflow-two-plugins"&gt;One workflow, two plugins&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.inkdrop.app"&gt;Inkdrop&lt;/a&gt; is a markdown note app; out of the box,
linking notes together meant hunting for IDs by hand. These two plugins cover
both directions of a wiki-style linking workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;note-from-selection&lt;/strong&gt; — selected text becomes a &lt;em&gt;new&lt;/em&gt; linked note&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;search-notes&lt;/strong&gt; — link to (or jump to) an &lt;em&gt;existing&lt;/em&gt; note, searched from
  inside the editor&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="note-from-selection"&gt;note-from-selection&lt;/h2&gt;
&lt;p&gt;&lt;img alt="note-from-selection on the Inkdrop plugin registry, v0.6.1, 269 installs" src="https://picklecillo.xyz/images/inkdrop-note-from-selection.png"&gt;&lt;/p&gt;
&lt;p&gt;Select text, hit &lt;code&gt;cmd-shift-e&lt;/code&gt;, and the selection becomes its own note — the
original text replaced by a link. &lt;code&gt;cmd-shift-ctrl-e&lt;/code&gt; pops a notebook picker to
file it elsewhere. The details that matter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No naming prompt&lt;/strong&gt; — the first line of the selection (up to a
  configurable 80 chars) becomes the title, so splitting a note never breaks
  flow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Links that stay fresh&lt;/strong&gt; — the inserted link is left with blank text on
  purpose: Inkdrop then renders the referenced note's &lt;em&gt;live&lt;/em&gt; title, status,
  and tags in previews. The title also lands in an HTML comment next to the
  link for readability in the raw editor:&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[](&lt;/span&gt;&lt;span class="n"&gt;inkdrop&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;noteId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="search-notes"&gt;search-notes&lt;/h2&gt;
&lt;p&gt;&lt;img alt="search-notes on the Inkdrop plugin registry, v0.6.3, 415 installs" src="https://picklecillo.xyz/images/inkdrop-search-notes.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/picklecillo/inkdrop-plugin-search-notes"&gt;The second plugin&lt;/a&gt;
opens a search modal without leaving the editor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cmd-shift-s&lt;/code&gt; — search notes and &lt;strong&gt;insert a link&lt;/strong&gt; to the selected result;
  works on selected text too.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alt-cmd-space&lt;/code&gt; — search and &lt;strong&gt;navigate&lt;/strong&gt; to the result, which pairs
  naturally with Inkdrop's back/forward navigation commands.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="coexisting-with-the-core-app"&gt;Coexisting with the core app&lt;/h2&gt;
&lt;p&gt;Inkdrop v5.8.0 later shipped its own note-from-selection and backlinks panel.
Instead of dying, the plugins leaned into what core doesn't do — prompt-free
default titles, cross-notebook filing, link-insertion from search — and
dropped the features core now did better. A plugin shouldn't compete with the
app on things the app does well.&lt;/p&gt;</content><category term="projects"/><category term="javascript"/><category term="inkdrop"/><category term="open-source"/><category term="plugin"/></entry><entry><title>Virtualenv env vars</title><link href="https://picklecillo.xyz/blog/virtualenv-env-vars/" rel="alternate"/><published>2022-07-08T00:00:00-04:00</published><updated>2022-07-08T00:00:00-04:00</updated><author><name>Gael Espinosa</name></author><id>tag:picklecillo.xyz,2022-07-08:/blog/virtualenv-env-vars/</id><summary type="html">&lt;p&gt;Auto-load a project's .env file when activating a virtualenv, with a postactivate script and a grep trick for switching environments by commenting lines.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Setup &lt;code&gt;postactivate&lt;/code&gt; script on a new virtualenv&lt;/p&gt;
&lt;h3 id="virtualenvsprojectbinpostactivate"&gt;&lt;code&gt;~/.virtualenvs/project/bin/postactivate&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/local/Cellar/bash/5.0.18/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/some/project/path/

&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^#&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_PATH&lt;/span&gt;/.env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xargs&lt;span class="k"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="someprojectpathenv"&gt;&lt;code&gt;~/some/project/path/.env&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# DEV ENV&lt;/span&gt;
&lt;span class="c1"&gt;# DATABASE_URL=&amp;lt;some_dev_env_db_url&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;# LOCAL ENV&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql://&amp;lt;local_db_url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; of all not commented lines allows for an easy env vars switch.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;virtualenvwrapper&lt;/code&gt;, just the &lt;code&gt;.env&lt;/code&gt; file and rerun the &lt;code&gt;workon project&lt;/code&gt; cmd.&lt;/p&gt;</content><category term="blog"/><category term="python"/><category term="virtualenv"/></entry></feed>