<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Jonathan Peacher</title><link>https://www.djpeacher.com/posts/</link><description>Recent content in Posts on Jonathan Peacher</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>All work licensed under CC BY-SA 4.0 unless otherwise stated.</copyright><lastBuildDate>Tue, 01 Jul 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://www.djpeacher.com/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Tracking Monster Damage With Xs and Slashes</title><link>https://www.djpeacher.com/posts/tracking-monster-damage-with-xs-and-slashes/</link><pubDate>Tue, 01 Jul 2025 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/tracking-monster-damage-with-xs-and-slashes/</guid><description>
&lt;p>&lt;a href="https://youtu.be/Uf9eZdjbzUE&amp;amp;t=2168">Mike Shea (Sly Flourish) on the Lazy RPG Talk Show&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>Turns out subtracting is hard [when tracking monster damage]&amp;hellip;so instead of subtracting damage from its monsters hit points, I would just track the damage up&amp;hellip;when the damage it took crosses over its hit points, then it&amp;rsquo;s time to die&amp;hellip;so what I started doing was just using X&amp;rsquo;s and slashes.&lt;/p>&lt;/blockquote>
&lt;p>This is a great suggestion! I&amp;rsquo;ve just been using standard tally marks, but I could definitely see how this streamlines things. Going to have to try it out at my next game.&lt;/p>
&lt;p>TLDR: Use &lt;code>/&lt;/code> for 5 damage and &lt;code>X&lt;/code> for 10 damage. Cross a &lt;code>/&lt;/code> with &lt;code>\&lt;/code> to make an &lt;code>X&lt;/code>. Use standard tallies for remainders. When you cross out 5 tallies, create a new &lt;code>/&lt;/code> and disregard those tallies.&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Tracking%20Monster%20Damage%20With%20Xs%20and%20Slashes">Reply via email</a></description></item><item><title>Going to try link blogging</title><link>https://www.djpeacher.com/posts/250613-going-to-try-link-blogging/</link><pubDate>Fri, 13 Jun 2025 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/250613-going-to-try-link-blogging/</guid><description>
&lt;p>Matt Birchler: &lt;a href="https://birchtree.me/blog/why-i-love-link-blogging/">Why I Love Link Blogging&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>The web allows us to create content that is connected with the rest of the web. Everything we do, especially us writers, is kicked off by something someone else said, and we should embrace that. Make your blog a part of a conversation, not an island that feels like you’re just doing this all on your own. None of us are, and we should be proud of that.&lt;/p>&lt;/blockquote>
&lt;p>Simon Willison: &lt;a href="https://simonwillison.net/2024/Dec/22/link-blog/">My approach to running a link blog&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>The purpose of my link blog: it’s an ongoing log of things I’ve found—effectively a combination of public bookmarks and my own thoughts and commentary on why those things are interesting.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>In &lt;a href="https://simonwillison.net/2024/Dec/22/link-blog/">a previous article&lt;/a> I proposed two categories of content that are low stakes and high value: things I learned and descriptions of my projects.
I realize now that link blogging deserves to be included a third category of low stakes, high value writing. We could think of that category as things I’ve found.&lt;/p>&lt;/blockquote>
&lt;p>I want to post more on this site, but I consistently struggle to find the time and the attention span to write up a good post. So I&amp;rsquo;m going to try link blogging, something two of my favorite, and most prolific, bloggers, Matt Birchler and Simon Willison, do on a regular basis.&lt;/p>
&lt;p>I&amp;rsquo;m still going to try to write some more long-form posts when inspiration strikes, but I&amp;rsquo;m hoping that these lower-stakes posts will help me build up a regular posting habit. Plus, I think they serve as a good reference for things I&amp;rsquo;ve found and thought were interesting.&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Going%20to%20try%20link%20blogging">Reply via email</a></description></item><item><title>PyTexas 2025</title><link>https://www.djpeacher.com/posts/pytexas-2025/</link><pubDate>Tue, 15 Apr 2025 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/pytexas-2025/</guid><description>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#day-1">Day 1&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keynote-mariatta">Keynote: Mariatta&lt;/a>&lt;/li>
&lt;li>&lt;a href="#reducing-the-oops-factor-pipelines-for-securing-your-python-development-lifecycle-on-a-budget">Reducing the &amp;ldquo;Oops Factor&amp;rdquo;: Pipelines for Securing your Python Development Lifecycle on a budget&lt;/a>&lt;/li>
&lt;li>&lt;a href="#the-pythonic-ideal-in-the-age-of-generative-ai">The Pythonic Ideal in The Age of Generative AI&lt;/a>&lt;/li>
&lt;li>&lt;a href="#generators-the-unsung-hero-of-async-programming">Generators: The Unsung Hero of Async Programming&lt;/a>&lt;/li>
&lt;li>&lt;a href="#demystifying-ai-agents-with-python-code">Demystifying AI Agents with Python Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="#python-untethered-building-robust-embedded-systems">Python Untethered: Building Robust Embedded Systems&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lighting-talks">Lighting Talks&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-2">Day 2&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keynote-jay-miller">Keynote: Jay Miller&lt;/a>&lt;/li>
&lt;li>&lt;a href="#building-a-test-framework-from-scratch-or-not">Building a Test Framework From Scratch (Or Not)!&lt;/a>&lt;/li>
&lt;li>&lt;a href="#place-making-and-productivity-build-maintainable-broad-scale-tools-with-a-small-team">Place-making and Productivity: Build Maintainable Broad-scale Tools With a Small Team&lt;/a>&lt;/li>
&lt;li>&lt;a href="#verbs-not-nouns-writing-documentation-users-want-to-read">Verbs, Not Nouns: Writing Documentation Users Want to Read&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lighting-talks-1">Lighting Talks&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;h2 id="day-1">Day 1&lt;/h2>
&lt;h3 id="keynote-mariatta">Keynote: Mariatta&lt;/h3>
&lt;p>❤️&lt;/p>
&lt;h3 id="reducing-the-oops-factor-pipelines-for-securing-your-python-development-lifecycle-on-a-budget">Reducing the &amp;ldquo;Oops Factor&amp;rdquo;: Pipelines for Securing your Python Development Lifecycle on a budget&lt;/h3>
&lt;ul>
&lt;li>More projects are having security issues, especially now with AI and vibe coding.&lt;/li>
&lt;li>A &amp;ldquo;swish cheese&amp;rdquo; approach to securing your codebase, most of which can be integrated into your CI pipeline.
&lt;ul>
&lt;li>Secret Detection: &lt;a href="https://github.com/gitleaks/gitleaks">gitleaks&lt;/a>&lt;/li>
&lt;li>Dependency Scanning: &lt;a href="https://github.com/pypa/pip-audit">pip-audit&lt;/a>&lt;/li>
&lt;li>SAST (Static Analytis Security Testing): &lt;a href="https://github.com/PyCQA/bandit">bandit&lt;/a>&lt;/li>
&lt;li>DAST (Dynamic Analysis Security Testing): Dastardly / Tenable&lt;/li>
&lt;li>YOU&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="the-pythonic-ideal-in-the-age-of-generative-ai">The Pythonic Ideal in The Age of Generative AI&lt;/h3>
&lt;ul>
&lt;li>AI ← LLM ← Coding Assistant&lt;/li>
&lt;li>An experienced engineer is defined by their experiences, not by the duration of their role.&lt;/li>
&lt;li>Pros/Cons of using a coding assistants:&lt;/li>
&lt;/ul>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Coding assistant&lt;/th>
&lt;th>Beginners&lt;/th>
&lt;th>Experienced&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Write code fast&lt;/td>
&lt;td>Con&lt;/td>
&lt;td>Pro (for boilerplate)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Write code beyond skill level&lt;/td>
&lt;td>Pro/Con&lt;/td>
&lt;td>Pro/Con&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Explain code&lt;/td>
&lt;td>Pro&lt;/td>
&lt;td>Pro&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Find bugs&lt;/td>
&lt;td>Pro/Con&lt;/td>
&lt;td>Pro/Con&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hallucinate&lt;/td>
&lt;td>Con&lt;/td>
&lt;td>Con&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Vibe coding&lt;/td>
&lt;td>Con&lt;/td>
&lt;td>Con&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Viable pair programmer&lt;/td>
&lt;td>-&lt;/td>
&lt;td>Pro&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ethical issues&lt;/td>
&lt;td>-&lt;/td>
&lt;td>Con&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>If you are a beginner, write code first, then use AI to explore when stuck, but make sure to understand the generated code.&lt;/li>
&lt;li>If you are experienced, let AI write the boring stuff (boilerplate, consistent code, etc.), and level up by prompting for code beyond your comfort zone (ask for options).&lt;/li>
&lt;li>If you are a teacher, use AI from the start. Ask it to suggest alternatives to existing code.&lt;/li>
&lt;li>Will AI replace you? Maybe. It will if you refuse to evolve with it.&lt;/li>
&lt;li>Is pythonic code dead? Maybe. But be kind to future developers (including yourself).&lt;/li>
&lt;li>Takeaways:
&lt;ul>
&lt;li>LLMs are just fancy autocomplete&lt;/li>
&lt;li>If we over-rely on AI when first learning, we run the risk of not gaining the experience needed to grow.&lt;/li>
&lt;li>AI is a tool, nothing more.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Advice:
&lt;ul>
&lt;li>Don&amp;rsquo;t panic&lt;/li>
&lt;li>Learn your craft&lt;/li>
&lt;li>Embrace AI&lt;/li>
&lt;li>Teach the next generation&lt;/li>
&lt;li>Keep it pythonic&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="generators-the-unsung-hero-of-async-programming">Generators: The Unsung Hero of Async Programming&lt;/h3>
&lt;ul>
&lt;li>The &lt;code>GeneratorExit&lt;/code> exception can be to cleanup generators.&lt;/li>
&lt;li>You can send data back into a generator using &lt;code>.send(x)&lt;/code> and &lt;code>x = yield y&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h3 id="demystifying-ai-agents-with-python-code">Demystifying AI Agents with Python Code&lt;/h3>
&lt;ul>
&lt;li>What are AI agents?&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&amp;ldquo;agent = llm + memory + planning + tools + while loop&amp;rdquo; – Tweet&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>You can give the ChatGPT API memory by including the previous messages in subsequent API requests.&lt;/li>
&lt;li>You can tell the ChatGPT API to use tools (functions), to do things like searching the web, that returns structured data.
&lt;ul>
&lt;li>You can use &lt;code>.signature()&lt;/code> to generate the function specs for these tools.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Tools like &lt;a href="https://github.com/crewAIInc/crewAI">crewAI&lt;/a> abstract all this away.&lt;/li>
&lt;/ul>
&lt;h3 id="python-untethered-building-robust-embedded-systems">Python Untethered: Building Robust Embedded Systems&lt;/h3>
&lt;ul>
&lt;li>What are embedded systems?&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&amp;ldquo;&amp;hellip;[an] embedded system is a device that contains a computer that the average person would not consider being a computer.&amp;rdquo; – Reddit&lt;/p>&lt;/blockquote>
&lt;h3 id="lighting-talks">Lighting Talks&lt;/h3>
&lt;ul>
&lt;li>&lt;code>pip install interrogate&lt;/code> for docstring coverage.&lt;/li>
&lt;/ul>
&lt;h2 id="day-2">Day 2&lt;/h2>
&lt;h3 id="keynote-jay-miller">Keynote: Jay Miller&lt;/h3>
&lt;ul>
&lt;li>Contributing to open source and community are similar.
&lt;ul>
&lt;li>You have an idea and want to share it with others.&lt;/li>
&lt;li>When you give away your ideas, they only tend to get better.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&amp;ldquo;Creating a new open source package (and community) is like adopting a puppy.&amp;rdquo; - Pamela Fox&lt;/li>
&lt;li>Let people know what help you need, and vice versa.&lt;/li>
&lt;li>Autocorrect &amp;ldquo;LGTM&amp;rdquo; to &amp;ldquo;Looks good to me, thank you!&amp;rdquo; and don&amp;rsquo;t say &amp;ldquo;just x&amp;rdquo;.&lt;/li>
&lt;li>There is always more that you could have done, but if you are doing something, it is helpful.&lt;/li>
&lt;li>Respect everyone&amp;rsquo;s capacity.&lt;/li>
&lt;li>Ambition + Inspration = Scope Creep&lt;/li>
&lt;/ul>
&lt;h3 id="building-a-test-framework-from-scratch-or-not">Building a Test Framework From Scratch (Or Not)!&lt;/h3>
&lt;ul>
&lt;li>What is a testing framework? A programming structure for coding automated test cases, executing them as a suite, and reporting their results.&lt;/li>
&lt;li>Just use &lt;code>pytest&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h3 id="place-making-and-productivity-build-maintainable-broad-scale-tools-with-a-small-team">Place-making and Productivity: Build Maintainable Broad-scale Tools With a Small Team&lt;/h3>
&lt;ul>
&lt;li>&lt;code>pain = n_teams * size_team * n_tools * n_migrations * (1 + r_turnover)&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="verbs-not-nouns-writing-documentation-users-want-to-read">Verbs, Not Nouns: Writing Documentation Users Want to Read&lt;/h3>
&lt;ul>
&lt;li>Two types: Reference / Tutorial&lt;/li>
&lt;li>Reference (Nowns): Comprehensive, focuses on the product, quick retrieval
&lt;ul>
&lt;li>e.g. man pages, api reference, code coments, readme files.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Tutorial (Verbs): Not comprehensive, focus on the user, and read beginning to end
&lt;ul>
&lt;li>e.g. tutorials, how-tos, walkthroughs, use cases&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Outline your doc first:
&lt;ul>
&lt;li>What do you want user to do?&lt;/li>
&lt;li>Ask the users, if you can&lt;/li>
&lt;li>Write down everything&lt;/li>
&lt;li>Define discrete steps&lt;/li>
&lt;li>Prioritize the use cases&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Advice
&lt;ul>
&lt;li>Look for the verbs&lt;/li>
&lt;li>Focus on what the users want to do&lt;/li>
&lt;li>Move the verbs up front&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="lighting-talks-1">Lighting Talks&lt;/h3>
&lt;ul>
&lt;li>LLMs are like APIs. You make a request, and they return data, but with &amp;ldquo;moods&amp;rdquo;.&lt;/li>
&lt;li>Micromentor with SLQAR: Show up, listen, question, act, recharge.&lt;/li>
&lt;li>How to say thanks to Python People:
&lt;ul>
&lt;li>Level 0: Actually say thanks&lt;/li>
&lt;li>Level 1: Say thanks in public&lt;/li>
&lt;li>Level 2: Show your support&lt;/li>
&lt;li>Level 3: Contribute/volunteer&lt;/li>
&lt;li>Level 4: Contribute money&lt;/li>
&lt;li>Level 5: Nominate them for awards&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Be kind to your screen sharing partner. Just share the window or tab.&lt;/li>
&lt;li>&amp;ldquo;Make friends, not connections&amp;rdquo; - Kojo&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20PyTexas%202025">Reply via email</a></description></item><item><title>Alamo Drafthouse 2024</title><link>https://www.djpeacher.com/posts/alamo-drafthouse-2024/</link><pubDate>Fri, 17 Jan 2025 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/alamo-drafthouse-2024/</guid><description>
&lt;p>Wow, only 11 movies this year at the Alamo Drafthouse! Turns out having your first child heavily reduces the time you have to go see movies 😫 This will probably be my last Alamo Drafthouse post since we canceled our Season Pass&amp;hellip;for now.&lt;/p>
&lt;ul>
&lt;li>Mean Girls (2024)&lt;/li>
&lt;li>The Zone of Interest&lt;/li>
&lt;li>Anatomy of a Fall&lt;/li>
&lt;li>Drive-Away Dolls&lt;/li>
&lt;li>Dune: Part Two&lt;/li>
&lt;li>Immaculate&lt;/li>
&lt;li>Civil War&lt;/li>
&lt;li>Kingdom of the Planet of the Apes&lt;/li>
&lt;li>Babes&lt;/li>
&lt;li>Inside Out 2&lt;/li>
&lt;li>Bad Boy: Ride or Die&lt;/li>
&lt;/ul>
&lt;p>Here are the previous two years if you&amp;rsquo;re curious. Till next time Alamo 🍿&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.djpeacher.com/posts/alamo-drafthouse-2022/">Alamo Drafthouse 2022&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.djpeacher.com/posts/alamo-drafthouse-2023/">Alamo Drafthouse 2023&lt;/a>&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20Alamo%20Drafthouse%202024">Reply via email</a></description></item><item><title>DjangoCon US 2024</title><link>https://www.djpeacher.com/posts/djangocon-us-2024/</link><pubDate>Fri, 27 Sep 2024 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/djangocon-us-2024/</guid><description>
&lt;p>I wasn&amp;rsquo;t able to attend in person this year or take too many notes due to work, but I was able to enjoy a few great talks in between things! Here&amp;rsquo;s to next year! 🤞🏼&lt;/p>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#day-1">Day 1&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#choosing-wisely-spa-vs-htmx-for-your-next-web-project">Choosing Wisely: SPA vs. HTMX for Your Next Web Project&lt;/a>&lt;/li>
&lt;li>&lt;a href="#error-culture">Error Culture&lt;/a>&lt;/li>
&lt;li>&lt;a href="#product-101-for-techies-and-tech-teams">Product 101 for Techies and Tech Teams&lt;/a>&lt;/li>
&lt;li>&lt;a href="#passkeys-your-password-free-future">Passkeys: Your password-free future&lt;/a>&lt;/li>
&lt;li>&lt;a href="#the-art-of-not-redirecting">The art of (not) redirecting&lt;/a>&lt;/li>
&lt;li>&lt;a href="#-django-the-web-framework-that-changed-my-life">❤️ Django: the web framework that changed my life&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-2">Day 2&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keynote-how-to-be-a-developer-and-other-lies-we-tell-ourselves">Keynote: How To Be A Developer and Other Lies We Tell Ourselves&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lightning-talks">Lightning Talks&lt;/a>&lt;/li>
&lt;li>&lt;a href="#-finding-20">❤️ Finding 2.0&lt;/a>&lt;/li>
&lt;li>&lt;a href="#you-got-that-nice-tech-salary-now-what">You got that nice tech salary, now what?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#how-to-design-and-implement-extensible-software-with-plugins">How to design and implement extensible software with plugins&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-3">Day 3&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#-keynote-the-fellowship-of-the-pony">❤️ Keynote: The Fellowship of the Pony&lt;/a>&lt;/li>
&lt;li>&lt;a href="#-hidden-gems-of-django-5x">❤️ Hidden gems of Django 5.x&lt;/a>&lt;/li>
&lt;li>&lt;a href="#-a-brief-history-of-django">❤️ A Brief History of Django&lt;/a>&lt;/li>
&lt;li>&lt;a href="#django-user-model-past-present-and-future">Django User Model: Past, Present, and Future&lt;/a>&lt;/li>
&lt;li>&lt;a href="#api-maybe-bootstrapping-a-web-application-circa-2024">API Maybe: Bootstrapping a Web Application circa 2024&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;h2 id="day-1">Day 1&lt;/h2>
&lt;h3 id="choosing-wisely-spa-vs-htmx-for-your-next-web-project">Choosing Wisely: SPA vs. HTMX for Your Next Web Project&lt;/h3>
&lt;ul>
&lt;li>Three options: Static HTML, Progressive Enhancements, SPA
&lt;ul>
&lt;li>← more server data, short user sessions&lt;/li>
&lt;li>→ more client data, long user sessions&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Static HTML is fast and simple, but not very interactive.&lt;/li>
&lt;li>SPAs are interactive, but expensive and complicated.&lt;/li>
&lt;li>PEs provide the best of both worlds.&lt;/li>
&lt;/ul>
&lt;h3 id="error-culture">Error Culture&lt;/h3>
&lt;blockquote>
&lt;p>[Error culture] accepts error notifications and ignores them, encouraging a reactive firefighting culture, instead of a proactive culture of problem-solving.&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>Are you in error culture?
&lt;ul>
&lt;li>Is your deleted items filled with lots of emails from no-reply style email addresses that you didn&amp;rsquo;t even read&amp;hellip;you just deleted them?&lt;/li>
&lt;li>Do you have a rule that just deletes emails?&lt;/li>
&lt;li>Do you get alerts and have no idea why or what to do about them?&lt;/li>
&lt;li>Are people rewarded for waiting until problems they knew about are big enough to alert everyone about and then resolve them?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Don&amp;rsquo;t take down a fence unless you know why it was put up. &amp;ndash; Chesterson&amp;rsquo;s Fence&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>Ask yourself these questions when managing alerts:
&lt;ul>
&lt;li>Is the alert important?&lt;/li>
&lt;li>Is the alert actionable?&lt;/li>
&lt;li>Who is the alert for?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Venn Diagram:
&lt;ul>
&lt;li>!Actionable + Important + Right People = Confusion&lt;/li>
&lt;li>Actionable + !Important + Right People = Time Waste&lt;/li>
&lt;li>Actionable + Important + !Right People = Frustration&lt;/li>
&lt;li>Actionable + Important + Right People = Best Case&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="product-101-for-techies-and-tech-teams">Product 101 for Techies and Tech Teams&lt;/h3>
&lt;ul>
&lt;li>What your PM really wants:
&lt;ul>
&lt;li>A team that is interested in the strategy&lt;/li>
&lt;li>Genuine interest in solving problems&lt;/li>
&lt;li>Empathy with end users&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>PM responsibilities
&lt;ul>
&lt;li>Empower their team with strategic insights&lt;/li>
&lt;li>Translate strategy into proposed actionables&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Team responsibilities
&lt;ul>
&lt;li>Take interest in the strategy&lt;/li>
&lt;li>Pay attention to details and quality&lt;/li>
&lt;li>Care for the user experience, from start to finish&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Becoming a product-minded engineer
&lt;ul>
&lt;li>Be interested in the business and its user base&lt;/li>
&lt;li>Stay curious&lt;/li>
&lt;li>Provide relevant input&lt;/li>
&lt;li>Be an ally to your PM&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="passkeys-your-password-free-future">Passkeys: Your password-free future&lt;/h3>
&lt;ul>
&lt;li>django-allauth now supports passkeys!&lt;/li>
&lt;/ul>
&lt;p>The magical incatation! 🪄&lt;/p>
&lt;pre tabindex="0">&lt;code># The main setup.
MFA_SUPPORTED_TYPES = [&amp;#34;webauthn&amp;#34;]
MFA_PASSKEY_LOGIN_ENABLED = True
MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN = True
MFA_PASSKEY_SIGNUP_ENABLED = True
# Allow email recovery.
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = &amp;#34;mandatory&amp;#34;
ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True
# Allow creation without username.
ACCOUNT_AUTHENTICATION_METHOD = &amp;#34;email&amp;#34;
ACCOUNT_USERNAME_REQUIRED = False
&lt;/code>&lt;/pre>&lt;h3 id="the-art-of-not-redirecting">The art of (not) redirecting&lt;/h3>
&lt;blockquote>
&lt;p>Your URLs are a mirror to the soul of your code.&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>Naming is hard, so:
&lt;ul>
&lt;li>Design as best as you can.&lt;/li>
&lt;li>Break as little as possible.&lt;/li>
&lt;li>Avoid 404s at all costs.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Status codes that will help!
&lt;pre tabindex="0">&lt;code>302_FOUND
301_MOVED_PERMANENTLY
307_TEMPORARY_REDIRECT
308_PERMANENT_REDIRECT
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>The art of not redirecting (or how to design URLs):
&lt;ul>
&lt;li>&lt;strong>Readable&lt;/strong>: People will read them, some will even type them by memory.
&lt;ul>
&lt;li>❌ &lt;code>/u/d/1/c/~q/page&lt;/code>&lt;/li>
&lt;li>✔️ &lt;code>/user/profile&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Predictable&lt;/strong>: People should be able to guess-navigate your site by rewriting
URLs.
&lt;ul>
&lt;li>✔️ &lt;code>/user/profile/&lt;/code>&lt;/li>
&lt;li>✔️ &lt;code>/user/settings/&lt;/code>&lt;/li>
&lt;li>✔️ &lt;code>/user/security/&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Concise&lt;/strong>: Straight to the point, no redundancy.
&lt;ul>
&lt;li>❌ &lt;code>/user/user-settings/security-settings&lt;/code>&lt;/li>
&lt;li>✔️ &lt;code>/user/settings/security&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Complete&lt;/strong>: Every part must lead to somewhere, or at least redirect to somewhere else.
&lt;ul>
&lt;li>✔️ &lt;code>/user/settings/security&lt;/code>&lt;/li>
&lt;li>✔️ &lt;code>/user/settings&lt;/code>&lt;/li>
&lt;li>✔️ &lt;code>/user&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Consistent&lt;/strong>: Single language, single style.
&lt;ul>
&lt;li>❌ &lt;code>/current_user/Seguridad/multi-factorAuth&lt;/code>&lt;/li>
&lt;li>✔️ &lt;code>/current-user/security/multi-factor-auth&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Beautifu&lt;/strong>l: Well at least not ugly.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>The art of redirecting (or how to avoid 404s at all costs):
&lt;ul>
&lt;li>&lt;code>django.contrib.redirects&lt;/code>, but that only works for static routes.&lt;/li>
&lt;li>For dynamic routes, they first used a middleware that mapped their old namespace to a new namespace.&lt;/li>
&lt;li>This only works for a single mapping, but what if you want to change them again? They implemented &lt;code>path(..., old=[...])&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="-django-the-web-framework-that-changed-my-life">❤️ Django: the web framework that changed my life&lt;/h3>
&lt;h2 id="day-2">Day 2&lt;/h2>
&lt;h3 id="keynote-how-to-be-a-developer-and-other-lies-we-tell-ourselves">Keynote: How To Be A Developer and Other Lies We Tell Ourselves&lt;/h3>
&lt;blockquote>
&lt;p>To be a better developer is to be a better person to others.&lt;/p>&lt;/blockquote>
&lt;h3 id="lightning-talks">Lightning Talks&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://github.com/django-commons">Django Commons&lt;/a>!&lt;/li>
&lt;li>&lt;a href="https://vurt.org/articles/twelve-rules/">Twelve rules for job applications and interviews&lt;/a> by Daniele Procida&lt;/li>
&lt;/ul>
&lt;h3 id="-finding-20">❤️ Finding 2.0&lt;/h3>
&lt;ul>
&lt;li>The hesitation to call your project 2.0 is a lot like imposter syndrome.&lt;/li>
&lt;li>How to find your 2.0:
&lt;ul>
&lt;li>&lt;strong>Intention&lt;/strong>. Intention to find your 2.0.&lt;/li>
&lt;li>&lt;strong>Assessment&lt;/strong>. Where is your starting point.&lt;/li>
&lt;li>&lt;strong>Vision&lt;/strong>. What is your destination.&lt;/li>
&lt;li>&lt;strong>Acceptance&lt;/strong>. Prepare for breaking changes.&lt;/li>
&lt;li>&lt;strong>Plan&lt;/strong>. Plan each version with a release date!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>When to start?&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Now is better than never &amp;ndash; The Zen of Python&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>Change is coming. Change is inevitable. The question isn&amp;rsquo;t if we are going to change, but how. Because we&amp;rsquo;re either going to change by design or by disaster &amp;ndash; Annie Leonard&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>Don&amp;rsquo;t wait for disaster, 2.0 is not sacred.&lt;/p>&lt;/blockquote>
&lt;h3 id="you-got-that-nice-tech-salary-now-what">You got that nice tech salary, now what?&lt;/h3>
&lt;ul>
&lt;li>You got that nice tech salary, now what?
&lt;ul>
&lt;li>Set financial goals&lt;/li>
&lt;li>Determine savings needs (&lt;a href="https://nesteggly.com">nesteggly.com&lt;/a>)&lt;/li>
&lt;li>Determine current expenses (last 3-6 months) (&lt;a href="https://ynab.com">ynab.com&lt;/a>)
&lt;ul>
&lt;li>Determine fixed, adjustable, and unexpected expenses.&lt;/li>
&lt;li>Be aware of &amp;ldquo;Hedonic Treadmill&amp;rdquo;: A permanent increase in expenses for a temporary increase in pleasure.
&lt;ul>
&lt;li>Example: buying better coffee and not being able to go back&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Define a monthly budget&lt;/li>
&lt;li>Check-in every 3-6 months&lt;/li>
&lt;li>Max out your tax-advantaged accounts&lt;/li>
&lt;li>Index funds&lt;/li>
&lt;li>Term life insurance&lt;/li>
&lt;li>Check out /r/personalfinance/wiki&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>But where do we put our money to grow?!
&lt;ul>
&lt;li>Not your savings account (but do have 3-6 months of expenses there!)&lt;/li>
&lt;li>Use any employer match.&lt;/li>
&lt;li>Max out tax-advantaged accounts
&lt;ul>
&lt;li>Roth/Traditional IRA&lt;/li>
&lt;li>401k&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Purchase low-cost, broad market, index funds.
&lt;ul>
&lt;li>50% US stocks&lt;/li>
&lt;li>30% International stocks&lt;/li>
&lt;li>20% Bonds&lt;/li>
&lt;li>Stocks = Higher reward, riskier&lt;/li>
&lt;li>Bonds = Lower reward, less risky&lt;/li>
&lt;li>Index funds track a collection of stocks index funds that outperform managed funds.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="how-to-design-and-implement-extensible-software-with-plugins">How to design and implement extensible software with plugins&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://github.com/pytest-dev/pluggy">pluggy&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://djp.readthedocs.io/en/latest/">DJP: Django Plugins&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="day-3">Day 3&lt;/h2>
&lt;h3 id="-keynote-the-fellowship-of-the-pony">❤️ Keynote: The Fellowship of the Pony&lt;/h3>
&lt;h3 id="-hidden-gems-of-django-5x">❤️ Hidden gems of Django 5.x&lt;/h3>
&lt;h3 id="-a-brief-history-of-django">❤️ A Brief History of Django&lt;/h3>
&lt;h3 id="django-user-model-past-present-and-future">Django User Model: Past, Present, and Future&lt;/h3>
&lt;blockquote>
&lt;p>&amp;ldquo;We should push the user model back to being Django&amp;rsquo;s responsibility and address that leak.&amp;rdquo; &amp;ndash; Carlton Gibson&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>Universal Concerns
&lt;ul>
&lt;li>User Contract&lt;/li>
&lt;li>Separation of authentication from authorization&lt;/li>
&lt;li>Forms&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>django-allauth solves many of these problems.&lt;/li>
&lt;li>My Current Approach
&lt;ol>
&lt;li>Custom user model&lt;/li>
&lt;li>User profile model on &amp;lsquo;User&amp;rsquo;&lt;/li>
&lt;li>Use &amp;lsquo;django-allauth&amp;rsquo; 3rd party package&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Custom user model (empty) + django-allauth = my preference&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="api-maybe-bootstrapping-a-web-application-circa-2024">API Maybe: Bootstrapping a Web Application circa 2024&lt;/h3>
&lt;blockquote>
&lt;p>Lean into Django.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>Django has always been the framework for perfectionists with deadlines. When those deadlines are financial, all the more so. Django is the perfect web framework for our post-zero percent interest rate world.&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>Locality of Behavior is a starting point not a destination&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>[Locality of behavior] is a tool, not an end in itself.&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>Tools to achieve locality of behavior:
&lt;ul>
&lt;li>&lt;a href="https://github.com/carltongibson/neapolitan">Neapolitan&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/carltongibson/django-template-partials">django-template-partials&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://htmx.org/">HTMX&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://alpinejs.dev/">Alpine.js&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tailwindcss.com/">Tailwind CSS&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20DjangoCon%20US%202024">Reply via email</a></description></item><item><title>What caught my eye at WWDC 2024</title><link>https://www.djpeacher.com/posts/what-caught-my-eye-at-wwdc-2024/</link><pubDate>Thu, 13 Jun 2024 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/what-caught-my-eye-at-wwdc-2024/</guid><description>
&lt;p>Well that was probably the most interesting WWDC in a while. Not saying it was great or perfect, but definitely a turning point. That intro was amazing though! Here&amp;rsquo;s what caught my eye along with my gut reaction:&lt;/p>
&lt;h2 id="ios">iOS&lt;/h2>
&lt;ul>
&lt;li>Custom home screen icon placement - &lt;strong>&lt;em>Wow, finally.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Custom home screen icon tints - &lt;strong>&lt;em>Hmm, not quite right.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Ability to lock apps - &lt;strong>&lt;em>Nice, but also sherlocks this as a paid app feature.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Ability to hide apps - &lt;strong>&lt;em>Sus stuff aside, hiding utility apps is a win.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>iMessage RCS support - &lt;strong>&lt;em>Finally.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>iMessage rich text - &lt;strong>&lt;em>Oh cool.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>iMessage emoji tapbacks - &lt;strong>&lt;em>Finally. Yes. Perfect.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>iMessage scheduled texts - &lt;strong>&lt;em>Finally. Yes. Perfect.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>iMessage over satelite - &lt;strong>&lt;em>Oh cool.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Airpods shake/nod for accept/decline - &lt;strong>&lt;em>Oh cool. That won&amp;rsquo;t misfire at all&amp;hellip;&lt;/em>&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="watchos">watchOS&lt;/h2>
&lt;ul>
&lt;li>Live activities - &lt;strong>&lt;em>Oh cool. Also cool that it reuses dynamic island activities.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Vitals app - &lt;strong>&lt;em>Oh cool, hopefully useful.&lt;/em>&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="ipados">iPadOS&lt;/h2>
&lt;ul>
&lt;li>SharePlay drawing - &lt;strong>&lt;em>Oh cool. I need this on iOS!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>SharePlay control - &lt;strong>&lt;em>Oh cool. I need this on iOS!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Calculator app - &lt;strong>&lt;em>Wow, finally.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Math notes with calculations, variables, and graphs - &lt;strong>&lt;em>Oh wow, yes! Amazing! Wish I had this in college.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Smart script for cleaning hand writing, pasting, and spell checking - &lt;strong>&lt;em>Oh wow, yes! My hand writing is miserable.&lt;/em>&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="macos">macOS&lt;/h2>
&lt;ul>
&lt;li>iPhone Mirroring - &lt;strong>&lt;em>Oh wow, love it, now add this to SharePlay!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>iPhone Notifications - &lt;strong>&lt;em>Oh wow, yes! I&amp;rsquo;ve wanted this before!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Tile snapping - &lt;strong>&lt;em>Wow, finally.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Passwords App - &lt;strong>&lt;em>Wow, finally.&lt;/em>&lt;/strong>
&lt;ul>
&lt;li>&lt;em>I don&amp;rsquo;t think this sherlocks existing password managers. I think the main win here is that this is going to get more people to actually use a password manger who would never before. I’m not switching anytime soon, but this is still a big win and step forward for online security generally.&lt;/em>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="visionos">visionOS&lt;/h2>
&lt;ul>
&lt;li>Widescreen mac mirroring - &lt;strong>&lt;em>Nice, I&amp;rsquo;m sure others will love this.&lt;/em>&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="tvos">tvOS&lt;/h2>
&lt;ul>
&lt;li>InSight - &lt;strong>&lt;em>Eh, cool I guess.&lt;/em>&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="apple-intelligence">Apple Intelligence&lt;/h2>
&lt;ul>
&lt;li>AI == Apple Intelligence - &lt;strong>&lt;em>Haha, of course they find a way to steal AI!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Website summaries - &lt;strong>&lt;em>Nice!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Priority notification - &lt;strong>&lt;em>Oh cool, this is going to be useful!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Writing tools - &lt;strong>&lt;em>Cool, but this does mean more content will be generated.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Image editing - &lt;strong>&lt;em>Love it!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Image generations - &lt;strong>&lt;em>Cool tech&amp;hellip;bad ethics. Also creepy.&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Context aware and conversational Siri with more integrated actions - &lt;strong>&lt;em>YES! Gold star! What Siri was meant to be! Can&amp;rsquo;t wait!&lt;/em>&lt;/strong> ⭐️&lt;/li>
&lt;li>Privacy cloud compute - &lt;strong>&lt;em>Good!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Genmoji - &lt;strong>&lt;em>Haha, I love this!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>Audio transcribing &lt;strong>&lt;em>Oh cool!&lt;/em>&lt;/strong>&lt;/li>
&lt;li>ChatGPT - 🤮&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20What%20caught%20my%20eye%20at%20WWDC%202024">Reply via email</a></description></item><item><title>Cloudy with a chance of totality</title><link>https://www.djpeacher.com/posts/cloudy-with-a-chance-of-totality/</link><pubDate>Fri, 24 May 2024 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/cloudy-with-a-chance-of-totality/</guid><description>
&lt;p>After watching &lt;a href="https://youtu.be/M3-WlpJFcKM?si=pI_PoiISA6j4Xpnz&amp;amp;t=22">the first lightning talk at DjangoCon US last year&lt;/a>, I was inspired to see and photograph the next eclipse&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>, and wouldn&amp;rsquo;t you know it, my house was just outside the path of totality, so I had to see it. Plus I was recently gifted some hand-me-down camera lenses and one of them happened to be 250mm. Just enough to get some good pictures!&lt;/p>
&lt;p>Fast forward to the day of the eclipse. I had all my gear ready and when I checked the weather for the day&amp;hellip;cloudy with a chance of &lt;del>totality&lt;/del> rain. I didn&amp;rsquo;t let that stop me, however, and I was hopeful that the clouds would part. So I drove to my viewing spot in the path of totality and set up shop.&lt;/p>
&lt;p>As predicted, it was very cloudy, and I struggled to find the right settings for the camera, but was able to dial it in just enough when the sun poked through the clouds. As the time drew near&amp;hellip;and then past&amp;hellip;I was worried I wasn&amp;rsquo;t going to see anything, but a few minutes later the clouds parted just enough for me to get some, I think, amazing amateur shots! It was really fun watching the moon make its way across the sun.&lt;/p>
&lt;p>My dreams were short-lived however as, right before totality, the clouds returned, blocking out the money shot! I was incredibly disappointed, but at least I was able to enjoy the eerie feeling of day turning to night.&lt;/p>
&lt;p>Ah well, I guess I will just have to try again in 2045!&lt;/p>
&lt;figure>
&lt;img alt="Cloudy with a chance of totality" src="https://www.djpeacher.com/posts/cloudy-with-a-chance-of-totality/cloudy-with-a-chance-of-totality001.jpg" loading="lazy" />
&lt;figcaption>
1/20s, f/28/5, ISO , 250mm, Canon Canon EOS Rebel T6s
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Cloudy with a chance of totality" src="https://www.djpeacher.com/posts/cloudy-with-a-chance-of-totality/cloudy-with-a-chance-of-totality002.jpg" loading="lazy" />
&lt;figcaption>
1/30s, f/28/5, ISO , 250mm, Canon Canon EOS Rebel T6s
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Cloudy with a chance of totality" src="https://www.djpeacher.com/posts/cloudy-with-a-chance-of-totality/cloudy-with-a-chance-of-totality003.jpg" loading="lazy" />
&lt;figcaption>
1/15s, f/28/5, ISO , 250mm, Canon Canon EOS Rebel T6s
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Cloudy with a chance of totality" src="https://www.djpeacher.com/posts/cloudy-with-a-chance-of-totality/cloudy-with-a-chance-of-totality004.jpg" loading="lazy" />
&lt;figcaption>
13/10s, f/28/5, ISO , 250mm, Canon Canon EOS Rebel T6s
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Cloudy with a chance of totality" src="https://www.djpeacher.com/posts/cloudy-with-a-chance-of-totality/cloudy-with-a-chance-of-totality005.jpeg" loading="lazy" />
&lt;figcaption>
1/623s, f/12/5, ISO , 8/5mm, Apple iPhone 12
&lt;/figcaption>
&lt;/figure>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>April 8, 2024&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div><a href="mailto: reply@djpeacher.com?subject=Re:%20Cloudy%20with%20a%20chance%20of%20totality">Reply via email</a></description></item><item><title>PyTexas 2024</title><link>https://www.djpeacher.com/posts/pytexas-2024/</link><pubDate>Sun, 21 Apr 2024 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/pytexas-2024/</guid><description>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#day-1">Day 1&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keynote-the-design-of-everyday-apis">Keynote: The Design of Everyday APIs&lt;/a>&lt;/li>
&lt;li>&lt;a href="#python-code-vs-pythonic-code">Python Code vs Pythonic Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="#containercraft-mastering-efficient-integration-testing">ContainerCraft: Mastering Efficient Integration Testing&lt;/a>&lt;/li>
&lt;li>&lt;a href="#anarchy-to-order---organizing-assorted-data-with-python-and-llms">Anarchy to Order - Organizing Assorted Data with Python and LLMs&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lessons-learned-maintaining-open-source-python-projects">Lessons Learned Maintaining Open-Source Python Projects&lt;/a>&lt;/li>
&lt;li>&lt;a href="#working-with-audio-in-python">Working with Audio in Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="#rest-easy-with-jupyrest">Rest Easy with Jupyrest&lt;/a>&lt;/li>
&lt;li>&lt;a href="#iterate-iterate-iterate">Iterate, Iterate, Iterate!&lt;/a>&lt;/li>
&lt;li>&lt;a href="#voice-computing-with-python-in-jupyter-notebooks">Voice Computing with Python in Jupyter Notebooks&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lightning-talks">Lightning Talks&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-2">Day 2&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keynote-thriving-with-python">Keynote: Thriving with Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="#20-goto-10-how-to-make-scrolling-ascii-art">20 GOTO 10: How to Make Scrolling ASCII Art&lt;/a>&lt;/li>
&lt;li>&lt;a href="#system-design-on-easy-mode">System Design on Easy Mode&lt;/a>&lt;/li>
&lt;li>&lt;a href="#building-efficient-containers-for-python-applications">Building Efficient Containers for Python Applications&lt;/a>&lt;/li>
&lt;li>&lt;a href="#always-use-sets">Always Use Sets&lt;/a>&lt;/li>
&lt;li>&lt;a href="#oh-the-methods-you-can-make-by-dunder-seuss">Oh the (Methods) You Can (Make): By Dunder Seuss&lt;/a>&lt;/li>
&lt;li>&lt;a href="#sanely-working-with-legacy-code">Sanely Working With Legacy Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lightning-talks-1">Lightning Talks&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;h2 id="day-1">Day 1&lt;/h2>
&lt;h3 id="keynote-the-design-of-everyday-apis">Keynote: The Design of Everyday APIs&lt;/h3>
&lt;ul>
&lt;li>The Design of Everyday Things - Don Norman&lt;/li>
&lt;li>Good API design is &lt;strong>intuitive&lt;/strong>, &lt;strong>flexible&lt;/strong>, and &lt;strong>simple&lt;/strong>.&lt;/li>
&lt;li>Intuitive:
&lt;ul>
&lt;li>Use domain nomenclature&lt;/li>
&lt;li>Clumsy naming hints at clumsy abstractions&lt;/li>
&lt;li>Provide symmetry&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Flexible:
&lt;ul>
&lt;li>Provide sane defaults&lt;/li>
&lt;li>Minimize repetition&lt;/li>
&lt;li>Be predictable and precise&lt;/li>
&lt;li>Let users be lazy&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Simple:
&lt;ul>
&lt;li>Provide composable functions&lt;/li>
&lt;li>Leverage language idioms&lt;/li>
&lt;li>Provide convenience&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Provide a good README:
&lt;pre tabindex="0">&lt;code># README
## Installation
## Get Started
## Learn More
&lt;/code>&lt;/pre>&lt;/li>
&lt;/ul>
&lt;h3 id="python-code-vs-pythonic-code">Python Code vs Pythonic Code&lt;/h3>
&lt;ul>
&lt;li>Experts &amp;ldquo;recognize&amp;rdquo;, beginners &amp;ldquo;reason&amp;rdquo;&lt;/li>
&lt;li>Python Power Moves
&lt;ul>
&lt;li>Decorators&lt;/li>
&lt;li>Comprehensions&lt;/li>
&lt;li>Generators&lt;/li>
&lt;li>Slicing&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Why is python hard?
&lt;ul>
&lt;li>Python code != Pythonic code&lt;/li>
&lt;li>Python code != &amp;lt;any other language&amp;rsquo;s concepts&amp;gt;&lt;/li>
&lt;li>Python is (too) easy&lt;/li>
&lt;li>What memory&lt;/li>
&lt;li>Python needs &lt;em>lots&lt;/em> of testing&lt;/li>
&lt;li>All about those libraries&amp;hellip;&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="containercraft-mastering-efficient-integration-testing">ContainerCraft: Mastering Efficient Integration Testing&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://testcontainers.com/">https://testcontainers.com/&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="anarchy-to-order---organizing-assorted-data-with-python-and-llms">Anarchy to Order - Organizing Assorted Data with Python and LLMs&lt;/h3>
&lt;ul>
&lt;li>Links in a graph are not enough, we need to label those links to create a knowledge graph.&lt;/li>
&lt;li>Graph databases (like &lt;a href="https://github.com/neo4j/neo4j">Neo4j&lt;/a>) does this!
&lt;ul>
&lt;li>No FKs, very flexible, label relationships&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="lessons-learned-maintaining-open-source-python-projects">Lessons Learned Maintaining Open-Source Python Projects&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>What is a maintainer?&lt;/strong> They keep projects operational and in an appropriate condition.&lt;/li>
&lt;li>&lt;strong>What is not a maintainer?&lt;/strong> Someone who adds new features or fixes issues&amp;hellip;that would be a contributor.&lt;/li>
&lt;li>&lt;strong>What does a maintainer do?&lt;/strong>
&lt;ul>
&lt;li>Ensures package and information about it is discoverable.&lt;/li>
&lt;li>Ensures consumption and contribution is familiar.&lt;/li>
&lt;li>Sets ground rules (hint: CODE_OF_CONDUCT.md).&lt;/li>
&lt;li>Triages incoming bug reports (hint: use templates).&lt;/li>
&lt;li>Grooms and rejects incoming feature requests (hint: think of long-term good rather than short term gain).&lt;/li>
&lt;li>Reviews pull request (hint: require tests, changelogs, ruff code reformatting, and commit squashing when needed).&lt;/li>
&lt;li>Cuts releases with a changelog (hint: think small and often. have version and deprecation policies).&lt;/li>
&lt;li>Thanks contributors.&lt;/li>
&lt;li>Ensures the projects architecture remains consistent.&lt;/li>
&lt;li>Lets other people help (hint: CONTRIBUTING.md).&lt;/li>
&lt;li>Ensure CI remains green and fast (hint: don&amp;rsquo;t forget to rebuild docs, add pull request warning).&lt;/li>
&lt;li>Fosters community (hint: issue tracker, slack/discord, social, stack overflow).&lt;/li>
&lt;li>Mentors and convert good contributors to co-maintainers.&lt;/li>
&lt;li>Ensures they can keep doing it (hint: priorities mental and physical health, it&amp;rsquo;s okay to step away)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Why be a maintainer?&lt;/strong>
&lt;ul>
&lt;li>Looks good on your CV.&lt;/li>
&lt;li>Gives opportunities to meet people.&lt;/li>
&lt;li>Mentoring experience.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>How to be a maintainer?&lt;/strong>
&lt;ul>
&lt;li>Pick something you use actively use on a semi-regular basis.&lt;/li>
&lt;li>Read the docs in detail.&lt;/li>
&lt;li>Practice by trying to solve an issue and reach out to the current maintainer.&lt;/li>
&lt;li>Know communication is deceptively hard, we all have different communication styles.
&lt;ul>
&lt;li>Be polite and patient, and give people the benefit of doubt.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="working-with-audio-in-python">Working with Audio in Python&lt;/h3>
&lt;ul>
&lt;li>Very cool lesson on how digital audio works.&lt;/li>
&lt;li>&lt;a href="https://github.com/spotify/pedalboard">Pedalboard&lt;/a> provides useful abstractions around audio files.&lt;/li>
&lt;li>Always process audio files as a stream to handle any size of input.&lt;/li>
&lt;li>Use &lt;a href="https://github.com/numpy/numpy">numpy&lt;/a> for faster operations.&lt;/li>
&lt;/ul>
&lt;h3 id="rest-easy-with-jupyrest">Rest Easy with Jupyrest&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://github.com/microsoft/jupyrest">Jupyrest&lt;/a> turns jupyter notebooks into json APIs.&lt;/li>
&lt;/ul>
&lt;h3 id="iterate-iterate-iterate">Iterate, Iterate, Iterate!&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://github.com/more-itertools/more-itertools">more-itertools&lt;/a> 😍&lt;/li>
&lt;/ul>
&lt;h3 id="voice-computing-with-python-in-jupyter-notebooks">Voice Computing with Python in Jupyter Notebooks&lt;/h3>
&lt;ul>
&lt;li>You can write faster (and avoid RSI) by using your voice instead of typing.&lt;/li>
&lt;li>You can be even faster by defining text replacement commands (e.g. &amp;ldquo;insert code block&amp;rdquo;).&lt;/li>
&lt;/ul>
&lt;h3 id="lightning-talks">Lightning Talks&lt;/h3>
&lt;ul>
&lt;li>You can make LLMs smarter by feeding it new data in the system prompt.&lt;/li>
&lt;li>&lt;a href="https://www.recurse.com/social-rules">Recurse Center: Social Rules&lt;/a>
&lt;ul>
&lt;li>No well-actually’s&lt;/li>
&lt;li>No feigned surprise&lt;/li>
&lt;li>No backseat driving&lt;/li>
&lt;li>No subtle -isms&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>How to give a talk:
&lt;ol>
&lt;li>Be Engaging&lt;/li>
&lt;li>Be Loud&lt;/li>
&lt;li>Be Big (60pt font)&lt;/li>
&lt;li>Be Prepared&lt;/li>
&lt;li>Be Considerate&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Tips for giving a good talk:
&lt;ul>
&lt;li>Tell a story&lt;/li>
&lt;li>Find your signposts (break points)&lt;/li>
&lt;li>Practice&lt;/li>
&lt;li>The energy you give is energy you get&lt;/li>
&lt;li>expect (and prepare for) the unexpected&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="day-2">Day 2&lt;/h2>
&lt;h3 id="keynote-thriving-with-python">Keynote: Thriving with Python&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>How do we succeed in a polyglot world?&lt;/strong> Choose the best language for the job.&lt;/li>
&lt;li>&lt;strong>How do we avoid the pitfalls?&lt;/strong>
&lt;ul>
&lt;li>Avoid chasing speed. All languages have tradeoffs.&lt;/li>
&lt;li>Avoid swooning at shiny. All languages will eventually bloat.&lt;/li>
&lt;li>Avoid hype and FOMO. Pretend it&amp;rsquo;s the news.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="20-goto-10-how-to-make-scrolling-ascii-art">20 GOTO 10: How to Make Scrolling ASCII Art&lt;/h3>
&lt;blockquote>
&lt;p>There will always be new forms of art.&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>What is &lt;a href="https://scrollart.org/">Scroll Art&lt;/a>?
&lt;ul>
&lt;li>ASCII art&lt;/li>
&lt;li>Terminal/console output of monospace text&lt;/li>
&lt;li>Previously printed text scrolls up (and only up)&lt;/li>
&lt;li>You can&amp;rsquo;t erase the text you&amp;rsquo;ve printed&lt;/li>
&lt;li>You can&amp;rsquo;t move the cursor to arbitrary XY coordinates&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Why?
&lt;ul>
&lt;li>Easy to understand. Input is text, output is text.&lt;/li>
&lt;li>Any computer can run them.&lt;/li>
&lt;li>Easily accessible. Can us any language.&lt;/li>
&lt;li>Easy way to start learning/teaching code.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="system-design-on-easy-mode">System Design on Easy Mode&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Durable execution&lt;/strong> is a higher level of abstraction for writing functions that are guaranteed to complete running.&lt;/li>
&lt;li>How? &lt;a href="https://temporal.io/">https://temporal.io/&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="building-efficient-containers-for-python-applications">Building Efficient Containers for Python Applications&lt;/h3>
&lt;ul>
&lt;li>Docker + Python is initially straightforward, but tricky to optimize.&lt;/li>
&lt;li>Why should I care?
&lt;ul>
&lt;li>Slower build times lead to decreased productivity and ability to fail fast.&lt;/li>
&lt;li>Larger image sizes lead to more storage requirements and longer download times.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>How do we measure efficiency?
&lt;ul>
&lt;li>Image size&lt;/li>
&lt;li>Build time (no cache)&lt;/li>
&lt;li>Rebuild (no change)&lt;/li>
&lt;li>Rebuild time (with code change)&lt;/li>
&lt;li>Rebuild time (with dependency change)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Possible Optimizations:
&lt;ul>
&lt;li>Layer Ordering: Order matters. Ex. install deps before copying code.&lt;/li>
&lt;li>Pin deps &amp;amp; Disable pip cache: Deps have deps, pin those too. You don&amp;rsquo;t need pip to cache inside an image.&lt;/li>
&lt;li>Smaller Base Image: Removes build tools saving storage. Install only needed tools, install, and then remove tools.&lt;/li>
&lt;li>Combining Layers: Chaining commands on same &lt;code>RUN&lt;/code> can save on storage.&lt;/li>
&lt;li>Multi-stage Build: Using &lt;code>builder&lt;/code> stage to create venv to use in &lt;code>runner&lt;/code> stage saves space from discarded &lt;code>builder&lt;/code> stage.&lt;/li>
&lt;li>Cache mount: Saving pip cache on your machine saves redownloads.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Other best practices:
&lt;ul>
&lt;li>Always use a Python-specific .dockerignore file&lt;/li>
&lt;li>Separate Dev and Prod dependencies&lt;/li>
&lt;li>Use the latest Debian/Ubuntu/Redhat distribution to base from&lt;/li>
&lt;li>Try to avoid specifying the Python patch version in the base image&lt;/li>
&lt;li>Use CPU specific vs GPU specific image&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="always-use-sets">Always Use Sets&lt;/h3>
&lt;ul>
&lt;li>Always use sets because they&amp;hellip;
&lt;ul>
&lt;li>clearly communicate expectations&lt;/li>
&lt;li>prevents unexpected drift in behavior&lt;/li>
&lt;li>allow us to write algorithms based on discrete math&lt;/li>
&lt;li>add clarity to the code through set operations&lt;/li>
&lt;li>are faster than lists or tuples - even when 10 items are changed&lt;/li>
&lt;li>worthwhile even if you have to sort or combine later&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Except&amp;hellip;
&lt;ul>
&lt;li>when you are running out of memory&lt;/li>
&lt;li>when you have a dict that is equally idiomatic&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="oh-the-methods-you-can-make-by-dunder-seuss">Oh the (Methods) You Can (Make): By Dunder Seuss&lt;/h3>
&lt;ul>
&lt;li>Very charming story time summary of the many&amp;hellip;many dunder methods! ❤️&lt;/li>
&lt;/ul>
&lt;h3 id="sanely-working-with-legacy-code">Sanely Working With Legacy Code&lt;/h3>
&lt;ul>
&lt;li>What is legacy code?
&lt;ul>
&lt;li>Code without documentation.&lt;/li>
&lt;li>Code without tests.&lt;/li>
&lt;li>Code with poorly written test that is hard to change.&lt;/li>
&lt;li>Code which you are not familiar with and often not your idea.&lt;/li>
&lt;li>Valuable code which you are not comfortable changing.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>The Plan
&lt;ul>
&lt;li>Begin with understanding the code we want to improve&lt;/li>
&lt;li>Check that our understanding of the code matches what is expected&lt;/li>
&lt;li>Add tests&lt;/li>
&lt;li>Make changes&lt;/li>
&lt;li>Communicate our changes to the code effective to others on our team&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="lightning-talks-1">Lightning Talks&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://flet.dev/">flet.dev&lt;/a>, multi-platform apps&lt;/li>
&lt;li>Use &lt;code>watchfiles&lt;/code> to auto run dev commands on save (e.g. linting, testing, etc.)&lt;/li>
&lt;li>Game Jam == Hackathon for games&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20PyTexas%202024">Reply via email</a></description></item><item><title>Trying out the Vision Pro</title><link>https://www.djpeacher.com/posts/trying-out-the-vision-pro/</link><pubDate>Tue, 06 Feb 2024 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/trying-out-the-vision-pro/</guid><description>
&lt;p>Being the Apple nerd that I am, when I found out there was going to be demos of the Vision Pro, I had to sign up! It’s not everyday you get to try out a new Apple product.&lt;/p>
&lt;p>Here are some of my first impressions:&lt;/p>
&lt;p>&lt;em>Display&lt;/em>. I was a little disappointed with the display. Don’t get me wrong, it’s a really good display, but I was really hoping Apple would have come out swinging with something orders of magnitude better than the competition. I found the display too dim and pixelated, slow at times, and contained too much &lt;a href="https://en.wikipedia.org/wiki/Vignetting">vignetting&lt;/a> around the edges. I think the only thing that saved the display for me were the prescription eye inserts.&lt;/p>
&lt;p>&lt;em>Controls&lt;/em>. The eye-hand controls are by far the defining feature of the Vision Pro. It was incredibly intuitive and eerily accurate. It honestly felt like it was reading my mind, and I quickly started zooming around the UI. I did get a few false positives here and there, but I think I’ll blame my fidgety hands for that. One thing I didn’t like too much however, was that it really made me think about what my eyes were doing, and I could already feel the strain in the short time that I used it. Only thing I forgot to try was the virtual keyboard&amp;hellip;eh, it was probably bad anyway.&lt;/p>
&lt;p>&lt;em>3D Content&lt;/em>. I was shown some of the 3D photos and videos you can create, and too be honest they just felt like blurry &lt;a href="https://en.wikipedia.org/wiki/Lenticular_printing">lenticular cards&lt;/a> and were not all that interesting. 3D movies were fun to see again, and the 3D environments you can place yourself in were a bit more intriguing, but it was Apple’s “Immersive Videos” where things really started to shine. They were absolutely amazing, and it nearly brought a tear to my eye because I really felt like I was there&amp;hellip;I left wanting more.&lt;/p>
&lt;p>&lt;em>Comfort&lt;/em>. Generally speaking, it was comfortable, but wow was it heavy. I felt the pressure mostly along my nose and cheek bones, and I could feel it well after the session had ended, not to mention the prominent red mark around my face. I know Apple really wants everyone to use that &amp;ldquo;solo knit band&amp;rdquo;, but I would probably never use it in favor of the &amp;ldquo;dual loop band&amp;rdquo;.&lt;/p>
&lt;p>So, am I going to buy the Vision Pro? No! It is way too expensive, not to mention the accessories, but if I had a few thousand dollars lying around, sure I&amp;rsquo;d buy one. I do think it&amp;rsquo;s an amazing product, and the start of an exciting journey, but I could never justify it in its current state. Besides, we all know what the end game is here 🤓&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Trying%20out%20the%20Vision%20Pro">Reply via email</a></description></item><item><title>Get a virtual library card</title><link>https://www.djpeacher.com/posts/get-a-virtual-library-card/</link><pubDate>Fri, 02 Feb 2024 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/get-a-virtual-library-card/</guid><description>
&lt;p>At my sister&amp;rsquo;s insistence, I finally got my first &lt;strong>virtual&lt;/strong> library card as an adult, and I can honestly say I will probably never buy a book, ebook, or audiobook again! Unless, of course, I want to display a book on my shelf&amp;hellip;&lt;/p>
&lt;p>I was originally concerned the virtual experience and catalog would not be satisfactory, but with apps like &lt;a href="https://www.overdrive.com/apps/libby">Libby&lt;/a> that is not an issue. Nearly everything I wanted was available as an ebook or audiobook, and the best part, you can send the ebooks to your Kindle!&lt;/p>
&lt;p>I&amp;rsquo;ve had it for about a month or two now, and in that time I&amp;rsquo;ve gotten through around 10 books, &lt;strong>way more&lt;/strong> than I normally would. I think this is for two reasons:&lt;/p>
&lt;ol>
&lt;li>&lt;em>Cost&lt;/em>&lt;/li>
&lt;li>&lt;em>Motivation&lt;/em>&lt;/li>
&lt;/ol>
&lt;p>&lt;em>Cost&lt;/em>. Books can be expensive, especially if I wanted to do the book-audiobook combo, which caused me to pass up books I wasn&amp;rsquo;t absolutely sure I would read. With a library card, they are effectively &lt;strong>free&lt;/strong>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>, so borrow away!&lt;/p>
&lt;p>&lt;em>Motivation&lt;/em>. When I bought a book, I had effectively forever to read it, which translates to&amp;hellip;I&amp;rsquo;m never going to read it. With a library card, I had a return date. This &lt;strong>deadline&lt;/strong> forced me to actually read the book before it goes away.&lt;/p>
&lt;p>Overall, I think getting a virtual library card has been a huge success. If I had to give one con, it&amp;rsquo;s that if you, like me, enjoy reading and listening to books at the same time, it can be hard sometimes to borrow the ebook and audiobook at the same time if there are different wait times. Luckily, you can defer items to get them to line up better.&lt;/p>
&lt;p>Happy borrowing!&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Or at least close to free depending on your area.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div><a href="mailto: reply@djpeacher.com?subject=Re:%20Get%20a%20virtual%20library%20card">Reply via email</a></description></item><item><title>Store tupperware with their lids on</title><link>https://www.djpeacher.com/posts/store-tupperware-with-their-lids-on/</link><pubDate>Wed, 24 Jan 2024 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/store-tupperware-with-their-lids-on/</guid><description>
&lt;p>Decluttering at the Speed of Life by Dana K. White&lt;/p>
&lt;blockquote>
&lt;p>Store food storage containers with their lids on&amp;hellip;I put lids on containers as I take them out of the dish-washer (or off the dish drainer when I hand wash) and stick them in the cabinet. They don’t nest, so no finagling is required. When I need a container, I grab it. All of it. No searching for a matching lid.&lt;/p>&lt;/blockquote>
&lt;p>Finally got around to listening to this book that was recommended by &lt;a href="https://mastodon.social/@webology/110437362488781280">Jeff Triplett on Mastodon&lt;/a>. This tupperware quote is my favorite, and most impactful! My wife and I have only been doing this for about a week and it&amp;rsquo;s already paying dividends.&lt;/p>
&lt;p>This is the way.&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Store%20tupperware%20with%20their%20lids%20on">Reply via email</a></description></item><item><title>Alamo Drafthouse 2023</title><link>https://www.djpeacher.com/posts/alamo-drafthouse-2023/</link><pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/alamo-drafthouse-2023/</guid><description>
&lt;p>55 movies this year&amp;hellip;nearly double &lt;a href="https://www.djpeacher.com/posts//alamo-drafthouse-2022/">last year&lt;/a>!&lt;/p>
&lt;ul>
&lt;li>M3GAN&lt;/li>
&lt;li>Mulholland Dr. (2001)&lt;/li>
&lt;li>Skinamarink 🫣&lt;/li>
&lt;li>Broker&lt;/li>
&lt;li>Infinity Pool Uncut&lt;/li>
&lt;li>Everything Everywhere All at Once (2022)&lt;/li>
&lt;li>Knock at the Cabin&lt;/li>
&lt;li>Ant-Man and the Wasp: Quantumania&lt;/li>
&lt;li>Cocaine Bear&lt;/li>
&lt;li>Scream VI&lt;/li>
&lt;li>Dungeon &amp;amp; Dragons: Honor Among Thieves 🎲&lt;/li>
&lt;li>The Super Mario Bros. Movie&lt;/li>
&lt;li>Beau is Afraid&lt;/li>
&lt;li>Suzume (Subtitled)&lt;/li>
&lt;li>Renfield&lt;/li>
&lt;li>Evil Dead Rise&lt;/li>
&lt;li>Guardians of the Galaxy Vol. 3&lt;/li>
&lt;li>Mother! (2017)&lt;/li>
&lt;li>The Boogeyman&lt;/li>
&lt;li>Transformers: Rise of the Beasts&lt;/li>
&lt;li>Past Lives&lt;/li>
&lt;li>The Flash&lt;/li>
&lt;li>Spider-Man: Across the Spider-Verse&lt;/li>
&lt;li>Asteroid City&lt;/li>
&lt;li>Flux Gourmet (2022)&lt;/li>
&lt;li>Joy Ride&lt;/li>
&lt;li>No Hard Feelings&lt;/li>
&lt;li>Insidious: The Red Door&lt;/li>
&lt;li>Oppenheimer&lt;/li>
&lt;li>Talk To Me&lt;/li>
&lt;li>Barbie&lt;/li>
&lt;li>Haunted Mansion (2023)&lt;/li>
&lt;li>Teenage Mutant Ninja Turtles: Mutant Mayhem&lt;/li>
&lt;li>Strays&lt;/li>
&lt;li>Oldboy (2003)&lt;/li>
&lt;li>Bottoms&lt;/li>
&lt;li>A Haunting in Venice&lt;/li>
&lt;li>The Nun II&lt;/li>
&lt;li>Eyes Wide Shut (1999)&lt;/li>
&lt;li>Saw X&lt;/li>
&lt;li>Strange Way of Life (2023) + The Human Voice (2020)&lt;/li>
&lt;li>The Creator&lt;/li>
&lt;li>The Exorcist: Believer&lt;/li>
&lt;li>When Evil Lurks&lt;/li>
&lt;li>Cat Person&lt;/li>
&lt;li>The Exorcist (1973)&lt;/li>
&lt;li>Killers of the Flower Moon&lt;/li>
&lt;li>Priscilla&lt;/li>
&lt;li>The Hunger Games: The Ballad of Songbirds &amp;amp; Snakes&lt;/li>
&lt;li>Saltburn&lt;/li>
&lt;li>Elf (2003)&lt;/li>
&lt;li>Napoleon (70mm)&lt;/li>
&lt;li>Poor Things&lt;/li>
&lt;li>Wonka&lt;/li>
&lt;li>The Boy and the Heron (Dubbed)&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20Alamo%20Drafthouse%202023">Reply via email</a></description></item><item><title>Generating social share images with Hugo</title><link>https://www.djpeacher.com/posts/generating-social-share-images-with-hugo/</link><pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/generating-social-share-images-with-hugo/</guid><description>
&lt;p>Aaro Luomanen, &lt;a href="https://aarol.dev/posts/hugo-og-image/">Generating OpenGraph images with Hugo&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>Have you ever shared a link somewhere and wondered where those little preview images of websites come from? Well if you didn’t know, they are called OpenGraph tags&amp;hellip;I managed to generate some sweet og:images using Hugo’s image manipulation tools.&lt;/p>&lt;/blockquote>
&lt;p>I&amp;rsquo;ve been wanting to setup social share images for a while, but I figured it would be too complicated or time consuming to manage, so I never got around to it. That is, until I found Aaro&amp;rsquo;s blog post! Turns out it is super simple to have Hugo (what this site is built on) generate OpenGraph images for you!&lt;/p>
&lt;p>Here is what you&amp;rsquo;d see if you shared this post on social media. It&amp;rsquo;s simple, but I like it. Thinking I might add tags to this in the future.&lt;/p>
&lt;p>&lt;img src="https://www.djpeacher.com/posts/generating-social-share-images-with-hugo/og.png" alt="Simple social share image with a white background and black text. From top to bottom it shows, my name, the publish date, and the post title.">&lt;/p>
&lt;p>Thanks Aaro!&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Generating%20social%20share%20images%20with%20Hugo">Reply via email</a></description></item><item><title>YouTube RSS</title><link>https://www.djpeacher.com/posts/youtube-rss/</link><pubDate>Mon, 18 Dec 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/youtube-rss/</guid><description>
&lt;p>About &lt;a href="https://fosstodon.org/@djpeacher/110471585752450798">6 month ago&lt;/a> I discovered that every channel and playlist on YouTube has an RSS feed, and since then I&amp;rsquo;ve been slowly &amp;ldquo;re-subscribing&amp;rdquo; to all my favorite channels. It really has been an amazing way to experience YouTube without all the algorithmic noise.&lt;/p>
&lt;p>Just add the following feeds with your desired channel or playlist to your favorite RSS reader!
You should be able to find channel IDs in its about section.&lt;/p>
&lt;pre tabindex="0">&lt;code>www.youtube.com/feeds/videos.xml?channel_id=&amp;lt;ID&amp;gt;
www.youtube.com/feeds/videos.xml?playlist_id=&amp;lt;ID&amp;gt;
&lt;/code>&lt;/pre><a href="mailto: reply@djpeacher.com?subject=Re:%20YouTube%20RSS">Reply via email</a></description></item><item><title>How does this still work</title><link>https://www.djpeacher.com/posts/how-does-this-still-work/</link><pubDate>Mon, 27 Nov 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/how-does-this-still-work/</guid><description>
&lt;p>Currently listening to &lt;strong>Life in Code by Ellen Ullman&lt;/strong>, and I&amp;rsquo;m reminded how brittle our digital world is and wonder how it hasn&amp;rsquo;t fallen apart yet 🙃&lt;/p>
&lt;blockquote>
&lt;p>“We build our computers the way we build our cities—over time, without a plan, on top of ruins.”&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>“Code and forget, code and forget: programming as a collective exercise in incremental forgetting.”&lt;/p>&lt;/blockquote><a href="mailto: reply@djpeacher.com?subject=Re:%20How%20does%20this%20still%20work">Reply via email</a></description></item><item><title>DjangoCon US 2023</title><link>https://www.djpeacher.com/posts/djangocon-us-2023/</link><pubDate>Thu, 26 Oct 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/djangocon-us-2023/</guid><description>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#day-1">Day 1&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#orientation-and-welcome">Orientation and Welcome&lt;/a>&lt;/li>
&lt;li>&lt;a href="#contributing-to-django-or-how-i-learned-to-stop-worrying-and-just-try-to-fix-an-orm-bug">Contributing to Django or how I learned to stop worrying and just try to fix an ORM Bug&lt;/a>&lt;/li>
&lt;li>&lt;a href="#html-ivating-your-django-web-apps-experience-with-htmx-alpinejs-and-streaming-html">HTML-ivating your Django web app&amp;rsquo;s experience with HTMX, AlpineJS, and streaming HTML&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lightning-talks---day-1">Lightning Talks - Day 1&lt;/a>&lt;/li>
&lt;li>&lt;a href="#using-database-triggers-to-reliably-track-model-history">Using database triggers to reliably track model history&lt;/a>&lt;/li>
&lt;li>&lt;a href="#beyond-the-basics-of-migrations">Beyond the Basics of Migrations&lt;/a>&lt;/li>
&lt;li>&lt;a href="#custom-model-managers-and-querysets-graduating-from-django-beginner-to-orm-master">Custom Model Managers and QuerySets: Graduating from Django Beginner to ORM Master&lt;/a>&lt;/li>
&lt;li>&lt;a href="#passkeys-on-django">Passkeys on Django&lt;/a>&lt;/li>
&lt;li>&lt;a href="#building-powerful-apis-with-django-django-rest-framework-and-openapi">Building Powerful APIs with Django, Django Rest Framework, and OpenAPI&lt;/a>&lt;/li>
&lt;li>&lt;a href="#hosting-and-devops-for-django">Hosting and DevOps for Django&lt;/a>&lt;/li>
&lt;li>&lt;a href="#-modern-editing-experience-for-your-django-models-with-wagtail-">✨ Modern editing experience for your Django models with Wagtail 🐦&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-2">Day 2&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#postgres-performance-from-slow-to-pro">Postgres Performance: From Slow to Pro&lt;/a>&lt;/li>
&lt;li>&lt;a href="#empathetic-testing-developing-with-compassion-and-humility">Empathetic testing: Developing with compassion and humility&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lightning-talks---day-2">Lightning Talks - Day 2&lt;/a>&lt;/li>
&lt;li>&lt;a href="#dont-buy-the-ai-hype">Don&amp;rsquo;t Buy the &amp;ldquo;A.I.&amp;rdquo; Hype&lt;/a>&lt;/li>
&lt;li>&lt;a href="#theres-more-to-open-source-than-code">There&amp;rsquo;s More to Open Source than Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="#managing-content-with-django">Managing Content with Django&lt;/a>&lt;/li>
&lt;li>&lt;a href="#an-approach-to-lightweight-tenancy-management-using-django-rest-framework">An approach to lightweight tenancy management using Django Rest Framework&lt;/a>&lt;/li>
&lt;li>&lt;a href="#what-django-deployment-is-really-about">What Django Deployment is Really About&lt;/a>&lt;/li>
&lt;li>&lt;a href="#introduction-to-github-actions-understanding-key-terms-and-building-your-first-github-action">Introduction to GitHub Actions: Understanding Key Terms and Building Your First GitHub Action&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-3">Day 3&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keynote-testing-modern-web-apps-like-a-champion">Keynote: Testing Modern Web Apps Like a Champion&lt;/a>&lt;/li>
&lt;li>&lt;a href="#django-migrations-friend-or-foe-optimize-your-django-migrations-for-faster-testing">Django migrations, friend or foe? Optimize your Django migrations for faster testing&lt;/a>&lt;/li>
&lt;li>&lt;a href="#inside-out-my-journey-of-understanding-inclusion">Inside Out: My Journey of Understanding Inclusion&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lightning-talks---day-3">Lightning Talks - Day 3&lt;/a>&lt;/li>
&lt;li>&lt;a href="#best-practices-for-making-a-wagtail-site-as-accessible-as-possible">Best Practices for Making a Wagtail Site as Accessible as Possible&lt;/a>&lt;/li>
&lt;li>&lt;a href="#one-database-table-one-model-many-behaviours-proxy-model">One database table, one model, many behaviours: Proxy model&lt;/a>&lt;/li>
&lt;li>&lt;a href="#back-to-the-future-of-hypermedia-in-django">Back to the Future of Hypermedia in Django&lt;/a>&lt;/li>
&lt;li>&lt;a href="#djangos-accessibility-track-record">Django’s accessibility track record&lt;/a>&lt;/li>
&lt;li>&lt;a href="#panel-discussion-who-put-me-in-charge-moving-beyond-day-to-day-coding-in-django">Panel Discussion: Who put me in charge? Moving beyond day-to-day coding in Django&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;h2 id="day-1">Day 1&lt;/h2>
&lt;h3 id="orientation-and-welcome">Orientation and Welcome&lt;/h3>
&lt;blockquote>
&lt;p>&amp;ldquo;I am going to say some words here I don&amp;rsquo;t fully understand&amp;rdquo; &amp;ndash; &lt;a href="https://hachyderm.io/@lacey">@lacey&lt;/a>&lt;/p>&lt;/blockquote>
&lt;h3 id="contributing-to-django-or-how-i-learned-to-stop-worrying-and-just-try-to-fix-an-orm-bug">Contributing to Django or how I learned to stop worrying and just try to fix an ORM Bug&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=1BFjg9XtptM&amp;amp;ab_channel=DjangoConUS">Your Web Framework Needs You! &amp;ndash; Carlton Gibson&lt;/a>&lt;/li>
&lt;li>Ticket Strategy: Something old and straightforward&lt;/li>
&lt;li>Steps:
&lt;ol>
&lt;li>Replicate the bug&lt;/li>
&lt;li>Read some docs&lt;/li>
&lt;li>Write some code&lt;/li>
&lt;li>Test the code&lt;/li>
&lt;li>Write down (&lt;a href="https://www.youtube.com/watch?v=GLkRK2rJGB0&amp;amp;ab_channel=DjangoConUS">publicly&lt;/a>) what you learned&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h3 id="html-ivating-your-django-web-apps-experience-with-htmx-alpinejs-and-streaming-html">HTML-ivating your Django web app&amp;rsquo;s experience with HTMX, AlpineJS, and streaming HTML&lt;/h3>
&lt;p>SPA&amp;rsquo;s are popular because they can provide dynamic user experiences with no reloading. This comes at the cost of larger file sizes making pages take longer to load, and higher complexity making the codebase harder to maintain.&lt;/p>
&lt;p>We can achieve the same dynamic experiences with tools like HTMX and AlpineJS, while also reducing load times and complexity.&lt;/p>
&lt;p>✅ These tools provide the features an elevated user experiences (normally via SPAs):&lt;/p>
&lt;ul>
&lt;li>(HTMX) Remove whole-page refreshes for every interaction&lt;/li>
&lt;li>(HTMX) Use small payloads from the server to update the interface&lt;/li>
&lt;li>(AlpineJS) Update HTML as a result of changes in data&lt;/li>
&lt;li>(AlpineJS) Empower rich on-page interactions&lt;/li>
&lt;li>(HTMX/AlpineJS) Be fast&lt;/li>
&lt;/ul>
&lt;p>✅ These tools provide faster pages loads by literally being small:&lt;/p>
&lt;ul>
&lt;li>Around 30KB&lt;/li>
&lt;/ul>
&lt;p>✅ These tools provide easy to maintain HTML attributes control behavior:&lt;/p>
&lt;ul>
&lt;li>Locality&lt;/li>
&lt;li>Remove your JS build system&lt;/li>
&lt;li>Write mostly Python and HTML&lt;/li>
&lt;li>Easier maintenance&lt;/li>
&lt;li>Faster iteration cycles&lt;/li>
&lt;/ul>
&lt;h3 id="lightning-talks---day-1">Lightning Talks - Day 1&lt;/h3>
&lt;ul>
&lt;li>April 8, 2024 Total Solar Eclipse
&lt;ul>
&lt;li>Last in the continental US until 2044/2045&lt;/li>
&lt;li>By glasses/filters early (ISO 12312-2 certified)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://alldjango.com/stickers">Django Stickers&lt;/a>! 🦄&lt;/li>
&lt;/ul>
&lt;h3 id="using-database-triggers-to-reliably-track-model-history">Using database triggers to reliably track model history&lt;/h3>
&lt;blockquote>
&lt;p>&lt;a href="https://github.com/Opus10/django-pghistory">django-pghistory&lt;/a> tracks changes to your Django models using Postgres triggers, providing:&lt;/p>
&lt;ul>
&lt;li>Reliable history tracking everywhere with no changes to your application code.&lt;/li>
&lt;li>Structured history models that mirror the fields of your models.&lt;/li>
&lt;li>Grouping of history with additional context attached, such as the logged-in user.&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;pre tabindex="0">&lt;code>import pghistory
@pghistory.track()
class TrackedModel(models.Model):
int_field = models.IntegerField()
text_field = models.TextField()
&lt;/code>&lt;/pre>&lt;h3 id="beyond-the-basics-of-migrations">Beyond the Basics of Migrations&lt;/h3>
&lt;ul>
&lt;li>DON&amp;rsquo;T reinvent the wheel: if Django&amp;rsquo;s autogenerated migrations work, use that&lt;/li>
&lt;li>DO rename custom migration files for clarity&lt;/li>
&lt;li>DO always use the reverse parameter when using RunPython or RunSQL&lt;/li>
&lt;li>DO make sure your coworkers know if you customized a migration so they can review it properly&lt;/li>
&lt;li>DON&amp;rsquo;T use migrations for repeat actions&lt;/li>
&lt;li>Efficiency matters on large tables (especially with CI/CD)&lt;/li>
&lt;/ul>
&lt;h3 id="custom-model-managers-and-querysets-graduating-from-django-beginner-to-orm-master">Custom Model Managers and QuerySets: Graduating from Django Beginner to ORM Master&lt;/h3>
&lt;ul>
&lt;li>What are Managers? An interface through which database query operations are provided to Django models.
&lt;ul>
&lt;li>Model methods = row level operations&lt;/li>
&lt;li>Manager methods = table level operations&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>How do you define Managers?
&lt;pre tabindex="0">&lt;code>objects = CustomManager() # basic usage
objects = CustomQuerySet.as_manager() # bread and butter usage
objects = Custommanager.from_queryset(CustomerQuerySet()) # advanced usage
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>Manager or QuerySet?
&lt;ul>
&lt;li>QuerySets = Chainable, Filters, Annotations/Aggregations, Reading&lt;/li>
&lt;li>Manager = Not Chainable, Creating, Updating, Deleting&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Why use custom Managers?
&lt;ul>
&lt;li>Readability&lt;/li>
&lt;li>Encapsulation&lt;/li>
&lt;li>Testing!!!&lt;/li>
&lt;li>Built-in service layer&lt;/li>
&lt;li>Gateway to learning the ORM&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Common Patterns
&lt;ul>
&lt;li>&lt;code>.for_CONTEXT(ctx)&lt;/code>
&lt;ul>
&lt;li>Manager or QuerySet&lt;/li>
&lt;li>Filter based on some context&lt;/li>
&lt;li>Ex: &lt;code>.for_user(user)&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>.is_CONDITION(cond?) .not_CONDITION(cond?) .exclude_CONDITION(cond?)&lt;/code>
&lt;ul>
&lt;li>Manager or QuerySet&lt;/li>
&lt;li>Filter based on some condition&lt;/li>
&lt;li>Ex: &lt;code>.is_leasable() .is_leased_for_year(year)&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>.create_MODEL() .create_MODEL_for_CONTEXT(ctx)&lt;/code>
&lt;ul>
&lt;li>Manager&lt;/li>
&lt;li>Create a model with side effects&lt;/li>
&lt;li>Ex: &lt;code>.create_user_for_club(club)&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>.within_RANGE()&lt;/code>
&lt;ul>
&lt;li>Manager or QuerySet&lt;/li>
&lt;li>Filter based on numerical/date range&lt;/li>
&lt;li>Ex: &lt;code>.within_start_date(days)&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>.greater_than_VALUE() .less_than_VALUE()&lt;/code>
&lt;ul>
&lt;li>Manager or QuerySet&lt;/li>
&lt;li>Filter based on numerical/date value&lt;/li>
&lt;li>Ex: &lt;code>greater_than_days(days)&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>.set_FIELD() .toggle_FIELD()&lt;/code>
&lt;ul>
&lt;li>Manager or QuerySet&lt;/li>
&lt;li>Update a field across entire QuerySet&lt;/li>
&lt;li>Ex: &lt;code>.set_active() .toggle_status()&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>.with_ANNOTATION()&lt;/code>
&lt;ul>
&lt;li>Manager or QuerySet&lt;/li>
&lt;li>Add additional info not available on Model&lt;/li>
&lt;li>Ex: &lt;code>.with_adjusted_income()&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="passkeys-on-django">Passkeys on Django&lt;/h3>
&lt;p>See &lt;a href="https://github.com/mkalioby/django-passkeys">django-passkeys&lt;/a>.&lt;/p>
&lt;h3 id="building-powerful-apis-with-django-django-rest-framework-and-openapi">Building Powerful APIs with Django, Django Rest Framework, and OpenAPI&lt;/h3>
&lt;ul>
&lt;li>Status codes
&lt;ul>
&lt;li>1xx: Informational &amp;mdash;&amp;gt; in progress&lt;/li>
&lt;li>2xx: Successful &amp;mdash;&amp;gt; done&lt;/li>
&lt;li>3xx: Redirection &amp;mdash;&amp;gt; naahh, ain&amp;rsquo;t nobody got time for that.&lt;/li>
&lt;li>4xx: Client error &amp;mdash;&amp;gt; its definitely you, not me&lt;/li>
&lt;li>5xx: Server error &amp;mdash;&amp;gt; okay, its me not you&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>API first approach
&lt;ul>
&lt;li>Separation of concerns&lt;/li>
&lt;li>Flexibility and scalability&lt;/li>
&lt;li>Collaboration and parallel development&lt;/li>
&lt;li>Clear and consistent interfaces&lt;/li>
&lt;li>Documentation and contract-driven development&lt;/li>
&lt;li>Future-proofing&lt;/li>
&lt;li>Integration and Third-party support&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Cache all the things!&lt;/li>
&lt;/ul>
&lt;h3 id="hosting-and-devops-for-django">Hosting and DevOps for Django&lt;/h3>
&lt;ul>
&lt;li>Goals
&lt;ol>
&lt;li>(Hosting) Setup is resilient to individual server failures&lt;/li>
&lt;li>(Hosting) Resources can be scaled in response to load&lt;/li>
&lt;li>(Deployment) Code can be tested before being deployed to production&lt;/li>
&lt;li>(Deployment) Deployments are automated&lt;/li>
&lt;li>(Data/Secrets) Data and secrets are secured and backed-up&lt;/li>
&lt;li>(Monitoring) Errors are easy to notice and diagnose&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Hosting
&lt;ul>
&lt;li>What are the options?
&lt;ul>
&lt;li>&lt;del>Bare servers (EC2)&lt;/del>&lt;/li>
&lt;li>Platform as a Service (EB)&lt;/li>
&lt;li>Managed Container Service (ECS)&lt;/li>
&lt;li>Kubernetes (EKS)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Why these three?
&lt;ul>
&lt;li>These technologies work one level up from servers/containers&lt;/li>
&lt;li>The unit of work is an instance of an application&lt;/li>
&lt;li>Dead servers can be automatically replaced&lt;/li>
&lt;li>Resources can be scaled manually or automatically&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Which one to pick?
&lt;ul>
&lt;li>&lt;strong>Platform as a Service&lt;/strong> is a sensible default and a great starting place&lt;/li>
&lt;li>&lt;strong>Managed Container Services&lt;/strong> make sense if you have many services or heavy non-web workloads&lt;/li>
&lt;li>&lt;strong>Kubernetes&lt;/strong> is for multi-cloud or on-prem deployment. Kubernetes can run all your infrastructure, but then you must reinvent a lot of things (esp. load balancers, databases, and block storage)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>What about inbound traffic?
&lt;ul>
&lt;li>Load Balancers (ALB)
&lt;ul>
&lt;li>Allow multiple application servers without single point of failure&lt;/li>
&lt;li>Allows autoscaling servers (on CPU usage or custom metric)&lt;/li>
&lt;li>Protects from some kinds of DDOS (adding Nginx/Apache can help)&lt;/li>
&lt;li>Can share one load balancer between multiple environments&lt;/li>
&lt;li>Bonus: handles HTTPS and HTTP redirects&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>What about static files?
&lt;ul>
&lt;li>Okay: unicorn + whitenoise&lt;/li>
&lt;li>Better: Reverse Proxy (Apache or Nginx)&lt;/li>
&lt;li>Best: CDN (CloudFront or CloudFlair)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>What about background tasks?
&lt;ul>
&lt;li>AWS Elastic Beanstalk Workers (weak at on-demand)&lt;/li>
&lt;li>AWS Lambda + SOS (15-minute limit)&lt;/li>
&lt;li>Celery (complex setup)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Deployment
&lt;ul>
&lt;li>Steps
&lt;ol>
&lt;li>Create new running copy of application (code, dependencies, static assets)&lt;/li>
&lt;li>Do peripheral infrastructure changes (migrate, hard part) See &lt;a href="https://www.youtube.com/watch?v=5ErDx3oi1lI&amp;amp;ab_channel=DjangoConUS">Django Migrations: Pitfalls and Solutions&lt;/a>&lt;/li>
&lt;li>Put new running copy of application into rotation&lt;/li>
&lt;li>Remove old running copies&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Environments
&lt;ul>
&lt;li>Dev Code &amp;amp; Fake Data: Local &amp;amp; Staging&lt;/li>
&lt;li>Dev Code &amp;amp; Real Data: Prod-Copy&lt;/li>
&lt;li>Prod Code &amp;amp; Fake Data: Demo/Training&lt;/li>
&lt;li>Prod Code &amp;amp; Real Data: Production&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Use infrastructure-as-code: Terraform&lt;/li>
&lt;li>CI/CD: GitHub Actions&lt;/li>
&lt;li>System Updates: Covered by PaaS. Don&amp;rsquo;t forget Docker.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Data/Secrets
&lt;ul>
&lt;li>Managed Databases
&lt;ul>
&lt;li>Database and OS version upgrades&lt;/li>
&lt;li>Automated backups and managed backup restoration&lt;/li>
&lt;li>Storage autoscaling&lt;/li>
&lt;li>Encryption at rest&lt;/li>
&lt;li>Managed read replicas and failover for prod. Environments (&amp;ldquo;multi-az&amp;rdquo;)
&lt;ul>
&lt;li>Reduces data loss from 24 hours, to seconds&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Static Files (S3)
&lt;ul>
&lt;li>Turn on encryption at rest&lt;/li>
&lt;li>Turn on versioning&lt;/li>
&lt;li>Use django-storages&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Secrets
&lt;ul>
&lt;li>Use a secrets manager&lt;/li>
&lt;li>Use django-environ&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Monitoring
&lt;ul>
&lt;li>Sentry (error monitoring)&lt;/li>
&lt;li>UptimeRobot (uptime monitoring tool)&lt;/li>
&lt;li>DataDog (profiling tool plus error monitoring)&lt;/li>
&lt;li>NewRelic (profiling tool)&lt;/li>
&lt;li>CloudWatch (AWS-native infrastructure monitoring tool)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="-modern-editing-experience-for-your-django-models-with-wagtail-">✨ Modern editing experience for your Django models with Wagtail 🐦&lt;/h3>
&lt;blockquote>
&lt;p>A typical Wagtail project would have your user-facing website structure be defined by the tree structure of the Page model. However, if you have an existing Django project with your own defined Django views, and you don’t want to use Wagtail’s Page model, you can still get plenty of benefits from Wagtail [by integrating the various model mixins into your existing models]!&lt;/p>&lt;/blockquote>
&lt;h2 id="day-2">Day 2&lt;/h2>
&lt;h3 id="postgres-performance-from-slow-to-pro">Postgres Performance: From Slow to Pro&lt;/h3>
&lt;ul>
&lt;li>Use database cache (&lt;strong>shared buffer&lt;/strong>)
&lt;ul>
&lt;li>Check cache hit ratio (you want ~98%) to know if you need to increase this&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Upgrading versions can increase speed&lt;/li>
&lt;li>&lt;a href="https://pgtune.leopard.in.ua/">PGTune&lt;/a> is a good tool for recommended settings!&lt;/li>
&lt;li>Performance tips
&lt;ol>
&lt;li>Stop run away queries, set a statement timeout&lt;/li>
&lt;li>Set up &lt;code>pg_stat_statements&lt;/code> and know your slowest queries. If they&amp;rsquo;re important, fix them&lt;/li>
&lt;li>Add indexes (but not too many) for your longest running and/or your most frequent queries&lt;/li>
&lt;li>Check your cache hit ratio&lt;/li>
&lt;li>Tune memory or add memory as needed
&lt;ul>
&lt;li>make sure your connections have enough memory&lt;/li>
&lt;li>make sure you have enough connections, look at &lt;code>pgbouncer&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h3 id="empathetic-testing-developing-with-compassion-and-humility">Empathetic testing: Developing with compassion and humility&lt;/h3>
&lt;ul>
&lt;li>Write tests with a focus on refactorability, outcomes, and uses&amp;hellip;rather than defending implementation choices
&lt;ul>
&lt;li>Does this test enable, or prevent future changes?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Seek alternatives to patch/mock (Ex: &lt;code>pip install responses freezegun&lt;/code>)&lt;/li>
&lt;li>This is an &amp;ldquo;act of kindness&amp;rdquo; for future developers and creates an enjoyable work experience&lt;/li>
&lt;/ul>
&lt;h3 id="lightning-talks---day-2">Lightning Talks - Day 2&lt;/h3>
&lt;ul>
&lt;li>BUILDING THINGS WHEN YOU HAVE A CLUTTERED MIND
&lt;ol>
&lt;li>CONCENTRATION, EVEN IN AN &amp;ldquo;IDEAL&amp;rdquo; ENVIRONMENT
&lt;ul>
&lt;li>It might be entirely different for you. Experiment.&lt;/li>
&lt;li>Self-help &amp;amp; productivity bloggers do not have all the answers for what works for YOU&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>I AM NOT ACCOMPLISHING ENOUGH - OTHERS ARE *AHEAD* OF ME IN LIFE
&lt;ul>
&lt;li>You are more than your level of productivity&lt;/li>
&lt;li>Life is not a race with a specified timeline&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>SHAME ABOUT HALF-FINISHED OR ABANDONED PROJECTS
&lt;ul>
&lt;li>Forgive yourself&lt;/li>
&lt;li>Life isn&amp;rsquo;t about the # of things you can accomplish&lt;/li>
&lt;li>You are more than your level of productivity&lt;/li>
&lt;li>Not every project needs to be finished&lt;/li>
&lt;li>The journey and experience are valuable&lt;/li>
&lt;li>And, you can almost always go back to that idea or project in the future&amp;hellip;&lt;/li>
&lt;li>Take notes. If you don&amp;rsquo;t write it down, it is not real.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h3 id="dont-buy-the-ai-hype">Don&amp;rsquo;t Buy the &amp;ldquo;A.I.&amp;rdquo; Hype&lt;/h3>
&lt;blockquote>
&lt;p>&amp;ldquo;These models do not hallucinate. They make stuff up because a programmer using a random generation function decided they should&amp;hellip;By saying they hallucinate, we are removing the responsibility of the programmer and the model trainer to be ethical.&amp;rdquo;&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;[In] the most recent version of ChatGPT, in the release notes, one of the bullet points was: Less likely to make up facts&amp;hellip;You cannot make up a fact. A fact is the truth. A fact is. You do not make up a fact. That sentence is an impossibility and shows the irresponsibility going on right now that they actually let that get out in the release notes.&amp;rdquo;&lt;/p>&lt;/blockquote>
&lt;h3 id="theres-more-to-open-source-than-code">There&amp;rsquo;s More to Open Source than Code&lt;/h3>
&lt;ul>
&lt;li>Non-code contributions: Any contributions that don&amp;rsquo;t involve code!
&lt;ul>
&lt;li>Documentation&lt;/li>
&lt;li>Blogging&lt;/li>
&lt;li>Trying stuff out&lt;/li>
&lt;li>Issue triage&lt;/li>
&lt;li>Code reviews&lt;/li>
&lt;li>Answering/Asking questions&lt;/li>
&lt;li>Volunteering at events&lt;/li>
&lt;li>Localizing&lt;/li>
&lt;li>Organizing events&lt;/li>
&lt;li>Hosting/facilitating hackathons&lt;/li>
&lt;li>Creating assets&lt;/li>
&lt;li>Giving talks&lt;/li>
&lt;li>Video content&lt;/li>
&lt;li>Social media&lt;/li>
&lt;li>Content curation&lt;/li>
&lt;li>Moderation&lt;/li>
&lt;li>Mentorship&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Why?
&lt;ul>
&lt;li>Getting out of the comfort zone&lt;/li>
&lt;li>Helping a project thrive&lt;/li>
&lt;li>Improving onboarding&lt;/li>
&lt;li>Meeting people&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>As a code contributor, what can I do to facilitate these?
&lt;ul>
&lt;li>Establish a safe, inclusive, effective community&lt;/li>
&lt;li>Tag issues&lt;/li>
&lt;li>Advocate for the community&lt;/li>
&lt;li>Include non-code contributions in &lt;code>CONTRIBUTORS.md&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="managing-content-with-django">Managing Content with Django&lt;/h3>
&lt;ul>
&lt;li>Wagtail
&lt;ul>
&lt;li>Feature rich: media management, workflow tools, polished user experience, commenting, search&lt;/li>
&lt;li>All encompassing experience. Most objects can be edited in Wagtail.&lt;/li>
&lt;li>Page-focused&lt;/li>
&lt;li>Growing community, lots of momentum&lt;/li>
&lt;li>Some batteries are hard to remove&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>django CMS
&lt;ul>
&lt;li>Minimalist approach, plugins recommended. &amp;ldquo;Django Admin + an editor&amp;rdquo;&lt;/li>
&lt;li>Component-first, easy to make new ones (called &amp;ldquo;plugins&amp;rdquo;)&lt;/li>
&lt;li>Heavy on frontend editing&lt;/li>
&lt;li>Smaller community, slower momentum&lt;/li>
&lt;li>Great multilingual support&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Conclusions
&lt;ul>
&lt;li>Wagtail is all encompassing, getting better about gaps with Django&lt;/li>
&lt;li>Wagtail has the better UX most of the time&lt;/li>
&lt;li>Wagtail is simpler, which is a pro/con&lt;/li>
&lt;li>django CMS is great at being a small piece of a larger puzzle&lt;/li>
&lt;li>django CMS puts more emphasis on content type and structure&lt;/li>
&lt;li>django CMS frontend editing is slick for quick changes&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="an-approach-to-lightweight-tenancy-management-using-django-rest-framework">An approach to lightweight tenancy management using Django Rest Framework&lt;/h3>
&lt;ul>
&lt;li>Single Tenant: When each tenant has it&amp;rsquo;s own application/db.&lt;/li>
&lt;li>&lt;strong>Multitenant&lt;/strong>: When each tenant shares a single application/db.
&lt;ul>
&lt;li>&lt;code>drf-nested-routers&lt;/code> to nest resources under each tenant.&lt;/li>
&lt;li>&lt;code>drf-access-policy&lt;/code> to provide role based permissions.&lt;/li>
&lt;li>You can create a &lt;code>TenantAwareManger&lt;/code> to protect against leaking data on custom queries.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="what-django-deployment-is-really-about">What Django Deployment is Really About&lt;/h3>
&lt;ul>
&lt;li>Four main concerns
&lt;ul>
&lt;li>Static Files
&lt;ul>
&lt;li>&lt;code>manage.py runserver&lt;/code> handled this during development, but&amp;hellip;&lt;/li>
&lt;li>Once you deploy, the production web server needs to handle this (django is too slow).&lt;/li>
&lt;li>Once you&amp;rsquo;ve set &lt;code>STATIC_ROOT&lt;/code>, you have to run &lt;code>manage.py collectstatic&lt;/code>.&lt;/li>
&lt;li>What about Whitenoise?
&lt;ul>
&lt;li>Middleware that intercepts and skips slow django request for static files.&lt;/li>
&lt;li>Great when you can&amp;rsquo;t directly configure your web server.&lt;/li>
&lt;li>Excels when serving from a CDN (handles headers).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Database
&lt;ul>
&lt;li>Your local and production database setups will most likely be different.&lt;/li>
&lt;li>Generally, if you&amp;rsquo;re using a cloud provider, you&amp;rsquo;ll be connecting to a remote, managed, database.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>WSGI Server
&lt;ul>
&lt;li>The thing that actually runs your Django code.&lt;/li>
&lt;li>&lt;code>manage.py runserver&lt;/code> handled this during development, but&amp;hellip;&lt;/li>
&lt;li>In production, you need to pick a production WSGI Server (gunicorn/uwsgi).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Web Server
&lt;ul>
&lt;li>Apache/nginx&lt;/li>
&lt;li>VPS: Manual&lt;/li>
&lt;li>PaaS: Managed&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Bonus:
&lt;ul>
&lt;li>Run &lt;code>manage.py check --deploy&lt;/code>&lt;/li>
&lt;li>Check out &lt;code>django-simple-deploy&lt;/code>&lt;/li>
&lt;li>Check out &lt;code>django-production&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="introduction-to-github-actions-understanding-key-terms-and-building-your-first-github-action">Introduction to GitHub Actions: Understanding Key Terms and Building Your First GitHub Action&lt;/h3>
&lt;blockquote>
&lt;p>You configure a GitHub Actions &lt;strong>workflow&lt;/strong> to be triggered when an &lt;strong>event&lt;/strong> occurs that then runs a series of &lt;strong>jobs&lt;/strong> on a &lt;strong>runner&lt;/strong> that include one or more &lt;strong>steps&lt;/strong> that call a script(s) or &lt;strong>action&lt;/strong>&lt;/p>&lt;/blockquote>
&lt;h2 id="day-3">Day 3&lt;/h2>
&lt;h3 id="keynote-testing-modern-web-apps-like-a-champion">Keynote: Testing Modern Web Apps Like a Champion&lt;/h3>
&lt;ul>
&lt;li>Follow up to &lt;a href="https://www.youtube.com/watch?v=ka5KRLUn47s&amp;amp;ab_channel=PyTexas">PyTexas Keynote - &amp;ldquo;Full-Stack Python&amp;rdquo;&lt;/a>&lt;/li>
&lt;li>Testing challenges: Tests are slow, brittle, flaky, don&amp;rsquo;t make sense, don&amp;rsquo;t make money, require changing context.&lt;/li>
&lt;li>Modern testing goals
&lt;ol>
&lt;li>Focus on building &lt;strong>fast feedback&lt;/strong> loops rather than certain types of tests.&lt;/li>
&lt;li>Make test development as &lt;strong>fast and painless&lt;/strong> as possible.&lt;/li>
&lt;li>Choose test tooling that naturally &lt;strong>complements dev workflows&lt;/strong>.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>Writing good tests (Arrange-Act-Assert Pattern)
&lt;ol>
&lt;li>Arrange things in the system&lt;/li>
&lt;li>Act on the target behavior&lt;/li>
&lt;li>Assert expected outcomes&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>&lt;code>pytest&lt;/code>&lt;/li>
&lt;li>Web test automation tool comparison
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Selenium&lt;/th>
&lt;th>Cypress&lt;/th>
&lt;th>Playwright&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Popular among testers&lt;/td>
&lt;td>Popular among developers&lt;/td>
&lt;td>So hot right now&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>C#, Java, JavaScript, Ruby, Python&lt;/td>
&lt;td>JavaScript only&lt;/td>
&lt;td>C#, Java, JavaScript, Python&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>All major full browsers&lt;/td>
&lt;td>Chrome, Edge, Firefox, Electron&lt;/td>
&lt;td>Chromium+, Firefox, WebKit&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>WebDriver protocol&lt;/td>
&lt;td>In-browser lavaScript&lt;/td>
&lt;td>Debug protocols&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Just a tool - requires a framework&lt;/td>
&lt;td>Full, modern framework&lt;/td>
&lt;td>Full. modern framework&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Can be slow/flaky if waiting is poor&lt;/td>
&lt;td>Visual execution but can be slow&lt;/td>
&lt;td>Typically the fastest execution&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Open source, standards, &amp;amp; gov&lt;/td>
&lt;td>Open source from Cypress&lt;/td>
&lt;td>Open source from Microsoft&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/li>
&lt;li>TLDR
&lt;ol>
&lt;li>Write &lt;strong>unit tests&lt;/strong> to catch problem directly in the code.&lt;/li>
&lt;li>Write &lt;strong>component tests&lt;/strong> if you have a component library.&lt;/li>
&lt;li>Write &lt;strong>API tests&lt;/strong> to grind through variations.&lt;/li>
&lt;li>Write &lt;strong>UI tests&lt;/strong> to cover user-centric behaviors.&lt;/li>
&lt;li>Do &lt;strong>performance and load testing&lt;/strong> to make sure the app works well.&lt;/li>
&lt;li>Run tests &lt;strong>continuously&lt;/strong> as part of standard development workflows.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h3 id="django-migrations-friend-or-foe-optimize-your-django-migrations-for-faster-testing">Django migrations, friend or foe? Optimize your Django migrations for faster testing&lt;/h3>
&lt;ul>
&lt;li>Once you&amp;rsquo;ve built up many migration, creating your test database will start slowing down.&lt;/li>
&lt;li>You can use &lt;code>--keepdb&lt;/code> when running tests to preserve the test database between runs, but this can be hard to take advantage of in CI/CD.&lt;/li>
&lt;li>You could use &lt;code>squashmigrations&lt;/code>, but turns out the database building performance is more or less the same.&lt;/li>
&lt;li>The best solution (for local and ci/cd) is to completely rebuild your migrations.&lt;/li>
&lt;/ul>
&lt;h3 id="inside-out-my-journey-of-understanding-inclusion">Inside Out: My Journey of Understanding Inclusion&lt;/h3>
&lt;ul>
&lt;li>Unconscious bias
&lt;ul>
&lt;li>Ex: The arborist was shocked that a woman mows my lawn&lt;/li>
&lt;li>Ex: The clerk assumed I&amp;rsquo;m not operating the jigsaw&lt;/li>
&lt;li>Sometimes concepts are &lt;strong>locked down&lt;/strong> in our minds (Some since upbringing!)&lt;/li>
&lt;li>An individual is honestly &lt;strong>unaware&lt;/strong> of a prejudice&lt;/li>
&lt;li>It&amp;rsquo;s a &lt;strong>twofold effort&lt;/strong>: to notice it, and then to shape it&lt;/li>
&lt;li>Source of &lt;strong>bias&lt;/strong> can be stereotypes, preconceived notions, past experiences, or even gut instinct
&lt;ul>
&lt;li>Appearance (age, [dis]abilities, gender, race, body size, etc)&lt;/li>
&lt;li>Data (title, education, language, name, religion, etc)&lt;/li>
&lt;li>Opinion (attribution, confirmation, conformity, contrast)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>We can &lt;strong>reflect&lt;/strong> on our own unconscious biases to improve the Django community as a whole&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Double bind
&lt;ul>
&lt;li>Conflicting expectations or stereotypes&lt;/li>
&lt;li>Challenging to meet both &lt;strong>career advancement&lt;/strong> and &lt;strong>likability&lt;/strong> criteria&lt;/li>
&lt;li>Women sometimes have to choose between being liked but not respected, or being respected but not liked&lt;/li>
&lt;li>Double bind paradoxes
&lt;ul>
&lt;li>Maintaining distance yet being approachable&lt;/li>
&lt;li>Advocating for oneself yet serving others&lt;/li>
&lt;li>Authoritative yet participative&lt;/li>
&lt;li>Demanding yet caring&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Culture aspects
&lt;ul>
&lt;li>What is normal in your culture, may not be in others&lt;/li>
&lt;li>We can further explore how cultural factors influences:
&lt;ul>
&lt;li>being a good person&lt;/li>
&lt;li>being a good communicator&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Book: The Culture Map
&lt;ul>
&lt;li>Communication context
&lt;ul>
&lt;li>Some cultures use high-context (&lt;strong>indirect&lt;/strong>, contextual)&lt;/li>
&lt;li>Some cultures use low-context (&lt;strong>direct&lt;/strong>, explicit)&lt;/li>
&lt;li>What&amp;rsquo;s desired in one style can be seen as negative in the other
&lt;ul>
&lt;li>High context could be seen as &lt;strong>not trustworthy or lier&lt;/strong>&lt;/li>
&lt;li>Low context could be seen as &lt;strong>condescending or patronizing&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Implicit does not (usually) work across cultures&lt;/li>
&lt;li>Perhaps is better to err on the side of verbose
&lt;ul>
&lt;li>Less room for misinterpretation&lt;/li>
&lt;li>Less effort to parse from the recipient&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Also important to make explicit why we are being explicit!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Being Convincing
&lt;ul>
&lt;li>A.K.A Persuasive, in a good way!&lt;/li>
&lt;li>Some cultures use principle-first (&lt;strong>Why&lt;/strong>, deductive reasoning)&lt;/li>
&lt;li>Some cultures use application-first (&lt;strong>How&lt;/strong>, inductive reasoning)&lt;/li>
&lt;li>We can find a balance, and provide both&lt;/li>
&lt;li>Use TLDRs&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Language challenges
&lt;ul>
&lt;li>Language [in]flexibility
&lt;ul>
&lt;li>It&amp;rsquo;s easy (at least possible!) to speak neutrally in English&lt;/li>
&lt;li>It&amp;rsquo;s not in other languages, like in Spanish, French, Hindi, or Arabic&lt;/li>
&lt;li>Ex: In Spanish, everyone is supposed to use the masculine form to describe a group of people, but in reality the masculine form is used to describe doctors, engineers, managers, etc., while the feminine form is used to describe nurses, secretaries, teachers, etc.&lt;/li>
&lt;li>Aspirational conditioning starting from childhood&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Inclusive naming in tech
&lt;ul>
&lt;li>Gender (&amp;ldquo;man-in-the-middle&amp;rdquo;)&lt;/li>
&lt;li>Disabilities (&amp;ldquo;crippled&amp;rdquo;)&lt;/li>
&lt;li>Neurodiversities (&amp;ldquo;dumb&amp;rdquo;, &amp;ldquo;crazy&amp;rdquo;, &amp;ldquo;sanity-check&amp;rdquo;)&lt;/li>
&lt;li>Groups that historically have suffered oppression (&amp;ldquo;master&amp;rdquo;, &amp;ldquo;slave&amp;rdquo;)&lt;/li>
&lt;li>Denoting negative connotations (&amp;ldquo;whitelist&amp;rdquo;, &amp;ldquo;blacklist&amp;rdquo;)&lt;/li>
&lt;li>Violent (&amp;ldquo;abort&amp;rdquo;, &amp;ldquo;kill&amp;rdquo;)&lt;/li>
&lt;li>Check your code/project/docs if you can!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Inclusive context
&lt;ul>
&lt;li>&amp;ldquo;Next spring&amp;rdquo;/&amp;ldquo;summer&amp;rdquo; applies to only half of the world!&lt;/li>
&lt;li>Abbreviations/acronyms are used for the sake of brevity or practicality but could impact new contributors
&lt;ul>
&lt;li>CRUD, MTI, DDL, DTL, DDT, etc.&lt;/li>
&lt;li>Useful, but spell it out the first time it&amp;rsquo;s introduced.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Allies
&lt;ul>
&lt;li>&lt;strong>Supportive Advocate&lt;/strong>: Actively supports marginalized groups and role models for inclusive policies/practices&lt;/li>
&lt;li>&lt;strong>Empathetic&lt;/strong>: Understands diverse experiences and challenges, and listens without judgment&lt;/li>
&lt;li>&lt;strong>Confronts Bias&lt;/strong>: Challenges discrimination using their privilege and influence, and amplify underrepresented voices&lt;/li>
&lt;li>&lt;strong>Self-Educates&lt;/strong>: Learns about diversity and inclusion and continuously improves as an ally&lt;/li>
&lt;li>&lt;strong>Not self-appointed&lt;/strong>: Recognized as such by the minorities&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Conclusions
&lt;ul>
&lt;li>&lt;strong>Varied Factors&lt;/strong>: it&amp;rsquo;s a balancing act, but it&amp;rsquo;s worth the effort&lt;/li>
&lt;li>&lt;strong>No Magic Formula&lt;/strong>: no guaranteed recipe or procedure exists&lt;/li>
&lt;li>&lt;strong>Acknowledge Challenges&lt;/strong>: the first step toward resolution&lt;/li>
&lt;li>&lt;strong>Reflect and Act&lt;/strong>: introspect as individuals and as a group&lt;/li>
&lt;li>&lt;strong>Pursue Equity&lt;/strong>: evaluate opportunities for change and improve&lt;/li>
&lt;li>&lt;strong>Provide support&lt;/strong>: allies are key in the process of change&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="lightning-talks---day-3">Lightning Talks - Day 3&lt;/h3>
&lt;ul>
&lt;li>Starting a business in 5 minutes
&lt;ul>
&lt;li>You will fail, but some things will succeed&amp;hellip;this is called &amp;ldquo;finding your niche&amp;rdquo;&lt;/li>
&lt;li>It will be expensive, so keep it simple&lt;/li>
&lt;li>Wait to hire your first employee&amp;hellip;until you have 3x their salary&lt;/li>
&lt;li>Everything is people, and people needs systems&lt;/li>
&lt;li>If you build it, they will not come&amp;hellip;get your first customer before launching&lt;/li>
&lt;li>You can do it!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Strategies for a long running side projects
&lt;ul>
&lt;li>Doc&amp;hellip;you&amp;hellip;men&amp;hellip;tay..shun&amp;hellip;&amp;hellip;Documentation!&lt;/li>
&lt;li>Tests&amp;hellip;what the code should and should not do.&lt;/li>
&lt;li>Issues&amp;hellip;write up your features as issues.
&lt;ul>
&lt;li>&lt;a href="https://simonwillison.net/2022/Oct/29/the-perfect-commit/">The Perfect Commit&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Code comments&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>How to get into dev mentorship
&lt;ul>
&lt;li>The only true measure of success is the number of people you have helped times the effect of that help.&lt;/li>
&lt;li>People in a position of privilege can open doors and make opportunities&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Humility: An unexpected treatment for imposter syndrome
&lt;ul>
&lt;li>Humility provides honest self-awareness and evaluation to contradict doubts.&lt;/li>
&lt;li>Humility encourages others around you.&lt;/li>
&lt;li>Humility is a discipline for everyone.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="best-practices-for-making-a-wagtail-site-as-accessible-as-possible">Best Practices for Making a Wagtail Site as Accessible as Possible&lt;/h3>
&lt;ul>
&lt;li>Accessibility is practice of ensuring our web content is able to be consumed by the widest possible spectrum of people.&lt;/li>
&lt;li>This is a legal responsibility for many&amp;hellip;and a moral responsibility for all.&lt;/li>
&lt;li>Wagtail has two main accessibility initiatives:
&lt;ul>
&lt;li>Accessibility of Wagtail itself&lt;/li>
&lt;li>Accessibility of sites it produces (this talk)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Wagtail comes with an accessibility checks that allows editors to identity and correct accessibility issues themselves.&lt;/li>
&lt;li>What is still up to developers
&lt;ul>
&lt;li>Semantic markup in templates&lt;/li>
&lt;li>Configuring Wagtail&amp;rsquo;s accessibility checker
&lt;ul>
&lt;li>Default is to only show errors that an editor can address&lt;/li>
&lt;li>Configure to show all errors to superusers&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Enforcing proper heading hierarchy
&lt;ul>
&lt;li>You could override blocks to validate&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Promoting good alt text
&lt;ul>
&lt;li>Wagtail doesn&amp;rsquo;t have alt-text on the image model because alt text should be contextual&lt;/li>
&lt;li>You can create a custom block that ties an image to alt text&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Using ARIA labels, where appropriate&lt;/li>
&lt;li>Helping editors help themselves
&lt;ul>
&lt;li>Use &lt;code>help_text&lt;/code>&lt;/li>
&lt;li>Create a &lt;code>HelpPanel&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="one-database-table-one-model-many-behaviours-proxy-model">One database table, one model, many behaviours: Proxy model&lt;/h3>
&lt;p>Proxy models allows you to create a new model based on an existing model without creating a new database table, can be used to modify the behavior of the original model.&lt;/p>
&lt;h3 id="back-to-the-future-of-hypermedia-in-django">Back to the Future of Hypermedia in Django&lt;/h3>
&lt;ul>
&lt;li>REST: Representational State Transfer&lt;/li>
&lt;li>HATEOAS: Hypermedia as the Engine of Application State
&lt;ul>
&lt;li>The server controls everything, the client just displays the result.&lt;/li>
&lt;li>Better for accessibility and sustainability.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Not HATEOAS: JSON as the Engine of Application State
&lt;ul>
&lt;li>Great for complex interdependencies and/or offline apps, but overkill for general CRUD apps.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://www.pythonbynight.com/talks/back-future-hypermedia-django">Common patterns and resources&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="djangos-accessibility-track-record">Django’s accessibility track record&lt;/h3>
&lt;ul>
&lt;li>96.3% of the top 1,000,000 homepages have basic accessibility issues&lt;/li>
&lt;li>~95% of a sample of 25,000 Django homepages have basic accessibility issues&lt;/li>
&lt;li>&lt;a href="https://chrome.google.com/webstore/detail/accessibility-insights-fo/pbjjkligggfmakdaogkfomddhfmpjeni/related">Accessibility Insights Extension&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="panel-discussion-who-put-me-in-charge-moving-beyond-day-to-day-coding-in-django">Panel Discussion: Who put me in charge? Moving beyond day-to-day coding in Django&lt;/h3>
&lt;blockquote>
&lt;p>&amp;ldquo;What&amp;rsquo;s important for you as a managers who is managing devs is to understand what they&amp;rsquo;re working on&amp;rdquo; &amp;ndash; Julia&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;I don&amp;rsquo;t know if it&amp;rsquo;s as much a skill [switching from contributor to manager] as it is like being genuinely interested in the people that work for you&amp;rdquo; &amp;ndash; Elizabeth&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;Sometimes&amp;hellip;I can sort of see a pothole coming, you know, either a technical pothole or maybe a political one or maybe a legal one or a financial one, and say ok, there&amp;rsquo;s a pothole coming up&amp;rdquo; &amp;ndash; Charles&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;Your input is not just being able to directly say &amp;lsquo;ok this project might be a problem.&amp;rsquo; It&amp;rsquo;s when you get other people to the point where they can see the project may be a problem.&amp;rdquo; &amp;ndash; Charles&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;You do lose a little bit of political sway [returning to an individual contributor].&amp;rdquo; &amp;ndash; Andrew&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;A good technical leader breaks a plan down to 3 month chunks.&amp;rdquo; &amp;ndash; Andrew&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;How do you identify some of those initial signals that you&amp;rsquo;re ready to take that step to [becoming a manager]?&amp;rdquo; &amp;ndash; Jay&lt;/p>
&lt;p>&amp;ldquo;It&amp;rsquo;s similar to the question: when are you ready to have children?&amp;rdquo; &amp;ndash; Charles&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;Being really good at documentation has always benefitted me in my career and particularly in management.&amp;rdquo; &amp;ndash; Elizabeth&lt;/p>&lt;/blockquote><a href="mailto: reply@djpeacher.com?subject=Re:%20DjangoCon%20US%202023">Reply via email</a></description></item><item><title>Evening things</title><link>https://www.djpeacher.com/posts/evening-things/</link><pubDate>Thu, 19 Oct 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/evening-things/</guid><description>
&lt;p>One feature that has been missing from my favorite todo app, &lt;a href="https://culturedcode.com/things/">Things 3&lt;/a>, has been the ability to automatically move tasks to the &lt;em>Evening&lt;/em> section when they appear on the &lt;em>Today&lt;/em> page. With the power of shortcuts, we can fix this!&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://www.icloud.com/shortcuts/875aceaf10084b91945eb8150e5a40f2">Click here to download the shortcut&lt;/a>.&lt;/li>
&lt;li>Create an automation to run the shortcut everyday.&lt;/li>
&lt;/ol>
&lt;p>&lt;a href="https://www.icloud.com/shortcuts/875aceaf10084b91945eb8150e5a40f2">&lt;img src="shortcut.png" alt="Shortcut that moves tagged tasks to the evening section in Things 3.">&lt;/a>&lt;/p>
&lt;p>The way it works is you mark tasks you want to appear in &lt;em>Evening&lt;/em> with a tag called &amp;ldquo;Evening&amp;rdquo; (you can change that to whatever you want) and, voilà, everyday the automation will move todays tasks tagged with &amp;ldquo;Evening&amp;rdquo; into the &lt;em>Evening&lt;/em> section!&lt;/p>
&lt;hr>
&lt;p>&lt;em>231020: Just realized that setting the automation to run whenever you open Things 3 also works great!&lt;/em>&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Evening%20things">Reply via email</a></description></item><item><title>Copy anything</title><link>https://www.djpeacher.com/posts/copy-anything/</link><pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/copy-anything/</guid><description>
&lt;p>I don&amp;rsquo;t know about you, but I often find myself wanting to copy some text that isn&amp;rsquo;t copyable! This usually occurs if the text in question is part of an application&amp;rsquo;s UI, for example. To solve this, I created a &lt;a href="https://www.icloud.com/shortcuts/ef96ddc9a9074b83ac0072f839fe4c39">handy shortcut&lt;/a> that allows you to select an area of your screen and extract any text in that area to your clipboard ✨&lt;/p>
&lt;p>&lt;a href="https://www.icloud.com/shortcuts/ef96ddc9a9074b83ac0072f839fe4c39">&lt;img src="shortcut.png" alt="Shortcut that extracts text from an interactive screenshot to your clipboard.">&lt;/a>&lt;/p>
&lt;p>You can integrate this into your workflow by assigning the shortcut to a keyboard shortcut. I used &lt;code>Command + Shift + 7&lt;/code> to bring it next to the existing keyboard shortcuts for taking screenshots.&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Copy%20anything">Reply via email</a></description></item><item><title>Italy</title><link>https://www.djpeacher.com/posts/italy/</link><pubDate>Wed, 20 Sep 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/italy/</guid><description>
&lt;p>If there&amp;rsquo;s one thing I learned, it&amp;rsquo;s that the history of Italy is layered &amp;hellip; just like lasagna 🍝&lt;/p>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy001.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy002.jpg" loading="lazy" />
&lt;figcaption>
1/1000s, f/8/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy003.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy004.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/8/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy005.jpg" loading="lazy" />
&lt;figcaption>
1/500s, f/8/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy006.jpg" loading="lazy" />
&lt;figcaption>
1/2000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy007.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy008.jpg" loading="lazy" />
&lt;figcaption>
1/50s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy009.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy010.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy011.jpg" loading="lazy" />
&lt;figcaption>
1/1600s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy012.jpg" loading="lazy" />
&lt;figcaption>
1/50s, f/8/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy013.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy014.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy015.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy016.jpg" loading="lazy" />
&lt;figcaption>
1/3200s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy017.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy018.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy019.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy020.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy021.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy022.jpg" loading="lazy" />
&lt;figcaption>
1/500s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy023.jpg" loading="lazy" />
&lt;figcaption>
1/1000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy024.jpg" loading="lazy" />
&lt;figcaption>
1/640s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy025.jpg" loading="lazy" />
&lt;figcaption>
1/60s, f/16, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy026.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy027.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy028.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy029.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy030.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy031.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy032.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy033.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy034.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy035.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy036.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy037.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy038.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy039.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy040.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy041.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy042.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy043.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy044.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy045.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy046.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy047.jpg" loading="lazy" />
&lt;figcaption>
1/320s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy048.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy049.jpg" loading="lazy" />
&lt;figcaption>
1/1600s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy050.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy051.jpg" loading="lazy" />
&lt;figcaption>
1/1000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy052.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy053.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy054.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy055.jpg" loading="lazy" />
&lt;figcaption>
1/1250s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy056.jpg" loading="lazy" />
&lt;figcaption>
1/1000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy057.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy058.jpg" loading="lazy" />
&lt;figcaption>
1/250s, f/8/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy059.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/8/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy060.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy061.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy062.jpg" loading="lazy" />
&lt;figcaption>
1/200s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy063.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy064.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy065.jpg" loading="lazy" />
&lt;figcaption>
1/1600s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy066.jpg" loading="lazy" />
&lt;figcaption>
1/2500s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy067.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy068.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy069.jpg" loading="lazy" />
&lt;figcaption>
1/2500s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy070.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy071.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy072.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy073.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy074.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy075.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy076.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy077.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy078.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy079.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy080.jpg" loading="lazy" />
&lt;figcaption>
1/640s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy081.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy082.jpg" loading="lazy" />
&lt;figcaption>
1/3200s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy083.jpg" loading="lazy" />
&lt;figcaption>
1/3200s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy084.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy085.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy086.jpg" loading="lazy" />
&lt;figcaption>
1/2000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy087.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy088.jpg" loading="lazy" />
&lt;figcaption>
1/3200s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy089.jpg" loading="lazy" />
&lt;figcaption>
1/1600s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy090.jpg" loading="lazy" />
&lt;figcaption>
1/500s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy091.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy092.jpg" loading="lazy" />
&lt;figcaption>
1/1000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy093.jpg" loading="lazy" />
&lt;figcaption>
1/640s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy094.jpg" loading="lazy" />
&lt;figcaption>
1/640s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy095.jpg" loading="lazy" />
&lt;figcaption>
1/400s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy096.jpg" loading="lazy" />
&lt;figcaption>
1/400s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy097.jpg" loading="lazy" />
&lt;figcaption>
1/400s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy098.jpg" loading="lazy" />
&lt;figcaption>
1/320s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy099.jpg" loading="lazy" />
&lt;figcaption>
1/1000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy100.jpg" loading="lazy" />
&lt;figcaption>
1/800s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy101.jpg" loading="lazy" />
&lt;figcaption>
1/400s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy102.jpg" loading="lazy" />
&lt;figcaption>
1/1000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy103.jpg" loading="lazy" />
&lt;figcaption>
1/640s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy104.jpg" loading="lazy" />
&lt;figcaption>
1/3200s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy105.jpg" loading="lazy" />
&lt;figcaption>
1/2000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy106.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy107.jpg" loading="lazy" />
&lt;figcaption>
1/500s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy108.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy109.jpg" loading="lazy" />
&lt;figcaption>
1/2500s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy110.jpg" loading="lazy" />
&lt;figcaption>
1/2500s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy111.jpg" loading="lazy" />
&lt;figcaption>
1/500s, f/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy112.jpg" loading="lazy" />
&lt;figcaption>
1/640s, f/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy113.jpg" loading="lazy" />
&lt;figcaption>
1/500s, f/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy114.jpg" loading="lazy" />
&lt;figcaption>
1/2500s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy115.jpg" loading="lazy" />
&lt;figcaption>
1/3200s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy116.jpg" loading="lazy" />
&lt;figcaption>
1/1250s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure>
&lt;figure>
&lt;img alt="Italy" src="https://www.djpeacher.com/posts/italy/italy117.jpg" loading="lazy" />
&lt;figcaption>
1/4000s, f/7/5, ISO , 30mm, Sony ZV-E10
&lt;/figcaption>
&lt;/figure><a href="mailto: reply@djpeacher.com?subject=Re:%20Italy">Reply via email</a></description></item><item><title>Why My Dockerfile ARG Was Missing</title><link>https://www.djpeacher.com/posts/why-my-dockerfile-arg-was-missing/</link><pubDate>Tue, 13 Jun 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/why-my-dockerfile-arg-was-missing/</guid><description>
&lt;p>I was doing some cleanup in a Dockerfile today when I noticed one of the packages was not on the correct version. It was on the latest version despite the fact there was an &lt;code>ARG&lt;/code> pinning it&amp;hellip;or so I thought. After some sleuthing, I found this helpful &lt;a href="https://stackoverflow.com/a/56748289">Stack Overflow post&lt;/a> that explained and pointed me in the &lt;a href="https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact">right direction&lt;/a>.&lt;/p>
&lt;p>Turns out&amp;hellip;&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;An &lt;code>ARG&lt;/code> declared before a &lt;code>FROM&lt;/code>&amp;hellip;can’t be used in any instruction after a &lt;code>FROM&lt;/code>.&amp;rdquo;&lt;/p>&lt;/blockquote>
&lt;p>So the reason my package was installing the latest version was because the &lt;code>ARG&lt;/code> didn&amp;rsquo;t exist! Conveniently, it also didn&amp;rsquo;t error, which is why it went unnoticed 🙃&lt;/p>
&lt;p>The solution? Move the &lt;code>ARG&lt;/code> down one line after the &lt;code>FROM&lt;/code>.&lt;/p>
&lt;pre tabindex="0">&lt;code># Bad
ARG VERSION=1.2.3
FROM ...
# Good
FROM ...
ARG VERSION=1.2.3
&lt;/code>&lt;/pre><a href="mailto: reply@djpeacher.com?subject=Re:%20Why%20My%20Dockerfile%20ARG%20Was%20Missing">Reply via email</a></description></item><item><title>PyTexas 2023</title><link>https://www.djpeacher.com/posts/pytexas-2023/</link><pubDate>Sun, 02 Apr 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/pytexas-2023/</guid><description>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#day-1">Day 1&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keynote-walking-the-line">Keynote: Walking the Line&lt;/a>&lt;/li>
&lt;li>&lt;a href="#a-tale-of-two-typings">A Tale of Two Typings&lt;/a>&lt;/li>
&lt;li>&lt;a href="#trust-fall-hidden-gems-in-mlflow-that-improve-model-credibility">Trust Fall: Hidden Gems in MLFlow that Improve Model Credibility&lt;/a>&lt;/li>
&lt;li>&lt;a href="#exploring-socio-technical-security-concerns-in-critical-open-source-python-repositories">Exploring Socio-technical Security Concerns in Critical Open-source Python Repositories&lt;/a>&lt;/li>
&lt;li>&lt;a href="#a-buildengineer-in-a-buildless-lang">A BuildEngineer in a buildless lang&lt;/a>&lt;/li>
&lt;li>&lt;a href="#recursion-for-beginners-a-beginners-guide-to-recursion">Recursion for Beginners: A Beginner&amp;rsquo;s Guide to Recursion&lt;/a>&lt;/li>
&lt;li>&lt;a href="#improving-code-without-losing-your-mind">Improving code without losing your mind&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lightning-talks">Lightning Talks&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-2">Day 2&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keynote-full-stack-python">Keynote: Full-Stack Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="#unlocking-the-power-of-health-data-an-introduction-to-fhir-and-python">Unlocking the Power of Health Data: An Introduction to FHIR and Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="#having-fun-with-application-design">Having fun with application design&lt;/a>&lt;/li>
&lt;li>&lt;a href="#how-to-build-and-ship-more-secure-python-apps-with-sigstore">How to Build and Ship More Secure Python Apps with Sigstore&lt;/a>&lt;/li>
&lt;li>&lt;a href="#holoviz-visualization-and-interactive-dashboards-in-python">HoloViz: Visualization and Interactive Dashboards in Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="#put-your-pants-on-and-lint-all-your-python-code">Put your Pants on and lint all your Python code!&lt;/a>&lt;/li>
&lt;li>&lt;a href="#using-python-for-digital-investigations">Using Python for Digital Investigations&lt;/a>&lt;/li>
&lt;li>&lt;a href="#lightning-talks-1">Lightning Talks&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;h2 id="day-1">Day 1&lt;/h2>
&lt;h3 id="keynote-walking-the-line">Keynote: Walking the Line&lt;/h3>
&lt;p>&lt;em>Amazing keynote, and I will definitely watch it again when it&amp;rsquo;s uploaded to YouTube!&lt;/em>&lt;/p>
&lt;ul>
&lt;li>Make small, incremental, and easy to understand changes.&lt;/li>
&lt;li>Test often and fail quickly.&lt;/li>
&lt;li>Orient yourself by checking an expected failure.&lt;/li>
&lt;/ul>
&lt;h3 id="a-tale-of-two-typings">A Tale of Two Typings&lt;/h3>
&lt;ul>
&lt;li>Legacy code is simply code without &lt;del>tests&lt;/del> types.&lt;/li>
&lt;li>Define your types, don&amp;rsquo;t just inline them.&lt;/li>
&lt;/ul>
&lt;h3 id="trust-fall-hidden-gems-in-mlflow-that-improve-model-credibility">Trust Fall: Hidden Gems in MLFlow that Improve Model Credibility&lt;/h3>
&lt;p>&lt;em>The talk focused on machine learning, but I think this can still apply outside of that.&lt;/em>&lt;/p>
&lt;ul>
&lt;li>Build trust through documentation and reproducibility.&lt;/li>
&lt;/ul>
&lt;h3 id="exploring-socio-technical-security-concerns-in-critical-open-source-python-repositories">Exploring Socio-technical Security Concerns in Critical Open-source Python Repositories&lt;/h3>
&lt;ul>
&lt;li>Catalyst: Someone intentionally introduced bad code into Linux for a research paper.&lt;/li>
&lt;li>Pull requests power open source contributions.&lt;/li>
&lt;li>FOSS needs to be careful what gets merged in.&lt;/li>
&lt;/ul>
&lt;h3 id="a-buildengineer-in-a-buildless-lang">A BuildEngineer in a buildless lang&lt;/h3>
&lt;ul>
&lt;li>Improve development by reducing stylistic diffs and increasing semantic diffs.&lt;/li>
&lt;/ul>
&lt;h3 id="recursion-for-beginners-a-beginners-guide-to-recursion">Recursion for Beginners: A Beginner&amp;rsquo;s Guide to Recursion&lt;/h3>
&lt;ul>
&lt;li>All recursive solutions have an iterative solution.&lt;/li>
&lt;li>Unless you are dealing with trees, avoid recursion.&lt;/li>
&lt;/ul>
&lt;h3 id="improving-code-without-losing-your-mind">Improving code without losing your mind&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://blog.alexewerlof.com/p/tech-debt-day/">https://blog.alexewerlof.com/p/tech-debt-day/&lt;/a>&lt;/li>
&lt;li>We tend to focus on getting features out the door.&lt;/li>
&lt;li>Like an artist, programmers should improve &amp;ndash; refactor &amp;ndash; their art.&lt;/li>
&lt;li>Set aside time to improve &amp;ndash; refactor &amp;ndash; your code.&lt;/li>
&lt;li>Make code understandable and obvious.&lt;/li>
&lt;/ul>
&lt;h3 id="lightning-talks">Lightning Talks&lt;/h3>
&lt;ul>
&lt;li>Make code obvious. Otherwise, make it familiar. Otherwise, make it well-documented. Start by making it obvious!&lt;/li>
&lt;li>For demos/testing, you can &lt;code>pip install&lt;/code> packages directly inside of the Python Interpreter!&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span style="color:#f92672">import&lt;/span> pip
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">&amp;gt;&amp;gt;&amp;gt;&lt;/span> pip&lt;span style="color:#f92672">.&lt;/span>main([&lt;span style="color:#e6db74">&amp;#34;install&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;django&amp;#34;&lt;/span>])
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="day-2">Day 2&lt;/h2>
&lt;h3 id="keynote-full-stack-python">Keynote: Full-Stack Python&lt;/h3>
&lt;ul>
&lt;li>Python is the &lt;del>second&lt;/del> best language for [doing] everything [together].&lt;/li>
&lt;li>&lt;strong>Simple&lt;/strong> is better than &lt;strong>complex&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Python&lt;/strong> transforms us from &lt;strong>programmers&lt;/strong> into &lt;strong>problem solvers&lt;/strong>.&lt;/li>
&lt;li>Aside from HTML/CSS, the entire stack can be built with Python!&lt;/li>
&lt;li>HTMX 😍&lt;/li>
&lt;/ul>
&lt;h3 id="unlocking-the-power-of-health-data-an-introduction-to-fhir-and-python">Unlocking the Power of Health Data: An Introduction to FHIR and Python&lt;/h3>
&lt;ul>
&lt;li>Healthcare coordination is a mess, with no interoperability.&lt;/li>
&lt;li>Enter the Electronic Health Record, but it had low adoption because it was only a syntactic protocol.&lt;/li>
&lt;li>Enter HL7FHIR which was syntactic, semantic, and even had an API scheme.&lt;/li>
&lt;/ul>
&lt;h3 id="having-fun-with-application-design">Having fun with application design&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Empathize&lt;/strong> &amp;ndash; Curiosity about your users.&lt;/li>
&lt;li>&lt;strong>Define&lt;/strong> &amp;ndash; Understand a problem.&lt;/li>
&lt;li>&lt;strong>Ideate&lt;/strong> &amp;ndash; Creativity in idea generation.&lt;/li>
&lt;li>&lt;strong>Prototype&lt;/strong> &amp;ndash; Build the right thing.&lt;/li>
&lt;li>&lt;strong>Test&lt;/strong> your assumptions.&lt;/li>
&lt;/ul>
&lt;h3 id="how-to-build-and-ship-more-secure-python-apps-with-sigstore">How to Build and Ship More Secure Python Apps with Sigstore&lt;/h3>
&lt;ul>
&lt;li>You wouldn&amp;rsquo;t pickup and chew gum (FOSS) from a wall would you? Would you!?&lt;/li>
&lt;li>Attacks on FOSS are increasing, and Sigstore hopes to fix this.&lt;/li>
&lt;li>&lt;a href="https://ttl.sh/">ttl.sh&lt;/a>, an anonymous and ephemeral Docker image registry.&lt;/li>
&lt;/ul>
&lt;h3 id="holoviz-visualization-and-interactive-dashboards-in-python">HoloViz: Visualization and Interactive Dashboards in Python&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://holoviz.org/">HoloViz&lt;/a>, a framework for developing data visualization dashboards.&lt;/li>
&lt;/ul>
&lt;h3 id="put-your-pants-on-and-lint-all-your-python-code">Put your Pants on and lint all your Python code!&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.pantsbuild.org/">Pants&lt;/a>, a fast, scalable, user-friendly build and developer workflow.&lt;/li>
&lt;li>&lt;a href="https://beta.ruff.rs/docs/">Ruff&lt;/a>, an extremely fast Python linter.&lt;/li>
&lt;/ul>
&lt;h3 id="using-python-for-digital-investigations">Using Python for Digital Investigations&lt;/h3>
&lt;ul>
&lt;li>Digital Investigations (open-source research), research that uses info that is publicly available.&lt;/li>
&lt;li>Research sources include public geospatial, media, user-generated, and archive data.&lt;/li>
&lt;li>Open-source research and software have similar names, different histories, and both use Python as their language of choice.&lt;/li>
&lt;/ul>
&lt;h3 id="lightning-talks-1">Lightning Talks&lt;/h3>
&lt;ul>
&lt;li>Schools, when compared to industry, focus on code instead of other import aspects like tests, documentation, and collaboration.&lt;/li>
&lt;li>Book Recommendations
&lt;ul>
&lt;li>Being Geek: The Software Developer&amp;rsquo;s Career Handbook&lt;/li>
&lt;li>A Philosophy of Software Design&lt;/li>
&lt;li>Think Like a Programmer: An Introduction to Creative Problem Solving&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Single click to select by character, double click to select by word, triple click to select by line!&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20PyTexas%202023">Reply via email</a></description></item><item><title>Multiple Monitors on Apple Silicon</title><link>https://www.djpeacher.com/posts/multiple-monitors-on-apple-silicon/</link><pubDate>Mon, 30 Jan 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/multiple-monitors-on-apple-silicon/</guid><description>
&lt;p>If you are reading this, you probably just discovered that your second monitor isn&amp;rsquo;t working when you connect it to your M1/M2 Mac. Surprise, those chips have only one video output and thus can only support one external monitor! Bummer&amp;hellip;&lt;/p>
&lt;p>To get around this, and move on with your life, you can use &lt;a href="https://www.synaptics.com/products/displaylink-graphics">DisplayLink&lt;/a> to output extra monitors via USB! Normally, connecting to an external monitor looks something like this:
&lt;script src="https://unpkg.com/mermaid/dist/mermaid.min.js">&lt;/script>
&lt;script>
let isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
let mermaidConfig = {
theme: (isDark) ? 'dark' : 'default',
};
mermaid.initialize(mermaidConfig);
&lt;/script>
&lt;div class="mermaid">
flowchart LR
Machine -- video --> Monitor
&lt;/div>&lt;/p>
&lt;p>With DisplayLink, that video signal gets compressed and outputted via USB. This data is then decompressed by a &lt;strong>DisplayLink adaptor&lt;/strong> and outputted to the monitor as a video signal. That looks something like this:&lt;/p>
&lt;script src="https://unpkg.com/mermaid/dist/mermaid.min.js">&lt;/script>
&lt;script>
let isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
let mermaidConfig = {
theme: (isDark) ? 'dark' : 'default',
};
mermaid.initialize(mermaidConfig);
&lt;/script>
&lt;div class="mermaid">
flowchart LR
Machine -- video --> DLS[DL Software] -- compressed --> DLA[DL Adaptor] -- video --> Monitor
&lt;/div>
&lt;p>From my experience using it so far, it works pretty well! I have noticed some minor latency and flickering issues, but that is a small price to pay, I think, for gaining a second monitor.&lt;/p>
&lt;p>So TLDR, to get a second monitor on M1/M2 Mac, you have to:&lt;/p>
&lt;ol>
&lt;li>&lt;a href="https://www.synaptics.com/products/displaylink-graphics/downloads">Install DisplayLink&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.synaptics.com/products/displaylink-graphics/displaylink-products-list">Buy a DisplayLink adaptor&lt;/a> that is compatible with your monitor specs.&lt;/li>
&lt;li>Plug and Display! &lt;i class="twa twa-rocket">&lt;/i>&lt;/li>
&lt;/ol><a href="mailto: reply@djpeacher.com?subject=Re:%20Multiple%20Monitors%20on%20Apple%20Silicon">Reply via email</a></description></item><item><title>Alamo Drafthouse 2022</title><link>https://www.djpeacher.com/posts/alamo-drafthouse-2022/</link><pubDate>Wed, 04 Jan 2023 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/alamo-drafthouse-2022/</guid><description>
&lt;ul>
&lt;li>Puss in Boots: The Last Wish&lt;/li>
&lt;li>Black Panther: Wakanda Forever&lt;/li>
&lt;li>The Whale&lt;/li>
&lt;li>Avatar: The Way of Water&lt;/li>
&lt;li>Bones and All&lt;/li>
&lt;li>Violent Night&lt;/li>
&lt;li>The Menu&lt;/li>
&lt;li>Triangle of Sadness&lt;/li>
&lt;li>Black Adam&lt;/li>
&lt;li>Ticket to Paradise&lt;/li>
&lt;li>Amsterdam&lt;/li>
&lt;li>Pearl&lt;/li>
&lt;li>Howl&amp;rsquo;s Moving Castle &lt;i class="twa twa-ring">&lt;/i>&lt;/li>
&lt;li>Barbarian&lt;/li>
&lt;li>The Invitation&lt;/li>
&lt;li>Three Sound Years of Longing&lt;/li>
&lt;li>Beast&lt;/li>
&lt;li>Bodies Bodies Bodies&lt;/li>
&lt;li>Bullet Train&lt;/li>
&lt;li>Nope&lt;/li>
&lt;li>Thor: Love and Thunder&lt;/li>
&lt;li>The Black Phone&lt;/li>
&lt;li>Star Trek: The Genesis Trilogy &lt;i class="twa twa-rocket">&lt;/i>&lt;/li>
&lt;li>Everything Everywhere All At Once &lt;i class="twa twa-star">&lt;/i>&lt;/li>
&lt;li>Jurassic World Dominion&lt;/li>
&lt;li>Elvis&lt;/li>
&lt;li>Lightyear&lt;/li>
&lt;li>Crimes of the Future&lt;/li>
&lt;li>Men&lt;/li>
&lt;li>Doctor Strange: In the Multiverse of Madness&lt;/li>
&lt;li>Sonic 2&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20Alamo%20Drafthouse%202022">Reply via email</a></description></item><item><title>DjangoCon US 2022</title><link>https://www.djpeacher.com/posts/djangocon-us-2022/</link><pubDate>Wed, 19 Oct 2022 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/djangocon-us-2022/</guid><description>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#day-1">Day 1&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#the-django-admin-is-your-oyster-lets-extend-its-functionality">The Django Admin Is Your Oyster: Let’s Extend Its Functionality&lt;/a>&lt;/li>
&lt;li>&lt;a href="#documenting-django-code-in-2022">Documenting Django Code in 2022&lt;/a>&lt;/li>
&lt;li>&lt;a href="#you-dont-need-containers-to-run-django-in-production">You Don&amp;rsquo;t Need Containers to Run Django in Production&lt;/a>&lt;/li>
&lt;li>&lt;a href="#herding-your-database-queries-diagnosing-improving-and-guarding-performance-of-db-interactions-in-your-django-apps">Herding your database queries: diagnosing, improving and guarding performance of DB interactions in your Django apps&lt;/a>&lt;/li>
&lt;li>&lt;a href="#the-django-jigsaw-puzzle-aligning-all-the-pieces">The Django Jigsaw Puzzle: Aligning All the Pieces&lt;/a>&lt;/li>
&lt;li>&lt;a href="#nurturing-a-legacy-codebase">Nurturing a &amp;ldquo;Legacy&amp;rdquo; Codebase&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-2">Day 2&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#keeping-track-of-architectural-ish-decisions-in-a-sustainable-way">Keeping track of architectural-ish decisions in a sustainable way&lt;/a>&lt;/li>
&lt;li>&lt;a href="#django-migrations-pitfalls-and-solutions">Django Migrations: Pitfalls and Solutions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#django-through-the-years">Django Through the Years&lt;/a>&lt;/li>
&lt;li>&lt;a href="#just-enough-ops-for-developers">Just enough ops for developers&lt;/a>&lt;/li>
&lt;li>&lt;a href="#your-first-deployment-shouldnt-be-so-hard">Your First Deployment Shouldn&amp;rsquo;t Be So Hard!&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#day-3">Day 3&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#async-django-the-practical-guide-youve-been-awaiting-for">Async Django: The practical guide you&amp;rsquo;ve been awaiting for.&lt;/a>&lt;/li>
&lt;li>&lt;a href="#why-large-django-projects-need-a-data-prefetching-layer">Why large Django projects need a data (prefetching) layer&lt;/a>&lt;/li>
&lt;li>&lt;a href="#a-pythonic-full-text-search">A pythonic full-text search&lt;/a>&lt;/li>
&lt;li>&lt;a href="#home-on-the-range-with-django---getting-comfortable-with-ranges-and-range-fields">Home on the range with Django - getting comfortable with ranges and range fields&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;h2 id="day-1">Day 1&lt;/h2>
&lt;h3 id="the-django-admin-is-your-oyster-lets-extend-its-functionality">The Django Admin Is Your Oyster: Let’s Extend Its Functionality&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>Don&amp;rsquo;t make it easy for attackers to find your admin page&amp;hellip;don&amp;rsquo;t use &lt;code>/admin&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Override &lt;code>ModelAdmin.get_search_results&lt;/code> to speed up searching.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Remember to use &lt;code>prefetch_related&lt;/code> and &lt;code>select_related&lt;/code> to reduce DB queries.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Add optional checkboxes to change forms to kickoff extra functions when saved.&lt;/p>
&lt;ul>
&lt;li>You can also dynamically set help text for better UX.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>You can use multiple databases in a single admin.&lt;/p>
&lt;pre tabindex="0">&lt;code># Migrate
$ python manage.py --database=sandbox
# settings.py
DATABASES = {
&amp;#39;default&amp;#39;: {...},
&amp;#39;sandbox&amp;#39;: {...}
}
# utils.py
class SandboxAdminModel(admin.ModelAdmin):
using = &amp;#39;sandbox&amp;#39;
def save_model(self, request, obj, form, change):
obj.save(using=self.using)
def delete_model(self, request, obj):
obj.delete(using=self.using)
def get_queryset(self, request):
return super().get_queryset(request).using(self.using)
# admin.py
@admin.register(MySandboxModel)
class MySandboxModelAdmin(SandboxModelAdmin):
...
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>Extend &lt;code>change_form.html&lt;/code> to add help text for the whole model, not just fields.&lt;/p>
&lt;pre tabindex="0">&lt;code># change_form.html
{% extends &amp;#34;admin/change_form.html&amp;#34; %}
{% block form_top %}
{{ original.admin_help_text }}
{% endblock %}
# models.py
class MyModel(models.Model):
admin_help_text = &amp;#34;...&amp;#34;
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>Create custom actions for bulk changes.&lt;/p>
&lt;pre tabindex="0">&lt;code># admin.py
def increase_msrp_by_8_perc(modeladmin, request, queryset):
...
@admin.register(Car)
class CarAdmin(admin.ModelAdmin):
actions = [increase_msrp_by_8_perc]
&lt;/code>&lt;/pre>&lt;/li>
&lt;/ul>
&lt;h3 id="documenting-django-code-in-2022">Documenting Django Code in 2022&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://diataxis.fr">https://diataxis.fr&lt;/a>: A framework for authoring technical documentation.&lt;/li>
&lt;/ul>
&lt;h3 id="you-dont-need-containers-to-run-django-in-production">You Don&amp;rsquo;t Need Containers to Run Django in Production&lt;/h3>
&lt;ul>
&lt;li>When &lt;strong>should&lt;/strong> you use containers? When you have&amp;hellip;
&lt;ul>
&lt;li>Thousands of programmers.&lt;/li>
&lt;li>Millions of users.&lt;/li>
&lt;li>Billions in valuation.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>There is a lot of FOMO around containers. For example, reproducibility is a big selling point for containers, but generally, this benefit is rarely taken advantage of.&lt;/li>
&lt;li>Some downsides of using containers are added &lt;a href="https://landscape.cncf.io">complexity&lt;/a> and cost.&lt;/li>
&lt;li>What do we want in production?
&lt;ul>
&lt;li>Secure environment.&lt;/li>
&lt;li>Protection against malicious users.&lt;/li>
&lt;li>Don&amp;rsquo;t want to wake up in the middle of the night.&lt;/li>
&lt;li>Seamless code updates.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Web Server (WSGI vs ASGI):
&lt;ul>
&lt;li>WSGI is 20 years old&amp;hellip;and it shows.
&lt;ul>
&lt;li>Limited concurrency.&lt;/li>
&lt;li>Graphic: &lt;em>Showed that as clients scale with &lt;code>gunicorn&lt;/code>, response time gets worse. The graph represented a BEST case scenario.&lt;/em>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>ASGI is 5 years old&amp;hellip;and it shows.
&lt;ul>
&lt;li>Unlimited concurrency (ignoring memory).&lt;/li>
&lt;li>Graphic: &lt;em>Showed that as clients scale with &lt;code>uvicorn&lt;/code>, response time gets worse. The graph represented a WORST case scenario.&lt;/em>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Reverse Proxy (nginx):
&lt;ul>
&lt;li>Can help limit concurrency as needed.&lt;/li>
&lt;li>nginx can handle 20 million RPM on a 8 core VM.&lt;/li>
&lt;li>Caddy is a new/easier alternative, but nginx is tried and tested.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Process Monitor (SystemD)&lt;/li>
&lt;li>Deployment (Git Deploy Config)&lt;/li>
&lt;li>Bonus Tip: Use software that offers LTS.&lt;/li>
&lt;/ul>
&lt;h3 id="herding-your-database-queries-diagnosing-improving-and-guarding-performance-of-db-interactions-in-your-django-apps">Herding your database queries: diagnosing, improving and guarding performance of DB interactions in your Django apps&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://engineering.pathai.com/herding-your-database-queries-in-django">Blog Post&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Tools like &lt;code>django-debug-toolbar&lt;/code> and &lt;code>django-silk&lt;/code> are great for diagnosing rogue queries, but there can be cases where these tools are not going to help directly (e.g. with a js frontend).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>To solve this, they rolled their own &lt;a href="https://github.com/Path-AI/django-request-stats-example/blob/main/req_stats/middleware.py">custom middleware&lt;/a> to analyze queries per request and output the result to the terminal.&lt;/p>
&lt;pre tabindex="0">&lt;code>Received request GET /library/books/, status 200, db_query_count=29, db_query_time_ms=136.421, duration_ms=192.56
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>After you optimize your queries, you can protect yourself from regression with unit tests by taking advantage of &lt;a href="https://pytest-django.readthedocs.io/en/latest/helpers.html#django-assert-max-num-queries">django_assert_max_num_queries&lt;/a>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>You can also use &lt;a href="https://coverage.readthedocs.io/en/6.4.3/contexts.html">coverage context&lt;/a> to check new endpoints that are created and don&amp;rsquo;t have unit tests.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="the-django-jigsaw-puzzle-aligning-all-the-pieces">The Django Jigsaw Puzzle: Aligning All the Pieces&lt;/h3>
&lt;ul>
&lt;li>(other) MVC == MVT (django)
&lt;ul>
&lt;li>Model == Model&lt;/li>
&lt;li>View == Template&lt;/li>
&lt;li>Controller == View&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>The Django Admin is great, but if your &lt;code>admin.py&lt;/code> is growing past 20 lines, you might be relying on it too much.&lt;/li>
&lt;li>&lt;code>django-extensions&lt;/code>: One of the things it does is auto import models in the django shell.&lt;/li>
&lt;li>Don&amp;rsquo;t use &lt;code>/admin&lt;/code>!&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code> WSGI Server
┌────────────────────────────────────────────────┐
│ Middlware │
│ ┌────────────────────────────────────────────┐ │
│ │ Django app │ │
HTTP request │ │ ┌────────────────────────────────────────┐ │ │
─────────────► ┌──────────┐ ──► │ │ │ ┌────────┐ ┌─────┐ ┌──────┐ │ │ │ ┌─────────┐
Browser │Web Server│ │ │ │ │URL conf│ ──► │ │ ─── │Models│ ───┼─┼─┼── │Databases│
◄───────────── └──────────┘ ◄── │ │ │ └────────┘ │ │ └──────┘ │ │ │ └─────────┘
HTTP response ▲ │ │ │ │Views│ │ │ │
│ │ │ │ │ │ ┌─────────┐ │ │ │
▼ │ │ │ │ │ ─── │Templates│ │ │ │
┌──────┐ │ │ │ └─────┘ └─────────┘ │ │ │
│ File │ │ │ └────────────────────────────────────────┘ │ │
│System│ │ └────────────────────────────────────────────┘ │
└──────┘ └────────────────────────────────────────────────┘
&lt;/code>&lt;/pre>&lt;h3 id="nurturing-a-legacy-codebase">Nurturing a &amp;ldquo;Legacy&amp;rdquo; Codebase&lt;/h3>
&lt;ul>
&lt;li>What is a &amp;ldquo;legacy&amp;rdquo; codebase?
&lt;ul>
&lt;li>Code with, potentially forgotten, history.&lt;/li>
&lt;li>Code that follow outdated conventions.&lt;/li>
&lt;li>Usually still running in production.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Should you evolve or rebuild? Ask yourself&amp;hellip;
&lt;ul>
&lt;li>Does the code meet current requirements?&lt;/li>
&lt;li>Are there frequent or sever production issues?&lt;/li>
&lt;li>How healthy are the dependencies?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Lets say you choose &amp;ldquo;evolve,&amp;rdquo; what should you do?
&lt;ul>
&lt;li>Automated tests with good coverage.&lt;/li>
&lt;li>Upgrade/Replace outdated dependencies.&lt;/li>
&lt;li>Use a linter and/or &lt;code>black&lt;/code> for code formatting.&lt;/li>
&lt;li>Use &lt;code>pre-commit&lt;/code> for cross-team consistency.&lt;/li>
&lt;li>Make sure current devs understand the story of the code.&lt;/li>
&lt;li>Help future devs by writing detailed commit messages along with an issue tracker.&lt;/li>
&lt;li>Document!&lt;/li>
&lt;li>Build a culture of leaving clues for the future!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="day-2">Day 2&lt;/h2>
&lt;h3 id="keeping-track-of-architectural-ish-decisions-in-a-sustainable-way">Keeping track of architectural-ish decisions in a sustainable way&lt;/h3>
&lt;ul>
&lt;li>The Problem
&lt;ul>
&lt;li>&lt;strong>Is there a problem?&lt;/strong> Image a new coworker asks why a particular decision was made, but no one knows. We lose relevant information of the decision making process (e.g. context and alternatives considered).&lt;/li>
&lt;li>&lt;strong>Why is this a problem?&lt;/strong> We cannot reliably reflect on our decisions, onboard new people, or evolve.&lt;/li>
&lt;li>&lt;strong>What&amp;rsquo;s causing this?&lt;/strong> Perhaps our tools are not specifically designed to capture changes, nor why they changed.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Architectural Decisions: The thing we need to capture.
&lt;ul>
&lt;li>&lt;strong>Architectural Decisions (AD)&lt;/strong>: A software design choice&amp;hellip;that is architecturally significant. It&amp;rsquo;s an AD if you need to ask:
&lt;ul>
&lt;li>Should we meet to discuss this?&lt;/li>
&lt;li>What framework should we use?&lt;/li>
&lt;li>Could we use [shiny-new-thing] for this?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Architecturally Significant Requirement (ASR)&lt;/strong>: A requirement that has a measurable effect.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions">Architectural Decision Record&lt;/a> (ADR) [&lt;a href="https://web.archive.org/web/20250222052925/https://18f.gsa.gov/2021/07/06/architecture_decision_records_helpful_now_invaluable_later/">also&lt;/a>]: The tool we can use to capture.
&lt;ul>
&lt;li>&lt;strong>What is an ADR?&lt;/strong> A short, practical to fill, text file describing a specific AD. Like a journal entry to future developers.&lt;/li>
&lt;li>&lt;strong>Who is it for?&lt;/strong> Primarily for developers and technology staff.&lt;/li>
&lt;li>&lt;strong>What&amp;rsquo;s in it?&lt;/strong>:
&lt;ul>
&lt;li>Context: Why did this need to happen? What need to be considered?&lt;/li>
&lt;li>Options: What were the options? What were the pros/cons of each?&lt;/li>
&lt;li>Consequences: What will happen as a result?&lt;/li>
&lt;li>Status: Has the decision been implemented or superseded?&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Where does it live?&lt;/strong> Close to code (benefits from peer reviewing and discoverability), wiki, or something else.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Takeaways:
&lt;ul>
&lt;li>Leads teams to consensus.&lt;/li>
&lt;li>Prevents knowledge hoarding.&lt;/li>
&lt;li>Gets new maintainers up to speed.&lt;/li>
&lt;li>Not one person with critical knowledge.&lt;/li>
&lt;li>Shows maintainers if a change they&amp;rsquo;d like to do has been considered previously.&lt;/li>
&lt;li>Can be used as justification to stakeholders.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="django-migrations-pitfalls-and-solutions">Django Migrations: Pitfalls and Solutions&lt;/h3>
&lt;ul>
&lt;li>Migrations and Branches
&lt;ul>
&lt;li>Rewinding migrations only works if the relevant migration files are in the codebase, regardless of applied migrations.&lt;/li>
&lt;li>This can cause problems when switching branches with divergent migrations. Solutions:
&lt;ul>
&lt;li>Reverse migrations before switching branches.&lt;/li>
&lt;li>Restore a backup from before you ran either branch&amp;rsquo;s migrations.&lt;/li>
&lt;li>Use backward compatible migrations.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>A similar problem occurs when trying to merge two branches with divergent migrations. Solution:
&lt;ul>
&lt;li>&lt;code>python manage.py makemigration -merge&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Reversible Migrations
&lt;ul>
&lt;li>Migrations are not time travel&amp;hellip;database backups are!&lt;/li>
&lt;li>Common rewind errors include, using &lt;code>RunPython&lt;/code> with no reverse function, removing a constraint if data has been added that violates that constraint, or deleting a field that is non-null and has no default.&lt;/li>
&lt;li>You can improve reversibility by backing up your database, set fields as nullable for some time before deleting them, and using a reverse function with &lt;code>RunPython&lt;/code> (or at least use &lt;code>noop&lt;/code>).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Backwards Compatible Migrations
&lt;ul>
&lt;li>The Deployment Race Condition: When, during deployment, the codebase and database will be out of sync! This can cause errors on your site if a request comes in while they are out of sync.&lt;/li>
&lt;li>Assuming you are adding to the database more often than removing, you can reduce the frequency of this issue by migrating the database first, then deploying your new code.&lt;/li>
&lt;li>For all other cases, you should try to create backwards compatible migration, i.e. migrations that once applied still work with you old code before that it gets updated.&lt;/li>
&lt;li>Some migrations trivially backwards compatible:
&lt;ul>
&lt;li>Ops with no DB schema change (RunPython, changing choices, squashing migrations, etc.)&lt;/li>
&lt;li>Adding a nullable field.&lt;/li>
&lt;li>Adding a model.&lt;/li>
&lt;li>Removing/Relaxing a constraint.&lt;/li>
&lt;li>Adding a constraint that all existing data/code already meets.&lt;/li>
&lt;li>Removing a model that isn&amp;rsquo;t referenced.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Others, are not. For those, we want to try and get those to &amp;ldquo;look&amp;rdquo; like the above list.
&lt;ul>
&lt;li>You can make some migration noops by using legacy database names.
&lt;ul>
&lt;li>To rename a field, set &lt;code>db_column&lt;/code> to the old name, and django will not update the database.&lt;/li>
&lt;li>To rename a model, set &lt;code>db_table&lt;/code> to old name, and django will not update the database.&lt;/li>
&lt;li>You can even move models between apps an avoid schema changes by using &lt;code>db_table&lt;/code> and &lt;code>SperateDatabaseAndState&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>You can decompose some migrations into two deploys.
&lt;ul>
&lt;li>Adding a constraint: First deploy code that satisfies constraint, then deploy migration to update data.&lt;/li>
&lt;li>Removing a model: First deploy code that remove all references, then deploy migration to remove the model.&lt;/li>
&lt;li>Remove a field: First deploy code that deprecates the field using &lt;code>django-deprecate-fields&lt;/code> and remove all code references, then deploy migration to remove the field.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>For all other, more complicated, cases, use scheduled downtime (maintenance mode).
&lt;ul>
&lt;li>Split/Merge fields/models.&lt;/li>
&lt;li>Change field types.&lt;/li>
&lt;li>Do &amp;ldquo;true&amp;rdquo; field/model renames.&lt;/li>
&lt;li>Compress multiple releases into one.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Failed Migrations
&lt;ul>
&lt;li>If your migrations fail, be aware that:
&lt;ul>
&lt;li>You should abort your deployment.&lt;/li>
&lt;li>Each migration is atomic, but the group of them are not.&lt;/li>
&lt;li>If your migrations are not backwards compatible, your users will be getting errors (unless you are in maintenance mode).&lt;/li>
&lt;li>If your migrations are not backwards compatible, your database will be in an incompatible state, and you can&amp;rsquo;t use &lt;code>manage.py shell&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>To fix this:
&lt;ul>
&lt;li>Avoid this to begin with by testing against production data!&lt;/li>
&lt;li>Correct data with &lt;code>manage.py shell&lt;/code>, assuming your migrations were backwards compatible.&lt;/li>
&lt;li>Push the broken migration onto the server (because you should have aborted the deployment), and reverse them (assuming they were reversible).&lt;/li>
&lt;li>Restore database to a backup.&lt;/li>
&lt;li>&lt;code>manage.py dbshell&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="django-through-the-years">Django Through the Years&lt;/h3>
&lt;p>Not much to say about this, other than it was a very fun history lesson! ❤️&lt;/p>
&lt;h3 id="just-enough-ops-for-developers">Just enough ops for developers&lt;/h3>
&lt;p>My main takeaway here was the following &lt;a href="https://fastapi.tiangolo.com/async/">fastapi analogy&lt;/a>:&lt;/p>
&lt;ul>
&lt;li>CPU = Cook&lt;/li>
&lt;li>Process = Cashier&lt;/li>
&lt;li>Request = Customer&lt;/li>
&lt;/ul>
&lt;h3 id="your-first-deployment-shouldnt-be-so-hard">Your First Deployment Shouldn&amp;rsquo;t Be So Hard!&lt;/h3>
&lt;p>Django is great, until you get to deployment, at which point there are a billion different ways to do it. This can be a barrier to entry for new developers and for experienced developers who need to prototype rapidly&amp;hellip;enter &lt;code>django-simple-deploy&lt;/code>.&lt;/p>
&lt;p>Prerequisites:&lt;/p>
&lt;ul>
&lt;li>A simple Django project.&lt;/li>
&lt;li>Use requirements.txt, Poetry, or Pipenv.&lt;/li>
&lt;li>Use Git.&lt;/li>
&lt;li>Have the target platform&amp;rsquo;s CLI installed with an active account.&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>$ pip install django-simple-deploy
# Add simple deploy to INSTALLED APPS.
$ manage.py simple_deploy --platform fly_io --automate-all
# Profit!
&lt;/code>&lt;/pre>&lt;h2 id="day-3">Day 3&lt;/h2>
&lt;h3 id="async-django-the-practical-guide-youve-been-awaiting-for">Async Django: The practical guide you&amp;rsquo;ve been awaiting for.&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>Async is exciting, but be aware that using async will make your application more complex.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>import asyncio&lt;/code>: Python&amp;rsquo;s implementation of an async runtime that Django uses.&lt;/p>
&lt;pre tabindex="0">&lt;code>import asyncio
async def foo():
...
await bar()
async def main():
tasks = [asyncio.create_task(foo()) for range(5)]
# Important! Wait for all tasks to complete.
await asyncio.gather(*tasks)
asyncio.run(main())
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>Can you use this for background tasks?&lt;/p>
&lt;ul>
&lt;li>Kind off&amp;hellip;if it fails, there is no built-in statuses, retires, or error handling.&lt;/li>
&lt;li>It depends&amp;hellip;on how you are running Django (WSGI or ASGI). With WSGI, hitting an async view spins up an event loop, but will disappear when then view exits (like the example above).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Aggregate Views (what you did before GraphQL):&lt;/p>
&lt;pre tabindex="0">&lt;code>import httpx
import asyncio
async def aggregate_view(): # pseudo code
async with httpx.AsyncClient() as client:
response_a, response_b = asyncio.gather(
client.get(view_a_url),
client.get(view_b_url),
)
return JsonResponse({
&amp;#39;response_a&amp;#39;: response_a.json(),
&amp;#39;response_b&amp;#39;: response_b.json(),
})
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>Chat App (four ways):&lt;/p>
&lt;ul>
&lt;li>Polling (HTMX): Simple, but doesn&amp;rsquo;t scale, isn&amp;rsquo;t responsive, and can lead to self DDOS.&lt;/li>
&lt;li>Long Polling (HTMX + Channels): Responsiveness, but creates a lot connections.&lt;/li>
&lt;li>Server-Sent Events (HTMX + Channels): Better, keeps the connection open.&lt;/li>
&lt;li>WebSockets (HTMX + Channels): Also keeps the connection open, but allows two-way communication.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="why-large-django-projects-need-a-data-prefetching-layer">Why large Django projects need a data (prefetching) layer&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&amp;ldquo;DRY isn&amp;rsquo;t helpful if you need to be careful.&amp;rdquo;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Django REST Framework loves DRY, but has high change amplification as a side effect.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Change Amplification: The expected number of places in the codebase that needs to be modified during an atomic change to the software.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>An example of this in DRF is needing to prefetch in serializers.&lt;/p>
&lt;pre tabindex="0">&lt;code>class MovieSerializer(ModelSerializer):
# data that requires prefetching
# You have to remeber to prefetch for each view using MovieSerializer.
class MovieListView(ListAPIView):
queryset = Movie.objects.prefetch_related(&amp;#34;directors&amp;#34;)
serializer_class = MovieSerializer
class MovieDetailView(ListAPIView):
queryset = Movie.objects.prefetch_related(&amp;#34;directors&amp;#34;)
serializer_class = MovieSerializer
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>You could do one of two things to combat this:&lt;/p>
&lt;ol>
&lt;li>Use non-DRF serializers that are very &lt;strong>explicit&lt;/strong> about prefetching.&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>&lt;code>django-virtual-models&lt;/code>.&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>Or keep DRF, but:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Warn about missing prefetches for each serializer.&lt;/li>
&lt;li>Automatically run necessary prefetches.&lt;/li>
&lt;li>Automatically prevent unnecessary ones.&lt;/li>
&lt;li>Keep serializer nesting support.&lt;/li>
&lt;li>Keep &lt;code>SerializerMethodField&lt;/code> support.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>TLDR: Use tools to be &lt;strong>explicit&lt;/strong> about the data you expect from the DB. Otherwise you&amp;rsquo;ll suffer from performance regressions and your read logic will break frequently.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="a-pythonic-full-text-search">A pythonic full-text search&lt;/h3>
&lt;ul>
&lt;li>PostgreSQL added full text search in 2008.&lt;/li>
&lt;li>Django added &lt;a href="https://docs.djangoproject.com/en/4.1/ref/contrib/postgres/search/">full text search&lt;/a> in 2016.&lt;/li>
&lt;li>Paolo has a &lt;a href="https://www.paulox.net/2017/12/22/full-text-search-in-django-with-postgresql/">great article&lt;/a> outlining the various search features.&lt;/li>
&lt;/ul>
&lt;h3 id="home-on-the-range-with-django---getting-comfortable-with-ranges-and-range-fields">Home on the range with Django - getting comfortable with ranges and range fields&lt;/h3>
&lt;ul>
&lt;li>Ranges are everywhere!&lt;/li>
&lt;li>Support for &lt;a href="https://docs.djangoproject.com/en/4.1/ref/contrib/postgres/fields/#range-fields">ranges&lt;/a> was added for PostgreSQL Django in 2015.&lt;/li>
&lt;li>The typical approach is to us &amp;ldquo;start&amp;rdquo; and &amp;ldquo;stop&amp;rdquo; fields, but that gets complicated fast.
&lt;ul>
&lt;li>The DB doesn&amp;rsquo;t know these two fields are related.&lt;/li>
&lt;li>You have to manually add bounding/validation/constraint logic.&lt;/li>
&lt;li>Queries can get weird/complicated.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Ranges fix all this!
&lt;ul>
&lt;li>1 field that stores lower, upper, and boundary information.&lt;/li>
&lt;li>Automatically validates/constrains values.&lt;/li>
&lt;li>Easy/Intuitive queries.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Ranges (Math):
&lt;ul>
&lt;li>Ranges = Intervals, Inclusive = Closed, Exclusive = Open&lt;/li>
&lt;li>(exclusive, exclusive) - (1, 3) - 2&lt;/li>
&lt;li>[inclusive, exclusive) - [1, 3) - 1, 2 &lt;code>The default for Django ranges!&lt;/code>&lt;/li>
&lt;li>(exclusive, inclusive] - (1, 3] - 2, 3&lt;/li>
&lt;li>[inclusive, inclusive] - [1, 3] - 1, 2, 3&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>There are lots of very useful query filters!
&lt;ul>
&lt;li>&lt;code>__overlap&lt;/code>, &lt;code>__contains&lt;/code>, &lt;code>__adjacent_to&lt;/code>, &lt;code>__fully_lt/gt&lt;/code>, etc.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Django supports a number of different value types via &lt;code>pysycopg2.extra&lt;/code>.
&lt;ul>
&lt;li>You can also create custom types! Example: IP ranges!&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Pitfalls
&lt;ul>
&lt;li>Limited Django Admin support.&lt;/li>
&lt;li>You have to use &lt;code>Cast&lt;/code> when using &lt;code>F()&lt;/code>.&lt;/li>
&lt;li>You have to use &lt;code>Lower&lt;/code> and &lt;code>Upper&lt;/code> database functions to access values.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Resources
&lt;ul>
&lt;li>&lt;code>psycopg2.extras&lt;/code>&lt;/li>
&lt;li>&lt;code>django-range-merge&lt;/code>&lt;/li>
&lt;li>&lt;code>django-generate-series&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20DjangoCon%20US%202022">Reply via email</a></description></item><item><title>Quick Hugo Deploy with GitHub Actions</title><link>https://www.djpeacher.com/posts/quick-hugo-deploy-with-github-actions/</link><pubDate>Thu, 18 Aug 2022 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/quick-hugo-deploy-with-github-actions/</guid><description>
&lt;p>Once you have your &lt;a href="https://gohugo.io">Hugo&lt;/a> project setup, all you need to do is push the following GitHub Action to deploy! &lt;i class="twa twa-rocket">&lt;/i>&lt;/p>
&lt;pre tabindex="0">&lt;code>name: Build
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 0
- uses: peaceiris/actions-hugo@v2
- run: hugo --minify
- uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: public
&lt;/code>&lt;/pre>
&lt;figure>&lt;figcaption>.github/workflows/build.yml&lt;/figcaption>&lt;/figure>
&lt;p>This action does three things each time you push to &lt;code>main&lt;/code>:&lt;/p>
&lt;ol>
&lt;li>Pulls your repository, including all submodules.&lt;/li>
&lt;li>Builds your site with the Hugo CLI.&lt;/li>
&lt;li>Deploys the build to GitHub Pages via the &lt;code>gh-pages&lt;/code> branch.&lt;/li>
&lt;/ol>
&lt;p>That&amp;rsquo;s it. After a few moments, your site should now be live at:&lt;/p>
&lt;pre tabindex="0">&lt;code>https://&amp;lt;username&amp;gt;.github.io/&amp;lt;repository&amp;gt;/
&lt;/code>&lt;/pre>&lt;p>If you want a custom domain for this site, you can &lt;a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/about-custom-domains-and-github-pages">configure a custom domain&lt;/a> with GitHub Pages.&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Quick%20Hugo%20Deploy%20with%20GitHub%20Actions">Reply via email</a></description></item><item><title>How to Be a Lazy Dungeon Master</title><link>https://www.djpeacher.com/posts/how-to-be-a-lazy-dungeon-master/</link><pubDate>Wed, 03 Aug 2022 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/how-to-be-a-lazy-dungeon-master/</guid><description>
&lt;h2 id="the-lazy-dungeon-masters-checklist">The Lazy Dungeon Master&amp;rsquo;s Checklist&lt;/h2>
&lt;p>The steps it takes to prepare a typical game session can be boiled down into a simple checklist:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="#review-the-characters">Review the characters&lt;/a>&lt;/li>
&lt;li>&lt;a href="#create-a-strong-start">Create a strong start&lt;/a>&lt;/li>
&lt;li>&lt;a href="#outline-potential-scenes">Outline potential scenes&lt;/a>&lt;/li>
&lt;li>&lt;a href="#define-secrets-and-clues">Define secrets and clues&lt;/a>&lt;/li>
&lt;li>&lt;a href="#develop-fantastic-locations">Develop fantastic locations&lt;/a>&lt;/li>
&lt;li>&lt;a href="#outline-important-npcs">Outline important NPCs&lt;/a>&lt;/li>
&lt;li>&lt;a href="#choose-relevant-monsters">Choose relevant monsters&lt;/a>&lt;/li>
&lt;li>&lt;a href="#select-magic-item-rewards">Select magic item rewards&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="review-the-characters-hahahugoshortcode26s0hbhb">Review the characters &lt;i class="twa twa-thinking-face">&lt;/i>&lt;/h3>
&lt;p>The first thing you need to do is write down the names, backgrounds, motivations, and desires of the characters. Use these notes to prime your brain and tie the characters to the rest of your game.&lt;/p>
&lt;h3 id="create-a-strong-start-hahahugoshortcode26s1hbhb">Create a strong start &lt;i class="twa twa-flexed-biceps-light-skin-tone">&lt;/i>&lt;/h3>
&lt;p>Set the stage by asking yourself, what&amp;rsquo;s happening, what&amp;rsquo;s the point, and where&amp;rsquo;s the action? We are looking for an event, hook, and closest action in one, maybe two, sentences. When in doubt, start with combat.&lt;/p>
&lt;h3 id="outline-potential-scenes-hahahugoshortcode26s2hbhb">Outline potential scenes &lt;i class="twa twa-clapper-board">&lt;/i>&lt;/h3>
&lt;p>Write down a short list of scenes that &lt;strong>might&lt;/strong> occur in your game. Only write down as much as you need to prime your brain. These scenes are not in any specific order and may not even occur.&lt;/p>
&lt;h3 id="define-secrets-and-clues-hahahugoshortcode26s3hbhb">Define secrets and clues &lt;i class="twa twa-magnifying-glass-tilted-right">&lt;/i>&lt;/h3>
&lt;p>Write down ten secrets and clues the characters &lt;strong>might&lt;/strong> discover during your game. These secrets and clues bring your campaign to life and reveal small pieces of the story or history of the world. They should be abstract enough to allow you to improvise their discovery during the game.&lt;/p>
&lt;h3 id="develop-fantastic-locations-hahahugoshortcode26s4hbhb">Develop fantastic locations &lt;i class="twa twa-national-park">&lt;/i>&lt;/h3>
&lt;p>Develop one or two locations that the characters &lt;strong>might&lt;/strong> discover for every hour of play you expect to have. These locations need to have an evocative name, three fantastic aspects, and be described using age and size. It is also a good idea to tie some of these locations to the backgrounds of the characters.&lt;/p>
&lt;h3 id="outline-important-npcs-hahahugoshortcode26s5hbhb">Outline important NPCs &lt;i class="twa twa-robot">&lt;/i>&lt;/h3>
&lt;p>Prepare a few NPCs that the characters &lt;strong>might&lt;/strong> discover and will drive the game session and the adventure. Improvise others during the game. NPC outlines should be brief and include a name, connection to the adventure, and a character archetype.&lt;/p>
&lt;h3 id="choose-relevant-monsters-hahahugoshortcode26s6hbhb">Choose relevant monsters &lt;i class="twa twa-man-zombie">&lt;/i>&lt;/h3>
&lt;p>Make a list of monsters the characters &lt;strong>might&lt;/strong> encounter that make sense for the story, situation, and location of your game. Don&amp;rsquo;t tie these monsters to any specific location or situation. Instead, improvise encounters based on the story and situation during the game. For boss fights, remember to account for character capabilities without negating those capabilities.&lt;/p>
&lt;h3 id="select-magic-item-rewards-hahahugoshortcode26s7hbhb">Select magic item rewards &lt;i class="twa twa-magic-wand">&lt;/i>&lt;/h3>
&lt;p>Players love magic items! Regularly ask the players what kinds of items they&amp;rsquo;d like to discover. Use that information to make a list of magic items that the characters &lt;strong>might&lt;/strong> discover during your game. When it fits the story, drop an item into the game. You can also drop in random magic items for some variability. You can tie magic items to the story by connecting them to quests, secrets, or clues.&lt;/p>
&lt;h2 id="adventuremd">adventure.md&lt;/h2>
&lt;p>The Lazy Dungeon Master&amp;rsquo;s Checklist should easily fit on a single page. Here is a markdown template for your next session!&lt;/p>
&lt;p>Roll for initiative! &lt;i class="twa twa-game-die">&lt;/i>&lt;/p>
&lt;pre tabindex="0">&lt;code>## The Strong Start
Description of your strong start.
## Potential Scenes
- Description of your scene.
-
## Secrets and Clues
- Description of a secret or clue.
-
-
-
-
-
-
-
-
-
## Fantastic Locations
**Location**: aspect, aspect, aspect
**Location**: aspect, aspect, aspect
**Location**: aspect, aspect, aspect
## NPCs
**Name.** Description/Connection. *Character* from *X*.
**Name.** Description/Connection. *Character* from *X*.
**Name.** Description/Connection. *Character* from *X*.
## Monsters
- Name
-
## Magic Items
- Name/Description
-
&lt;/code>&lt;/pre><a href="mailto: reply@djpeacher.com?subject=Re:%20How%20to%20Be%20a%20Lazy%20Dungeon%20Master">Reply via email</a></description></item><item><title>Python Version Manager 101 (pyenv)</title><link>https://www.djpeacher.com/posts/pyenv/</link><pubDate>Mon, 28 Feb 2022 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/pyenv/</guid><description>
&lt;p>When you boot up a new machine, it will usually come preinstalled with a version of Python. You can verify this by running the following:&lt;/p>
&lt;pre tabindex="0">&lt;code># This shows the version installed.
$ python -V
Python 2.7.18
# This shows the location of the installation.
$ which python
/usr/bin/python
&lt;/code>&lt;/pre>&lt;p>That&amp;rsquo;s great, but &lt;strong>do not use &amp;ldquo;System Python.&amp;rdquo;&lt;/strong> It came with your operating system because it is a dependency that should not be tampered with. Doing so could harm your system or the very least, cause you a lot of grief down the road.&lt;/p>
&lt;p>You could work around this by manually installing other versions of Python, but that is hard to manage and can get messy fast, especially if you have multiple projects depending on &lt;strong>different&lt;/strong> versions of Python.&lt;/p>
&lt;p>Instead of dealing with all that, you might want to invest of few minutes setting up a &lt;strong>Python Version Manager&lt;/strong>, in this case, &lt;code>pyenv&lt;/code>, that will let you easily switch between multiple versions of Python.&lt;/p>
&lt;p>If that sounds good to you, continue reading to learn how to install and use &lt;code>pyenv&lt;/code>.&lt;/p>
&lt;h2 id="installation">Installation&lt;/h2>
&lt;p>I&amp;rsquo;m not going to get super specific on the installation of &lt;code>pyenv&lt;/code> because it depends on the machine you are working with. These instructions are what got me set up on my specific machine (M1 Mac using Zsh), so your mileage may vary. Here are the official instructions if these don&amp;rsquo;t work for you.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://brew.sh">homebrew&lt;/a>, the missing package manager for macOS.&lt;/li>
&lt;li>&lt;a href="https://github.com/pyenv/pyenv#installation">pyenv&lt;/a>, the python version manager.&lt;/li>
&lt;li>&lt;a href="https://github.com/pyenv/pyenv-virtualenv#installation">pyenv-virtualenv&lt;/a>, a virtualenv plugin for &lt;code>pyenv&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h3 id="dependencies">Dependencies&lt;/h3>
&lt;p>Before you can install &lt;code>pyenv&lt;/code>, you need to install all its dependencies.&lt;/p>
&lt;pre tabindex="0">&lt;code># Install Xcode Command Line Tools
xcode-select --install
# Install Homebrew
/bin/bash -c &amp;#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&amp;#34;
echo &amp;#39;export PATH=/opt/homebrew/bin:$PATH&amp;#39; &amp;gt;&amp;gt; ~/.zshrc
# Install Python build dependencies
brew install openssl readline sqlite3 xz zlib
&lt;/code>&lt;/pre>&lt;h3 id="install-pyenv">Install &lt;code>pyenv&lt;/code>&lt;/h3>
&lt;p>Now we can install &lt;code>pyenv&lt;/code> using &lt;code>brew&lt;/code> and configure your terminal to let &lt;code>pyenv&lt;/code> dictate which version of Python you are using.&lt;/p>
&lt;pre tabindex="0">&lt;code># Install pyenv
brew install pyenv pyenv-virtualenv
# Configure shell environment
echo &amp;#39;eval &amp;#34;$(pyenv init --path)&amp;#34;&amp;#39; &amp;gt;&amp;gt; ~/.zshrc
echo &amp;#39;eval &amp;#34;$(pyenv virtualenv-init -)&amp;#34;&amp;#39; &amp;gt;&amp;gt; ~/.zshrc
echo &amp;#39;if which pyenv-virtualenv-init &amp;gt; /dev/null; then eval &amp;#34;$(pyenv virtualenv-init -)&amp;#34;; fi&amp;#39; &amp;gt;&amp;gt; ~/.zprofile
&lt;/code>&lt;/pre>&lt;p>You know you&amp;rsquo;ve set everything up correctly if you see the following:&lt;/p>
&lt;pre tabindex="0">&lt;code># You&amp;#39;ve installed pyenv!
$ pyenv --version
pyenv 2.2.4
# pyenv put itself between your system and terminal!
# Notice that the installation path is different now.
$ which python
/Users/&amp;lt;user&amp;gt;/.pyenv/shims/python
&lt;/code>&lt;/pre>&lt;h2 id="usage">Usage&lt;/h2>
&lt;p>With &lt;code>pyenv&lt;/code> installed, we can do some powerful stuff!&lt;/p>
&lt;ul>
&lt;li>Install pretty much whatever version of Python we want.&lt;/li>
&lt;li>Create virtualenvs that act like separate Python installations.&lt;/li>
&lt;li>Set the global, local, and shell versions of Python we want to use.&lt;/li>
&lt;/ul>
&lt;h3 id="how-it-works">How It Works&lt;/h3>
&lt;p>By default, &lt;code>pyenv&lt;/code> will use &amp;ldquo;System Python,&amp;rdquo; which as I mentioned we shouldn&amp;rsquo;t use, so we need to install other versions. When you install a new version of Python using &lt;code>pyenv&lt;/code>, it stores them in its root directory (&lt;code>~/.pyenv&lt;/code>).&lt;/p>
&lt;p>After we install the versions of Python we want, we need to tell &lt;code>pyenv&lt;/code> where and when we want to use each version by using the &lt;code>global&lt;/code>, &lt;code>local&lt;/code>, and &lt;code>shell&lt;/code> commands.&lt;/p>
&lt;ul>
&lt;li>When we use the &lt;code>global&lt;/code> command, we are setting the global Python version. &lt;code>pyenv&lt;/code> records this information in this file &lt;code>~/.pyenv/version&lt;/code>.&lt;/li>
&lt;li>When we use the &lt;code>local&lt;/code> command, we are setting the local application-specific Python version. This will apply to all its subdirectories as well! &lt;code>pyenv&lt;/code> records this information by creating a file in the current directory called &lt;code>.python-version&lt;/code>.&lt;/li>
&lt;li>When we use the &lt;code>shell&lt;/code> command, we are setting the shell-specific Python version. &lt;code>pyenv&lt;/code> records this information by setting the &lt;code>$PYENV_VERSION&lt;/code> environment variable.&lt;/li>
&lt;/ul>
&lt;h4 id="so-how-does-pyenv-decide-which-version-to-use">So how does &lt;code>pyenv&lt;/code> decide which version to use?&lt;/h4>
&lt;p>As we move between directories &lt;code>pyenv&lt;/code> searches for the files and environment variable we set above and processes them in the following order:&lt;/p>
&lt;ol>
&lt;li>&lt;code>$PYENV_VERSION&lt;/code>. This version will be used if set.&lt;/li>
&lt;li>&lt;code>.python-version&lt;/code>. This version will be used if this file exists.&lt;/li>
&lt;li>&lt;code>~/.pyenv/version&lt;/code>. This version will be used if this file exists.&lt;/li>
&lt;li>If none of these exist, it will use &amp;ldquo;System Python&amp;rdquo;.&lt;/li>
&lt;/ol>
&lt;h4 id="what-about-virtualenvs">What about virtualenvs?&lt;/h4>
&lt;p>Working with virtualenvs is extremely simple. All we have to do is use the &lt;code>virtualenv&lt;/code> command and specify the version of Python we want to use. &lt;code>pyenv&lt;/code> then creates the virtualenv it&amp;rsquo;s root directory which we can use with the &lt;code>global&lt;/code>, &lt;code>local&lt;/code>, and &lt;code>shell&lt;/code> commands, just like the full Python installations!&lt;/p>
&lt;p>And if you configured your shell correctly, you don&amp;rsquo;t even have to run any &lt;code>activate&lt;/code> or &lt;code>deactivate&lt;/code> commands, &lt;code>pyenv&lt;/code> will handle that when you enter and leave directories.&lt;/p>
&lt;h3 id="command-overview">Command Overview&lt;/h3>
&lt;p>Now that you have a basic understanding of how &lt;code>pyenv&lt;/code> works, here is an overview of its available commands.&lt;/p>
&lt;h4 id="install">&lt;code>install&lt;/code>&lt;/h4>
&lt;p>Install a Python version using python-build. &lt;code>pyenv&lt;/code> builds each installation from source, so it could be a few seconds for this command to complete.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv install 3.10.2
&lt;/code>&lt;/pre>&lt;h4 id="uninstall">&lt;code>uninstall&lt;/code>&lt;/h4>
&lt;p>Uninstall a specific Python version.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv uninstall 3.10.2
&lt;/code>&lt;/pre>&lt;h4 id="versions">&lt;code>versions&lt;/code>&lt;/h4>
&lt;p>List all Python versions available to pyenv.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv versions
system
* 3.10.2 (set by /Users/&amp;lt;user&amp;gt;/.pyenv/version)
&lt;/code>&lt;/pre>&lt;p>Notice that there is a &lt;code>*&lt;/code> indicating what is the active version of Python and it tells you how &lt;code>pyenv&lt;/code> decided to use that version.&lt;/p>
&lt;h4 id="global">&lt;code>global&lt;/code>&lt;/h4>
&lt;p>Set or show the global Python version.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv global
system
$ pyenv global 3.10.2
$ pyenv global
3.10.2
$ python -V
Python 3.10.2
&lt;/code>&lt;/pre>&lt;p>This will update the &lt;code>/Users/&amp;lt;user&amp;gt;/.pyenv/version&lt;/code> file.&lt;/p>
&lt;h4 id="local">&lt;code>local&lt;/code>&lt;/h4>
&lt;p>Set or show the local application-specific Python version.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv local
pyenv: no local version configured for this directory
$ pyenv local 3.10.2
$ pyenv local
3.10.2
$ python -V
Python 3.10.2
&lt;/code>&lt;/pre>&lt;p>This will create or update the &lt;code>.python-version&lt;/code> file in the current directory.&lt;/p>
&lt;h4 id="shell">&lt;code>shell&lt;/code>&lt;/h4>
&lt;p>Set or show the shell-specific Python version.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv shell
pyenv: no shell-specific version configured
$ pyenv shell 3.10.2
$ pyenv shell
3.10.2
$ python -V
Python 3.10.2
&lt;/code>&lt;/pre>&lt;p>This will set the &lt;code>$PYENV_VERSION&lt;/code> environment variable.&lt;/p>
&lt;h4 id="virtualenv">&lt;code>virtualenv&lt;/code>&lt;/h4>
&lt;p>Creates a Python virtualenv in the &lt;code>pyenv&lt;/code> root directory.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv virtualenv [version] &amp;lt;name&amp;gt;
&lt;/code>&lt;/pre>&lt;p>If no version number is given, the current active version will be used. You can name the virtualenv whatever you want, but it is best practice to name it the same as the relevant project.&lt;/p>
&lt;h4 id="virtualenv-delete">&lt;code>virtualenv-delete&lt;/code>&lt;/h4>
&lt;p>Uninstall a specific Python virtualenv.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv virtualenv-delete &amp;lt;name&amp;gt;
&lt;/code>&lt;/pre>&lt;h4 id="virtualenvs">&lt;code>virtualenvs&lt;/code>&lt;/h4>
&lt;p>List all Python virtualenvs.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv virtualenv project
$ pyenv virtualenvs
3.10.2/envs/project (created from /Users/&amp;lt;user&amp;gt;/.pyenv/versions/3.10.2)
project (created from /Users/&amp;lt;user&amp;gt;/.pyenv/versions/3.10.2)
&lt;/code>&lt;/pre>&lt;p>You will see two entries per virutalenv (&lt;code>pyenv versions&lt;/code> does the same thing). In this example, &lt;code>3.10.2/envs/project&lt;/code> is the actual virtualenv and &lt;code>project&lt;/code> is a shorthand simlink to that folder.&lt;/p>
&lt;p>Once a vitualenv is created, we can use it along with the &lt;code>global&lt;/code>, &lt;code>local&lt;/code>, and &lt;code>shell&lt;/code> commands!&lt;/p>
&lt;pre tabindex="0">&lt;code>$ pyenv local project
$ pyenv local
project
&lt;/code>&lt;/pre>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I hope by reading this post you see how useful having a version manager like &lt;code>pyenv&lt;/code> can be and were able to get up and running quickly. I know I sure have!&lt;/p>
&lt;p>If you have any feedback, click the button below. I&amp;rsquo;d love to hear from you!&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Python%20Version%20Manager%20101%20%28pyenv%29">Reply via email</a></description></item><item><title>Immune Part 1: Meet Your Immune System</title><link>https://www.djpeacher.com/posts/immune-part-1/</link><pubDate>Wed, 23 Feb 2022 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/immune-part-1/</guid><description>
&lt;p class="notice">
If you’d like to read the whole thing yourself and support the author, you can buy the book (not affiliated): &lt;a href="https://sites.prh.com/immune">https://sites.prh.com/immune&lt;/a>
&lt;/p>
&lt;h2 id="what-is-the-immune-system">What is the Immune System?&lt;/h2>
&lt;p>3.5 billion years ago, cells figured out how to gather resources by leeching off of other cells. 541 million years ago,
multicellular life formed and developed systems to defend against intruders. While we can’t directly analyze ancient
immune systems, we can study it via the tree of life. The farther separated two creatures are on the tree and still
share the same trait, the older the trait must generally be.&lt;/p>
&lt;p>Modern animals are the height of the immune system development which at its core is a tool to distinguish the &lt;strong>other&lt;/strong>
from the &lt;strong>self&lt;/strong>. While distinguishing the other from the self is the &lt;em>core&lt;/em>, it is not the &lt;em>goal&lt;/em>. Its overall goal is
to achieve &lt;em>homeostasis&lt;/em>, what we call health.&lt;/p>
&lt;p>Ultimately the battle to stay healthy is futile and will be lost in the end, but our immune system fights to keep us
going a little bit longer. Our immune system can also go wrong and be corrupted. When tricked, it can help spread
diseases, protect cancer cells, cause allergies, and even get confused and deceive the body itself is the enemy. When
too enthusiastic, it can cause unpleasant systems while it’s doing its job and can even cause death.&lt;/p>
&lt;h2 id="what-is-there-to-defend">What Is There to Defend?&lt;/h2>
&lt;p>Immune cells are tasked with protecting the ~40 trillion cells in your body. Most threats are stopped by the skin, but
intruders can still enter via your mucus membranes: windpipe, lungs, eyelids, mouth, nose, stomach, intestines,
reproductive traces, and bladder.&lt;/p>
&lt;h2 id="what-are-your-cells">What Are Your Cells?&lt;/h2>
&lt;p>Cells are the smallest units of life that we can clearly identify. Generally, something is alive if it separates itself
from the universe around it, has a metabolism (intakes outside resources and expels inside garbage), responds to
stimuli, grows, and can make more of itself. Cells are not conscious, have free will, or feelings. They are essentially
biological robots. Cells have various “organs” inside them called organelles. These organs are surrounded by millions of
molecules. Half of which are water molecules and other half consists of 1k-10k different kinds of proteins.&lt;/p>
&lt;p>Proteins are the most fundamental organic building blocks and can be used for basically everything, from sending
signals, constructing simple wall/structures to complex micromachines. Proteins are made from chains of amino acids
(organic building blocks that come in 20 different varieties). A typical protein has 50-2000 amino acids (30k is largest
known). There are billions of billions of possible proteins, but only 1-million to 1-billion useful ones. Your cells
know which proteins to make thanks to the instructions on your DNA.&lt;/p>
&lt;p>About 1% of your DNA is made up of building manuals for proteins. The rest is responsible for regulating which proteins
are built when and how and how many of them at which time. Instructions on DNA are converted into proteins in a two-step
process: 1. Special proteins read DNA and covers it into messenger molecules call mRNA (the language DNA uses to
communicate orders). 2. The mRNA is then taken from the nucleus to the proteus production center called the ribosome.
Here, mRNA is read and translated into amino acids that are put together in the order they arrived.&lt;/p>
&lt;p>In the world of proteins, shape is everything. Their shapes determine what they can and can’t do. A proteins shape is
determined by the amino acids it has and their sequence. As a protein is built, the chain folds together into a specific
shape. Proteins work by interacting with each other in very complex ways. They move and interact by wiggling really
fast. Because of their scale they behave very different compared to a human scale and can in theory move up to 5m/s if
they were not surrounded by other molecules. This motion is called Brownian motion and is why water is so important for
cells and leads to biological pathways.&lt;/p>
&lt;p>Biological pathways is a fancy word to describe a series of interactions between individual things that lead to a change
in a cell. A proteins specific shape enable them to fit together or interact with other proteins in specific ways.
Sequences of these interactions, called pathways, cause cells to do things. An individual cell is pretty dump, but many
cells working are pretty smart. The phenomenon occurs in many places in nature and is called emergence. It is the
observation that the whole has properties and abilities that the parts do not have.&lt;/p>
&lt;h2 id="the-empires-and-kingdoms-of-the-immune-system">The Empires and Kingdoms of the Immune System&lt;/h2>
&lt;p>Your Immune System consists of two major realms: Innate and Adaptive Immunity.&lt;/p>
&lt;p>Your Innate Immune System is ready to fight after birth, and can identify if an enemy is not self, but other. It does
the down-and-dirty hand-to-hand combat, but it also determines what broad category your enemies fall in and how
dangerous they are. And finally it has the power to activate your second line of defense:&lt;/p>
&lt;p>Your Adaptive Immune System, which needs a few years before it is ready to deploy efficiently. It is specific and can
draw from an incredibly large library to fight every possible individual enemy that nature can throw at it, with
powerful super weapons. But while it is powerful, one of its most important jobs is to make the Innate Immune System
even stronger.&lt;/p><a href="mailto: reply@djpeacher.com?subject=Re:%20Immune%20Part%201%3a%20Meet%20Your%20Immune%20System">Reply via email</a></description></item><item><title>What Am I?</title><link>https://www.djpeacher.com/posts/what-am-i/</link><pubDate>Tue, 22 Feb 2022 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/what-am-i/</guid><description>
&lt;p>I am someone who generally writes code using web-based technologies. What am I? This question has bugged me for years and I think I&amp;rsquo;ve finally come to a conclusion.&lt;/p>
&lt;p>If you like spoilers, look at my &lt;a href="https://www.djpeacher.com/">home page&lt;/a> or the &lt;a href="#okay-so-what-am-i">conclusion&lt;/a> below. If not, here is my breakdown. Keep in mind that there exists many more titles than I mention below, but I am just trying to focus on my field. This is also just based on what I&amp;rsquo;ve been exposed to and I am happy to be proven wrong.&lt;/p>
&lt;p>So what am I? Let&amp;rsquo;s start at the beginning&amp;hellip;&lt;/p>
&lt;h2 id="programmercoder">Programmer/Coder&lt;/h2>
&lt;p>I am 100% a programmer and a coder. Those two words essentially mean the same thing. I write programs with code.&lt;/p>
&lt;p>While I think all other titles fall under this category, I do not consider it a job title. It&amp;rsquo;s just too generic to represent an occupation.&lt;/p>
&lt;h2 id="software-engineer-vs-software-developer">Software Engineer vs. Software Developer&lt;/h2>
&lt;p>Going a level deeper, we find Software Engineers and Software Developers. As far as I can tell, both of these mean the same thing, and the word &amp;ldquo;Software&amp;rdquo; is optional. These titles represent anyone who designs and creates systems to solve problems.&lt;/p>
&lt;p>So I am a [Software] Engineer/Developer?&lt;/p>
&lt;h2 id="web-vs-mobile-vs-desktop">Web vs. Mobile vs. Desktop&lt;/h2>
&lt;p>Well, we can go another layer deeper and specialize in web, mobile, or desktop applications which produces these lovely titles:&lt;/p>
&lt;ul>
&lt;li>Web [Software] Engineer/Developer&lt;/li>
&lt;li>Mobile [Software] Engineer/Developer&lt;/li>
&lt;li>Desktop [Software] Engineer/Developer&lt;/li>
&lt;/ul>
&lt;p>They all do the same thing, just in their respective specialization.&lt;/p>
&lt;p>So am I any of these? Well yes, I primarily specialize in the web, but with tools like React Native, Expo, and Electron, those lines are beginning to blur.&lt;/p>
&lt;h2 id="full-stack-vs-backend-vs-frontend">Full-Stack vs. Backend vs. Frontend&lt;/h2>
&lt;p>Unfortunately, we can go yet another layer deeper and specialize in the frontend, backend, or even both layers of an application, thus producing even more titles:&lt;/p>
&lt;ul>
&lt;li>Full-Stack [Web/Software] Engineer/Developer&lt;/li>
&lt;li>Backend [Web/Software] Engineer/Developer&lt;/li>
&lt;li>Frontend/UI [Web/Software] Engineer/Developer&lt;/li>
&lt;/ul>
&lt;p>These terms primarily get used in web environments (which is why I wrote &amp;ldquo;Web/Software&amp;rdquo;), but they could also apply to mobile and desktop.&lt;/p>
&lt;p>This one is easy for me. I work across the entire stack, so I must be a Full-Stack [Web/Software] Engineer/Developer?&lt;/p>
&lt;h2 id="okay-so-what-am-i">Okay, so what am I?&lt;/h2>
&lt;p>I am someone who designs and creates systems to solve problems on the web across both the frontend and backend. And thanks to cross-platform technologies, it has never been easier for me to take my web development skills and port them over to mobile and desktop environments. Based on this description, the best titles for me are:&lt;/p>
&lt;ul>
&lt;li>Engineer&lt;/li>
&lt;li>Developer&lt;/li>
&lt;li>Software Engineer&lt;/li>
&lt;li>Software Developer&lt;/li>
&lt;/ul>
&lt;p>All these titles encompass what I currently do and where my capabilities lie. I work across the stack, primarily in web environments, but depending on the technologies I could port those skills over to mobile and desktop. That said, I could see some ambiguity if I just used Engineer or Developer. So we are down to two:&lt;/p>
&lt;ul>
&lt;li>Software Engineer&lt;/li>
&lt;li>Software Developer&lt;/li>
&lt;/ul>
&lt;p>At this point, it just comes down to personal preference, so just because it sounds cooler&amp;hellip;&lt;/p>
&lt;p>I am a &lt;strong>Software Engineer&lt;/strong>!&lt;/p>
&lt;p>Here is a title breakdown:&lt;/p>
&lt;pre tabindex="0">&lt;code>- Programmer/Coder
+ [Software] Engineer/Developer
- Web [Software] Engineer/Developer
+ Full-Stack [Web/Software] Engineer/Developer
+ Backend [Web/Software] Engineer/Developer
+ Frontend/UI [Web/Software] Engineer/Developer
- Mobile [Software] Engineer/Developer
- Desktop [Software] Engineer/Developer
&lt;/code>&lt;/pre>&lt;h2 id="further-reading">Further Reading&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=2GonoPx9DgU">The Difference Between A Software Engineer And A Software Developer&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=ys7V9JSTE5A">What does &amp;ldquo;Senior&amp;rdquo; Software Engineer mean?&lt;/a>&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20What%20Am%20I%3f">Reply via email</a></description></item><item><title>New Computer Who Dis?</title><link>https://www.djpeacher.com/posts/new-computer-who-dis/</link><pubDate>Mon, 21 Feb 2022 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/new-computer-who-dis/</guid><description>
&lt;h2 id="install">Install&lt;/h2>
&lt;p>&lt;strong>Command Line Tools&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code>xcode-select --install
&lt;/code>&lt;/pre>&lt;p>&lt;strong>Oh My Zsh&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code>sh -c &amp;#34;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&amp;#34;
&lt;/code>&lt;/pre>&lt;p>&lt;strong>Homebrew&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code>/bin/bash -c &amp;#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&amp;#34;
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>&lt;a href="https://github.com/pyenv/pyenv/wiki">pyenv&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://formulae.brew.sh/formula/node#default">node&lt;/a> (need to consider a package manager)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Applications&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://code.visualstudio.com/download">Visual Studio Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.docker.com/get-docker/">Docker&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://apps.apple.com/us/app/1password-7-password-manager/id1333542190?mt=12">1Password&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://apps.apple.com/us/app/things-3/id904280696?mt=12">Things 3&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://bjango.com/mac/istatmenus/">iStat Menus 6&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://apps.apple.com/us/app/magnet/id441258766?mt=12">Magnet&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://apps.apple.com/us/app/bandwidth/id490461369?mt=12">Bandwidth+&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://apps.apple.com/us/app/progress-bar/id1441939775?mt=12">Progress&lt;/a>&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20New%20Computer%20Who%20Dis%3f">Reply via email</a></description></item><item><title>SWR 101 - React Hooks for Data Fetching</title><link>https://www.djpeacher.com/posts/swr-101/</link><pubDate>Fri, 10 Sep 2021 00:00:00 +0000</pubDate><guid>https://www.djpeacher.com/posts/swr-101/</guid><description>
&lt;p>&lt;a href="https://swr.vercel.app">SWR&lt;/a> is a fast, lightweight, and reusable data fetching package for React that uses the &lt;a href="https://tools.ietf.org/html/rfc5861">&lt;code>stale-while-revalidate&lt;/code>&lt;/a> caching strategy. By using this package, you can simplify and enhance your project&amp;rsquo;s data fetching logic.&lt;/p>
&lt;p>Normally, data is fetched in a top level component using &lt;code>useEffect&lt;/code> and passed down to its children using props. Additionally, logic must be added to handle errors and revalidation. This can lead to complicated and messy codebases, especially when data is needed throughout a large component tree. Enter, SWR.&lt;/p>
&lt;pre tabindex="0">&lt;code>npm install swr
&lt;/code>&lt;/pre>&lt;pre tabindex="0">&lt;code>import useSWR from &amp;#39;swr&amp;#39;;
const fetcher = (url) =&amp;gt; fetch(url).then((r) =&amp;gt; r.json());
function App() {
return (
&amp;lt;div&amp;gt;
&amp;lt;Welcome /&amp;gt;
&amp;lt;Avatar /&amp;gt;
&amp;lt;/div&amp;gt;
);
}
function Welcome() {
const { data, error } = useSWR(&amp;#39;/api/user&amp;#39;, fetcher);
if (error) return &amp;lt;div&amp;gt;error&amp;lt;/div&amp;gt;;
if (!data) return &amp;lt;div&amp;gt;loading&amp;lt;/div&amp;gt;;
return &amp;lt;div&amp;gt;Welcome {data.name}!&amp;lt;/div&amp;gt;;
}
function Avatar() {
const { data, error } = useSWR(&amp;#39;/api/user&amp;#39;, fetcher);
if (error) return &amp;lt;div&amp;gt;error&amp;lt;/div&amp;gt;;
if (!data) return &amp;lt;div&amp;gt;loading&amp;lt;/div&amp;gt;;
return &amp;lt;img src={data.avatar} alt={data.name} /&amp;gt;;
}
&lt;/code>&lt;/pre>&lt;p>In the example above, the &lt;a href="https://swr.vercel.app/docs/options">&lt;code>useSWR(key, fetcher, options)&lt;/code>&lt;/a> hook returns &lt;code>data&lt;/code> from cache (initially undefined), then sends a fetch request using &lt;code>fetcher&lt;/code> (a custom fetch function that can use any library you want), and finally updates &lt;code>data&lt;/code> and the cache from the response.&lt;/p>
&lt;p>The amazing thing here is that data is independent of the component tree (parents don&amp;rsquo;t need to know anything or pass props) and because &lt;code>useSWR&lt;/code> uses a &lt;code>key&lt;/code> to fetch data, it avoids duplicate requests by sharing the cached data across all components (only 1 request is made). Not to mention that it handles errors and revalidation out of the box!&lt;/p>
&lt;p>We can even make reusable hooks to make common fetch requests even easier to perform!&lt;/p>
&lt;pre tabindex="0">&lt;code>function useUser() {
const { data, error } = useSWR(`/api/user/`, fetcher);
return {
user: data,
isLoading: !error &amp;amp;&amp;amp; !data,
isError: error,
};
}
&lt;/code>&lt;/pre>&lt;p>&lt;a href="https://swr.vercel.app">SWR&lt;/a> is a powerful package that simplifies and streamlines your data fetching logic. I&amp;rsquo;ve only touched on the core aspects of the package. For further reading, check out the links below.&lt;/p>
&lt;h2 id="further-reading">Further Reading&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://swr.vercel.app/docs/options">Options&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/docs/global-configuration">Global Configuration&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/docs/error-handling">Error Handling&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/docs/revalidation">Revalidation&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://swr.vercel.app/docs/revalidation#revalidate-on-focus">onFocus&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/docs/revalidation#revalidate-on-interval">onInterval&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/docs/revalidation#revalidate-on-reconnect">onReconnect&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/docs/conditional-fetching">Conditional Data Fetching&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/docs/mutation">Mutation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/docs/pagination">Pagination&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://swr.vercel.app/examples/basic">Examples&lt;/a>&lt;/li>
&lt;/ul><a href="mailto: reply@djpeacher.com?subject=Re:%20SWR%20101%20-%20React%20Hooks%20for%20Data%20Fetching">Reply via email</a></description></item></channel></rss>