<?xml version="1.0" encoding="UTF-8" ?> <?xml-stylesheet type="text/xsl" href="rss.xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>PaulCutler.org</title><description>Paul Cutler&#39;s blog and homepage</description><link>https://paulcutler.org/</link><atom:link href="https://paulcutler.org/feed_rss_updated.xml" rel="self" type="application/rss+xml" /> <language>en</language> <pubDate>Fri, 02 Oct 2026 11:32:00 -0000</pubDate> <lastBuildDate>Fri, 02 Oct 2026 11:32:00 -0000</lastBuildDate> <ttl>1440</ttl> <generator>MkDocs RSS plugin - v1.19.0</generator> <image> <url>None</url> <title>PaulCutler.org</title> <link>https://paulcutler.org/</link> </image> <item> <title>HyperHDR Part 5 - LEDs</title> <author>Paul Cutler</author> <description>&lt;p&gt;If there is one part of this project that scares the hell out of me, it’s the LEDs. This is the part of the project I’ve researched the most about and still feel I know the least about.&lt;/p&gt; &lt;p&gt;The HyperHDR developers recommend the SK6812 5v LEDs over WS2812B, commonly known as Neopixels. The SK6812 LEDs are RGBW and add an extra white channel that Neopixels don;t have. &lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;5v 5 meter reel of SK6812 LEDs&#34; src=&#34;../../../2026-09-15-hyperhdr-part-5-leds/leds.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;My TV measures 39” tall x 76” or approximately or 99cm x 193cm, so I’m going to need just under 6 meters of LEDs. And of course they come in 5 meter reels.&lt;/p&gt; &lt;p&gt;So not only am I going to need to solder the microcontroller and the power to the LEDs, I now have to solder two LED strips together. I will also need to add a second power run and inject power either at the end or the middle of the LED strip to make sure the LEDs have enough power to stay bright. And let’s just say my soldering skills aren’t that great.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Closeup of 5vSK6812 LEDs&#34; src=&#34;../../../2026-09-15-hyperhdr-part-5-leds/led-closeup.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;This is where this topic being the most research of anything I’ve done yet. I’ve read articles and learn guides, I’ve watched YouTube videos, I even consulted with Claude. And I still don’t feel like I have all the information I need to start this part.&lt;/p&gt; &lt;p&gt;If I’ve understood everthing correctly:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;I’m going to install the 3 prong AC cable into the power supply.&lt;/li&gt; &lt;li&gt;I’m then going to cut the LED strip along the cut line. One YouTube video suggested sacrificing one of the LEDs so you solder together two half-pads, giving you a full pad and more space. (But if I do that, I need to keep track of how many LEDs I’m using in total, as HyperHDR wants to know the total number LEDs used. That includes adding the second strip as well.)&lt;/li&gt; &lt;li&gt;I solder power and ground from the power supply to the LED strips.&lt;/li&gt; &lt;li&gt;I solder the ground and data pads to the Adafruit Scorpio GPIO16 (HyperHDR’s Scorpio firmware comes with GPIO16 as the default) and the ground goes to the power supply and the data cable goes to the data in on the LED&lt;/li&gt; &lt;li&gt;I’m then going to cut the LED strip again and solder ground, data, and power.&lt;/li&gt; &lt;li&gt;Somewhere around the 3 meter mark I’m going to solder only power and ground from the power supply into the LED strip.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;I should have bought another Scorpio as backup for when I screw this up.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/09/15/hyperhdr-part-5---leds/</link> <pubDate>Tue, 15 Sep 2026 20:38:44 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/09/15/hyperhdr-part-5---leds/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/09/15/hyperhdr-part-5---leds.png" type="image/png" length="None" /> </item> <item> <title>HyperHDR Part 4 - Space Constraints</title> <author>Paul Cutler</author> <description>&lt;p&gt;One of the goals of the project is not to take my TV off the wall. It&#39;s a 75&#34; Sony, and you can see from the photo there is some space to work with:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Space behind the TV and the wall&#34; src=&#34;../../../2026-09-13-space/tv-space.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;I need to find a way to mount:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;HDMI Splitter&lt;/li&gt; &lt;li&gt;Ugreen USB Video Capture device&lt;/li&gt; &lt;li&gt;Raspberry Pi 5&lt;/li&gt; &lt;li&gt;300w power supply&lt;/li&gt; &lt;li&gt;Adafruit rp2040 Scorpio&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;..all without taking the TV down and hidden behind the TV. The power supply is the one giving me the most anxiety - I&#39;m concerned about heat and I&#39;m not quite sure how to mount it to the wall. There are DIN rails, but I have a feeling that&#39;s not what I want. I&#39;ve found 3D printed clips that screw into the wall / studs, or I&#39;m wondering if command velcro strips will work. I&#39;m also worried about getting the placement just right since I need two electrical runs to the SK6812 LEDs.&lt;/p&gt; &lt;p&gt;I am planning on using the command velcro strips on everything else, including the Pi, the Ugreen, etc. I&#39;ve printed a 3D case for the Scorpio, but I need to take another look at how it&#39;s wired and if it will work in that case. &lt;/p&gt; &lt;p&gt;Lastly, I need to mount a power strip behind the TV. If you look really close you can see there is apower outlet behind the TV next to a conduit run. Right now the TV and the plain white LED strip are plugged in. I&#39;ll have to figure out a way to wriggle my hands behind it to swap out what&#39;s plugged in. (More than likely it will be my wife&#39;s hands.)&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Close-up of the space behind the TV and the wall&#34; src=&#34;../../../2026-09-13-space/space-closeup.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;Next up: LEDs&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/09/13/hyperhdr-part-4---space-constraints/</link> <pubDate>Sat, 12 Sep 2026 15:57:46 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/09/13/hyperhdr-part-4---space-constraints/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/09/13/hyperhdr-part-4---space-constraints.png" type="image/png" length="None" /> </item> <item> <title>HyperHDR Part 3 - The HDMI Splitter</title> <author>Paul Cutler</author> <description>&lt;p&gt;One of the keys to the HyperHDR setup is the splitting the video source, usually HDMI, so it goes to both the display and to the USB video capture device which is plugged into a Raspberry Pi. The Raspberry Pi analyzes the colors and it runs HyperHDR to match those colors on the screen to the LEDs.&lt;/p&gt; &lt;p&gt;If you still haven&#39;t watched this 30 second video, it&#39;s a great example, especially with the flashlight:&lt;/p&gt; &lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/zztKsVhBu3k?si=PsvxEM3j2gv-RlC9&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt; &lt;p&gt;The key to splitting the HDMI signal is the abilty to bypass HDCP, high definition copy protection. Most game consoles come with HDCP off for streaming, but devices like a 4K Blu-Ray Players and the AppleTV require it. &lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;HBAVLink HB-S102B 2x1 HDMI Splitter&#34; src=&#34;../../../2026-09-12-hyperhdr-part3-hdmi/hbavlink-1.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;Luckily, they make HDMI splitters with HDCP bypass, but the trick is finding one that works with your system. The first one I ordered, the &lt;a href=&#34;https://www.amazon.com/dp/B08T62MKH1&#34;&gt;HBAVLink HB-S102B&lt;/a&gt; looked like it would work, and it split the HDMI signal successfully, but there was no HDR output when playing a 4K Blu-Ray. HDR is high dynamic range, and it&#39;s more important than the amount of pixels (4k) - it&#39;s what makes the colors pop, and the lights bright, and the color black a deep black. Luckily, I noticed that the colors looked washed out and, sure enough, there was no HDR.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;CORSAHD 2x1 HDMI Splitter&#34; src=&#34;../../../2026-09-12-hyperhdr-part3-hdmi/corsahd.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;That led to me swapping out HDMI cables, eventually buying 2 new 3&#39; cables just to make sure I had compatible ones. I emailed the company and while I waited to hear back from them, I bought the &lt;a href=&#34;https://www.amazon.com/dp/B0CLL5GQXT&#34;&gt;CORSAHD 4K@60Hz HDMI Splitter 1x2&lt;/a&gt;. That worked like a champ - HDR came right through.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;HBAVLink HB-S102H 2x1 HDMI Splitter&#34; src=&#34;../../../2026-09-12-hyperhdr-part3-hdmi/hbavlink-2.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;But then I heard back from HBAVLink right away and they shipped me an &lt;a href=&#34;https://www.amazon.com/HBAVLINK-HDMI-Splitter-Out-60Hz/dp/B0F6392X29/ref=sr_1_1&#34;&gt;upgraded model&lt;/a&gt; - and shipped it overnight! Now that&#39;s customer service, kudos to them for the quick responses via email and fast shipping. Plugged that one in and I&#39;m good to go. If you look at the Amazon page, you&#39;ll notice it doesn&#39;t explicitly say HDCP bypass, but it worked right away.&lt;/p&gt; &lt;p&gt;Next up: Space requirements&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/09/12/hyperhdr-part-3---the-hdmi-splitter/</link> <pubDate>Fri, 11 Sep 2026 19:20:46 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/09/12/hyperhdr-part-3---the-hdmi-splitter/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/09/12/hyperhdr-part-3---the-hdmi-splitter.png" type="image/png" length="None" /> </item> <item> <title>HyperHDR Part 2 - Bill of Materials</title> <author>Paul Cutler</author> <description>&lt;p&gt;Have you ever looked at a project in the Adafruit Learn Guide system and thought, &#34;That&#39;s way too expensive for a project, but it looks cool&#34;. This is one of those projects. Though 2 items make up almost a third of the cost, it does add up. And I&#39;m not including the Raspberry Pi 5 I just happened to have laying around not doing anything. But I was lucky and got an unexpected bonus at work, so here we go.&lt;/p&gt; &lt;p&gt;I&#39;ve spent the better part of the week researching all of these parts. When I decide to do a project like this, I go down the rabbit hole and try to learn as much as I can. I&#39;ll have more on that when it comes time to talk about the LEDs.&lt;/p&gt; &lt;p&gt;The total cost, excluding the Raspberry Pi, comes in at around $350. (Gulp). The USB capture card ($100) and 2 strips of LEDs ($88) make up the bulk of the cost.&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt; &lt;th style=&#34;text-align: center;&#34;&gt;&lt;strong&gt;Item&lt;/strong&gt;&lt;/th&gt; &lt;th style=&#34;text-align: center;&#34;&gt;&lt;strong&gt;Price&lt;/strong&gt;&lt;/th&gt; &lt;th style=&#34;text-align: center;&#34;&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/s?k=pi5+power+supply&#34;&gt;Pi5 power supply&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$15&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;I&#39;ve had a Pi 5 for a couple years, but just powered it off USB-C. Need to fix that.&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/UGREEN-Ultra-Low-Streaming-Recording-Compatible/dp/B0D4LV836Z&#34;&gt;USB capture card - UGREEN 4K@60Hz Video Capture Card&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$100&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;Recommended by the HyperHDR team, this makes up almost a third of the cost&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/BTF-LIGHTING-Individually-Addressable-Flexible-Waterproof/dp/B01N49DSC1&#34;&gt;LEDs - 60 LED 5v - BTF-LIGHTING RGB+Cool White SK6812&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$88 (2 × $44)&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;Of course I need roughly 6 meters and they come in 5 meter strips, so I&#39;ll need two. Again, almost a third of the total project cost. The RGBWs are recommended for the extra white channel, so choosing the Sk6812s over WS2812 Neopixels.&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/dp/B01D8FLZV6/?coliid=I29ZROWYJT4NN9&amp;amp;colid=TZRWREZEGB51&amp;amp;ref_=list_c_wl_lv_ov_lig_dp_it&amp;amp;th=1&#34;&gt;300w BTF-Lighting Power supply&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$33&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;Beefy, should be more than enough. I&#39;m worried about heat, but I&#39;ll mount it on the DIN rails below.&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/Pinfox-Universal-Appliance-Replacement-Pigtail/dp/B06XRKXLVV&#34;&gt;Pinfox 6ft 16 Gauge 3 Prong Power Supply Cord Cable&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$11&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/XHF-Adhesive-Management-Organizer-Ethernet/dp/B0C7TNXZQS&#34;&gt;Adhesive Cable Clips - 60 PCS XHF 5/8&#34; Clear Clips&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$7&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;Going to go with these instead of sticking the LEDs right onto the back of the TV.&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.mycablemart.com/store/cart.php?m=product_detail&amp;amp;p=9618&#34;&gt;3&#39; HDMI cables&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$18 (2 × $9)&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;Already bought, had to test the HDMI splitter.&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/s?k=din+rail+power+supply+mount&#34;&gt;Power supply mount / DIN rails&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$10&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/Charging-Compatible-Samsung-Galaxy-Devices-Black/dp/B0B4JGLTZW&#34;&gt;2 USB-C to USB-A cables - Hrbzo USB-C Cable 3-Pack&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$7&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;Can&#39;t believe I don&#39;t have a couple of these laying around.&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.amazon.com/dp/B0CLL5GQXT?ref=ppx_yo2ov_dt_b_fed_asin_title&#34;&gt;CORSAHD 4K@60Hz HDMI Splitter 1x2&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$30&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;More on this in the next blog post.&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td style=&#34;text-align: center;&#34;&gt;&lt;a href=&#34;https://www.adafruit.com/product/5650&#34;&gt;Adafruit rp2040 Scorpio&lt;/a&gt;&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;$15&lt;/td&gt; &lt;td style=&#34;text-align: center;&#34;&gt;Again, recommended by the HyperHDR team.&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;Next: The HDMI splitter&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/09/11/hyperhdr-part-2---bill-of-materials/</link> <pubDate>Thu, 10 Sep 2026 18:39:39 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/09/11/hyperhdr-part-2---bill-of-materials/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/09/11/hyperhdr-part-2---bill-of-materials.png" type="image/png" length="None" /> </item> <item> <title>HyperHDR Part 1 - An Introduction</title> <author>Paul Cutler</author> <description>&lt;p&gt;I was recently reminded of HyperHDR when they popped up in my GitHub feed after releasing version 22 last week. I had starred the project late last year when &lt;a href=&#34;https://learn.adafruit.com/ambient-video-lighting-with-hyperhdr&#34;&gt;Liz Clark of Adafruit wrote up a Learn Guide for installing HyperHDR&lt;/a&gt; using a Raspberry Pi 5 and a Trinkey.&lt;/p&gt; &lt;p&gt;HyperHDR is an ambient lighting system you install behind your TV and it uses a video capture device to match the LED colors to the colors playing on your television.&lt;/p&gt; &lt;p&gt;Check out this 30 second video which does a great job of showing HyperHDR in action:&lt;/p&gt; &lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/zztKsVhBu3k?si=PsvxEM3j2gv-RlC9&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt; &lt;p&gt;To contrast that, today I just have some white LEDs behind my television, this is what it looks like with the lights off:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Trinity and Neo from the Matrix&#34; src=&#34;../../../2026-09-07-hyperhdr-part1/tv-lights.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;I only have LEDs on the sides and the top, nothing along the bottom, which is something I’m looking to fix.&lt;/p&gt; &lt;p&gt;Over the next weeks I’ll be blogging my progress in installing HyperHDR behind my television.&lt;/p&gt; &lt;p&gt;Next up: Bill of Materials&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/09/10/hyperhdr-part-1---an-introduction/</link> <pubDate>Thu, 10 Sep 2026 17:01:18 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/09/10/hyperhdr-part-1---an-introduction/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/09/10/hyperhdr-part-1---an-introduction.png" type="image/png" length="None" /> </item> <item> <title>100 episodes!</title> <author>Paul Cutler</author> <description>&lt;p&gt;With the release of today&#39;s episodes of The CircuitPython Show and The Bootloader, it marks my 100th episode between the two shows. If you ever look into starting a podcast, most of the articles out there mention that the average podcaster doesn&#39;t make it past episode 7, and here we are four and a half years later.&lt;/p&gt; &lt;p&gt;I&#39;ve had the opportunity to interview a lot of great people, I&#39;ve made some friends, and made a good friend in my co-host Tod Kurt. I&#39;m grateful to everyone who has been a guest on an episode, listened to the show, or shared it with a friend.&lt;/p&gt; &lt;p&gt;Here&#39;s to many more episodes!&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/09/07/100-episodes/</link> <pubDate>Mon, 07 Sep 2026 13:19:43 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/09/07/100-episodes/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/09/07/100-episodes.png" type="image/png" length="None" /> </item> <item> <title>Moviebuff</title> <author>Paul Cutler</author> <description>&lt;p&gt;Moviebuff is a Django application originally written by &lt;a href=&#34;https://codeberg.org/retiolus/moviebuff&#34;&gt;retiolus&lt;/a&gt; that was released last August. I’m not sure how I originally came across it, but I remember it being demonstrated on Mastodon. Moviebuff allows you to track and rate the movies you watch and share them on the Fediverse.&lt;/p&gt; &lt;p&gt;I already have a &lt;a href=&#34;https://silversaucer.com&#34;&gt;music site&lt;/a&gt; and I’ve been tracking &lt;a href=&#34;https://www.paulcutler.org/blog/2026/04/01/what-im-watching---q1-2026/&#34;&gt;what I watch manually&lt;/a&gt;, so I was kind of excited for Moviebuff. I forked it right away, but there wasn’t any installation instructions and I’ve never used Django. I was able to reverse the first few things needed in the &lt;code&gt;.env&lt;/code&gt; file, such as the Django secret key, an API key from &lt;a href=&#34;https://www.themoviedb.org&#34;&gt;The Movie Database&lt;/a&gt;, but I couldn’t figure out how to get Federation working. The code to add Federation didn’t appear to be in the original repository or any of its branches.&lt;/p&gt; &lt;p&gt;So there it sat. I debated a few times about opening an issue in the Moviebuff repository asking for help, but I didn’t want to bug the maintainer and I was just grateful that anything had been shared and open sourced.&lt;/p&gt; &lt;p&gt;After successfully using Claude Code to help create the &lt;a href=&#34;https://github.com/prcutler/CircuitPython_bambulabs/&#34;&gt;circuitpython-bambulabs&lt;/a&gt; library, I pointed Claude at Moviebuff and away it went. It surprised me again when it was able to add Federation to the project based on the scaffolding that was already there, no database changes needed.&lt;/p&gt; &lt;p&gt;I’ve done a few projects in Pyramid and FastAPI, so Python based web frameworks weren’t totally new to me. Partnered with Claude, I went to work.&lt;/p&gt; &lt;p&gt;Things I’ve updated and changed:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Added dark mode and made it the default with a toggle to change light or dark mode on the home page&lt;/li&gt; &lt;li&gt;Added working Federation based on the buiding blocks present in the app&lt;/li&gt; &lt;li&gt;Changed Federation to only post when a movie is rated. Also added a Share to Fediverse button using HTMX that appears when a movie is added to the Watched Movies list&lt;/li&gt; &lt;li&gt;Added an environment variable and code to enable or disable registration (to avoid spammers)&lt;/li&gt; &lt;li&gt;Disabled the registration pages if the user is unauthenticated and registration is disabled&lt;/li&gt; &lt;li&gt;Updated the home page to show a legend, the five most recent movies rated, movie posters for the four most recent movies rated, and HTMX to page through the rated movies&lt;/li&gt; &lt;li&gt;In the Dashboard, added HTMX to page through all the Watched Movies&lt;/li&gt; &lt;li&gt;In each movie card, added the star ratings for the movie&lt;/li&gt; &lt;li&gt;Updated the datetime fields to be MON/DAY/YEAR and added commas to the movie&#39;s budget&lt;/li&gt; &lt;li&gt;Added a Font Awesome film icon in the upper left and made it and Moviebuff clickable to return to the home page&lt;/li&gt; &lt;li&gt;Added a custom footer that matches &lt;a href=&#34;https://silversaucer.com&#34;&gt;silversaucer.com&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Added a CONTRIBUTING.md and CODE_OF_CONDUCT.md&lt;/li&gt; &lt;li&gt;Added installation instructions in the README&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;You can see it in action at &lt;a href=&#34;https://moviebuff.silversaucer.com&#34;&gt;https://moviebuff.silversaucer.com&lt;/a&gt;. I’ve added most of the movies I watched in Feburary and March and I’m still debating about adding earlier movies.&lt;/p&gt; &lt;p&gt;The code &lt;a href=&#34;https://codeberg.org/prcutler/moviebuff&#34;&gt;lives at Codeberg&lt;/a&gt; and I’ve made a &lt;a href=&#34;https://github.com/prcutler/moviebuff&#34;&gt;synchronized mirror on GitHub&lt;/a&gt;. If you like it, give it a star! And if you really want to know what I’ve watched, you can follow me on Mastodon at &lt;code&gt;@prcutler@moviebuff.silversaucer.com&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;I’m so grateful to retiolus for creating the app and releasing it under an open source license. I’m looking forward to rating, cataloging, and sharing the movies I watch.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Moviebuff homepage screenshot&#34; src=&#34;../../../2026-04-07-moviebuff/moviebuff.png&#34; /&gt;&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/04/07/moviebuff/</link> <pubDate>Sat, 15 Aug 2026 23:35:05 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/04/07/moviebuff/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/04/07/moviebuff.png" type="image/png" length="None" /> </item> <item> <title>ListenBrainz All the Things</title> <author>Paul Cutler</author> <description>&lt;p&gt;In the last two episodes of &lt;a href=&#34;https://thebootloader.net&#34;&gt;The Bootloader&lt;/a&gt;, I&#39;ve covered &lt;a href=&#34;https://parachord.com&#34;&gt;Parachord&lt;/a&gt;, the cross-platform music player, and &lt;a href=&#34;https://achordion.xyz&#34;&gt;Achordion&lt;/a&gt;, it&#39;s accompanying music commuity. Achordion is built on top of &lt;a href=&#34;https://listenbrainz.org&#34;&gt;ListenBrainz&lt;/a&gt;, which, per its website, keeps track of the music you listen to and provides insights into your listening habits. Looking at various ListenBrainz tools, I ended up creating (or re-creating) my own, with some help from Claude.&lt;/p&gt; &lt;h2 id=&#34;listenbrainz-widget&#34;&gt;ListenBrainz Widget&lt;/h2&gt; &lt;p&gt;For the last few years, I&#39;ve maintained a &lt;a href=&#34;https://paulcutler.org/spinning&#34;&gt;Now Spinning&lt;/a&gt; page, which I update monthly with the records that are on the turntable. ListenBrainz offers a &lt;a href=&#34;https://listenbrainz.readthedocs.io/en/latest/users/widgets.html&#34;&gt;widget you can embed in a web page&lt;/a&gt;, but I wanted something a little more stylish to add to my Now Spinning page. That led me to create &lt;a href=&#34;https://github.com/prcutler/listenbrainz-widget&#34;&gt;&lt;code&gt;listenbrainz-widget&lt;/code&gt;&lt;/a&gt;. It includes two widgets, the first being a &#34;Now Playing&#34; widget that I&#39;ve embedded on my Now Spinning page that looks like this:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Now Playing widget featuring Metric&#34; src=&#34;../../../2026-06-13-listenbrainz-all-the-things/metric.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;Using GitHub Pages, you host the HTML there and then you can embed it on any web page.&lt;/p&gt; &lt;p&gt;The second widget is a &#34;Top 5 Albums&#34; image generator that you can then share on social media. It creates a 1200x1200 PNG of the top five albums you listened to based on the data range you choose.&lt;/p&gt; &lt;p&gt;I even created &lt;a href=&#34;https://prcutler.github.io/listenbrainz-widget/index.html&#34;&gt;a cool landing page&lt;/a&gt; for the widgets. The &lt;a href=&#34;https://github.com/prcutler/listenbrainz-widget&#34;&gt;README&lt;/a&gt; in the repository has all the details on how to install and run it.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Top 5 albums of the week&#34; src=&#34;../../../2026-06-13-listenbrainz-all-the-things/top5-preview.png&#34; /&gt;&lt;/p&gt; &lt;h2 id=&#34;listenbrainz-autoposter&#34;&gt;listenbrainz-autoposter&lt;/h2&gt; &lt;p&gt;Inspired by the &lt;a href=&#34;https://github.com/willmanduffy/scrobble-blue&#34;&gt;scrobble-blue&lt;/a&gt; project, &lt;a href=&#34;https://github.com/prcutler/listenbrainz-autoposter&#34;&gt;&lt;code&gt;listenbrainz-autoposter&lt;/code&gt;&lt;/a&gt; posts your top 5 album listens from the previous week to Bluesky and / or Mastodon. This uses GitHub Actions to automate the widget above instead of Cloudflare, like the original project.&lt;/p&gt; &lt;p&gt;I have it set to share my top every Tuesday, giving ListenBrainz enough time to process the previous week.&lt;/p&gt; &lt;h2 id=&#34;listenbrainz-to-bluesky&#34;&gt;listenbrainz-to-bluesky&lt;/h2&gt; &lt;p&gt;Another project inspired by someone else, this time it is again &lt;a href=&#34;https://github.com/willmanduffy/scrobble-blue&#34;&gt;scrobble-blue&lt;/a&gt; by &lt;a href=&#34;https://github.com/willmanduffy&#34;&gt;Will Manduffy&lt;/a&gt;. &lt;a href=&#34;https://github.com/prcutler/listenbrainz-to-bluesky&#34;&gt;listenbrainz-to-bluesky&lt;/a&gt; adds one line to your Bluesky profile with a &#34;Listening to&#34; and the name of the song and the artist of your most recent scrobble. This one is self-hosted and runs via a cron job every few minutes.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Paul&#39;s Bluesky Profile showing Listening to: Alright Hear This by Beastie Boys&#34; src=&#34;../../../2026-06-13-listenbrainz-all-the-things/lbtobsky.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;Too much oversharing? Probably, but I know a few folks have liked some of the music recommendations, so I&#39;m going to share for now.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/06/13/listenbrainz-all-the-things/</link> <pubDate>Sat, 13 Jun 2026 17:28:54 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/06/13/listenbrainz-all-the-things/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/06/13/listenbrainz-all-the-things.png" type="image/png" length="None" /> </item> <item> <title>Bambu Labs CircuitPython Library</title> <author>Paul Cutler</author> <description>&lt;p&gt;I follow a number of people on GitHub and one of them is my good friend &lt;a href=&#34;https://todbot.com&#34;&gt;todbot&lt;/a&gt;. I noticed in my feed he he had starred an Arduino program called &lt;a href=&#34;https://github.com/Keralots/BambuHelper&#34;&gt;Bambu Helper&lt;/a&gt; that displays information and statistics from your Bambu Labs printer, such as the percentage progress of the current print, nozzle temperature, fan speed, and more.&lt;/p&gt; &lt;p&gt;That got me thinking - if it can be done in Arduino, it can be done in CircuitPython. It&#39;s been a couple years since I had a good CircuitPython project, so away I went. I started researching the &lt;a href=&#34;https://bambutools.github.io/bambulabs_api/api.html&#34;&gt;Bambu API&lt;/a&gt; and more GitHub repositories than I can count, mostly Python projects that connect to a Bambu Printer using MQTT.&lt;/p&gt; &lt;p&gt;I then used &lt;a href=&#34;https://mqttx.app&#34;&gt;MQTTX&lt;/a&gt; to connect to my printer and test the various MQTT methods. I was able to successfully connect to my P1P printer both through Bambu Cloud and locally over my network.&lt;/p&gt; &lt;p&gt;And then I cheated - I used Claude to bootstrap the project by pointing it at the API Docs and the BambuHelper Arduino app to create a &lt;a href=&#34;https://github.com/prcutler/CircuitBambu&#34;&gt;proof of concept in CircuitPython&lt;/a&gt;. (I know, I know... the AI skeptic just used AI) It got pretty close - it did get the MQTT command to request a full status update wrong, but that was an easy fix.&lt;/p&gt; &lt;p&gt;I had the proof of concept working on my S3 Qualia board and 4&#34; display:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Bambu Labs info displayed on a 4&amp;quot; screen&#34; src=&#34;../../../2026-03-29-circuitpython-bambulabs-library/qualia.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;One thing I learned, though I&#39;m waiting to confirm, is that CircuitPython only uses MQTT 5.0, and not 3.1.1. Connecting via Bambu Cloud will connect on both MQTT standards, but the local connection only will connect using 3.1.1, which CircuitPython doesn&#39;t appear to use that I could figure out. That means you have a few extra hoops to jump through to get a token and user ID, but it wasn&#39;t that hard and I&#39;ve documented the process.&lt;/p&gt; &lt;p&gt;Unfortunately I appear to have fried both my S3 and S2 Reverse TFTs (thanks macOS) which I wanted to prototype with. Using a $50 worth of equipment is a bit much for a project like this. But that got me thinking - what if I could create a library so people could just get the info from the printer and then build their own UI on top of it to match their choice of microcontroller and screen?&lt;/p&gt; &lt;p&gt;So that&#39;s what I did next, by creating the &lt;a href=&#34;https://github.com/prcutler/CircuitPython_bambulabs/&#34;&gt;CircuitPython_bambulabs&lt;/a&gt; library. This is the first time I&#39;ve ever created a library and I&#39;m following along with both the &lt;a href=&#34;https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/overview&#34;&gt;Learn Guide&lt;/a&gt; and the &lt;a href=&#34;https://docs.circuitpython.org/en/stable/docs/design_guide.html&#34;&gt;design reference&lt;/a&gt;. Parts of the Learn Guide are outdate (hello Ruff), but overall it hasn&#39;t been bad, though I loathe writing reStructured Text and much prefer Markdown. At least the cookiecutter setup makes it easy to edit.&lt;/p&gt; &lt;p&gt;Assuming you&#39;ve got all the settings correct in &lt;code&gt;settings.toml&lt;/code&gt;, the library handles the MQTT setup and querying the printer to get the JSON response and breaking down that response into individual methods.&lt;/p&gt; &lt;p&gt;It is also possible to send commands to your printer, for example to set the bed temperature or turn the light on or off. I purposefully did not include commands, this library is only for viewing the various status messages available from the printer.&lt;/p&gt; &lt;p&gt;I created a &lt;a href=&#34;https://github.com/prcutler/CircuitPython_bambulabs/blob/main/examples/bambulabs_simpletest.py&#34;&gt;simpletest&lt;/a&gt; that connects to the printer and prints to serial a nicely formatted list of all the information returned from the printer and a raw dump of the JSON.&lt;/p&gt; &lt;p&gt;The GitHub Actions for the library are currently failing as it doesn&#39;t import the &lt;code&gt;bambulabs&lt;/code&gt; library or the &lt;code&gt;wifi&lt;/code&gt; module. I&#39;m not sure why yet and have asked for some help. If you want to test it out, you can &lt;a href=&#34;https://github.com/prcutler/CircuitPython_bambulabs/&#34;&gt;clone the repo&lt;/a&gt; and copy the &lt;code&gt;bambulabs.py&lt;/code&gt; file to your &lt;code&gt;/lib&lt;/code&gt; directory or the root directory of your CircuitPython microcontroller.&lt;/p&gt; &lt;p&gt;When the library is finally published and is available via &lt;code&gt;circup&lt;/code&gt;, I&#39;ll post an update to the blog. And if you have any feedback, please let me know by dropping me an email or leaving an issue or comment in the repository.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/03/29/bambu-labs-circuitpython-library/</link> <pubDate>Wed, 01 Apr 2026 15:44:13 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/03/29/bambu-labs-circuitpython-library/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/03/29/bambu-labs-circuitpython-library.png" type="image/png" length="None" /> </item> <item> <title>circuitpython-bambulabs Published!</title> <author>Paul Cutler</author> <description>&lt;p&gt;Following up on my &lt;a href=&#34;https://www.paulcutler.org/blog/2026/03/29/bambu-labs-circuitpython-library/&#34;&gt;blog post from yesterday&lt;/a&gt;, I spent Sunday preparing the &lt;code&gt;circuitpython-bambulabs&lt;/code&gt; library to be published.&lt;/p&gt; &lt;p&gt;Unfortunately, I ran into an issue with the CI where it was failing to build. Thanks to some pointers from todbot, I was able to refactor the library and move the MQTT setup into the &lt;code&gt;BambuPrinter&lt;/code&gt; class. This makes me happy as the user doesn&#39;t have to set up MQTT, assuming they&#39;ve entered their printer settings in &lt;code&gt;settings.toml&lt;/code&gt;, the library handles connecting for you.&lt;/p&gt; &lt;p&gt;I also heard from Brent Rubell at Adafruit who confirmed that the MQTT library for CircuitPython uses MQTT 3.1.1 - which is odd, because while MQTTX would connect locally using 3.1.1 (and not 5.0), CircuitPython would not connect locally. I&#39;ll investigate this more as I&#39;m guessing some people would rather connect locally instead of through Bambu Cloud.&lt;/p&gt; &lt;p&gt;Other things I learned:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;The &lt;a href=&#34;https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/creating-a-library&#34;&gt;Creating and sharing a CircuitPython library Learn Guide&lt;/a&gt; needs some updates. The process to create docs on ReadtheDocs has changed, libraries use &lt;code&gt;ruff&lt;/code&gt; now for linting instead of &lt;code&gt;Pylint&lt;/code&gt; and &lt;code&gt;black&lt;/code&gt;, and sharing in the Library bundle needs some updates.&lt;/li&gt; &lt;li&gt;The Learn Guide doesn&#39;t cover publishing to PyPi, though the &lt;code&gt;cookiecutter&lt;/code&gt; template does include the GitHub Actions to publish the library. It took me longer than it probably shoudl to figure out how to enter the secrets in GitHub to allow it to build and upload. This would be helpful to add to the Learn Guide.&lt;/li&gt; &lt;li&gt;The section in the Learn Guide on Sharing in a Bundle also needs an update. The section on verifying didn&#39;t work for me, though Updating the Library list did.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Lastly, a big thank you to Foamyguy who merged my pull request to add the library to the Community Libraries right away! You can find more about the library by:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Install via &lt;code&gt;circup&lt;/code&gt;: &lt;code&gt;circup install bambulabs&lt;/code&gt; to install on your microcontroller. (Just tested it and it&#39;s so cool to see that work!)&lt;/li&gt; &lt;li&gt;&lt;a href=&#34;https://pypi.org/project/circuitpython-bambulabs/&#34;&gt;PyPi&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&#34;https://circuitpython-bambulabs.readthedocs.io/en/latest/&#34;&gt;ReadtheDocs&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&#34;https://github.com/prcutler/CircuitPython_bambulabs&#34;&gt;GitHub repository&lt;/a&gt; (And if you like it, give it a ⭐️)&lt;/li&gt; &lt;/ul&gt;</description> <link>https://paulcutler.org/blog/2026/03/30/circuitpython-bambulabs-published/</link> <pubDate>Wed, 01 Apr 2026 15:44:13 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/03/30/circuitpython-bambulabs-published/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/03/30/circuitpython-bambulabs-published.png" type="image/png" length="38183" /> </item> <item> <title>What I&#39;m Watching - Q1 2026</title> <author>Paul Cutler</author> <description>&lt;p&gt;It&#39;s a new year and I&#39;m still tracking what I&#39;m watching. I have a number of television shows still in progress. I don&#39;t write them down until the season I&#39;m watching has finished.&lt;/p&gt; &lt;p&gt;Highlights include finally getting around to For All Mankind season 4 with season five having just started. I highly recommend this show if you haven&#39;t seen it. I also enjoyed &lt;em&gt;Predator: Badlands&lt;/em&gt;, &lt;em&gt;Have Fun, Don&#39;t Die, Good Luck&lt;/em&gt;, and &lt;em&gt;The Rip&lt;/em&gt;. Whatever you do, don&#39;t watch &lt;em&gt;Mercy&lt;/em&gt;. I still need to catch up on some of the Oscar nominees.&lt;/p&gt; &lt;h2 id=&#34;legend&#34;&gt;Legend&lt;/h2&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;The Criterion Channel = *&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;4K UHD = +&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;AppleTV+ = ^&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Netflix = ~&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&#34;january&#34;&gt;January&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Predator: Badlands&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Boogie Nights&lt;/strong&gt;+ (1997)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;The Rip&lt;/strong&gt;~ (2026)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Dust Bunny&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;Landman S2&lt;/li&gt; &lt;li&gt;What Lies Beneath~ (2000)&lt;/li&gt; &lt;li&gt;For All Mankind S4^&lt;/li&gt; &lt;li&gt;&lt;strong&gt;If I Had Legs, I’d Kick You&lt;/strong&gt; (2025)&lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&#34;february&#34;&gt;February&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;Loot S3^&lt;/li&gt; &lt;li&gt;&lt;strong&gt;The Wrecking Crew&lt;/strong&gt; (2026)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;The Ballad of Wallis Island&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;The Assessment&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;The Lazarus Project S2&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Is This Thing On?&lt;/strong&gt; (2026)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Mercy&lt;/strong&gt; (2026)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;The Housemaid&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Eternity&lt;/strong&gt; (2026)^&lt;/li&gt; &lt;li&gt;Industry S1&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Honey Don’t&lt;/strong&gt; (2025)~&lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&#34;march&#34;&gt;March&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Crazy Stupid Love&lt;/strong&gt; (2011)~&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Mission: Impossible 2&lt;/strong&gt; (2001)+&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Dracula&lt;/strong&gt; (2027)&lt;/li&gt; &lt;li&gt;A Man on the Inside S2~&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Good Luck, Have Fun, Don’t Die&lt;/strong&gt; (2027)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Pacific Rim&lt;/strong&gt; (2013)+&lt;/li&gt; &lt;li&gt;Firefly (2001)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Serenity&lt;/strong&gt; (2005)+&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Send Help&lt;/strong&gt; (2026)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;How to Make a Killing&lt;/strong&gt; (2026)&lt;/li&gt; &lt;/ul&gt;</description> <link>https://paulcutler.org/blog/2026/04/01/what-im-watching---q1-2026/</link> <pubDate>Wed, 01 Apr 2026 15:39:12 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/04/01/what-im-watching---q1-2026/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/04/01/what-im-watching---q1-2026.png" type="image/png" length="None" /> </item> <item> <title>What I&#39;m Watching - Q4 2025</title> <author>Paul Cutler</author> <description>&lt;p&gt;Wrapping up the year, here&#39;s everything I watched in October, November, and December, excluding sports. Because there is a lot of football that isn&#39;t accounted for, which is why my total watchlist is down. Well, that and a bit of gaming took out a chunk in November and I was traveling half of December.&lt;/p&gt; &lt;p&gt;I plan on continuing to track my watchlist monthly, though I&#39;m unsure if I&#39;ll keep blogging it. A small part of me is tempted to do what Steven Soderbergh does, and track &lt;strong&gt;everything&lt;/strong&gt;. But I probably really don&#39;t want to know how much time I spend watching baseball and football.&lt;/p&gt; &lt;p&gt;Highlights include K-Pop Demon Hunters, Peacemaker S2, Task, Wake Up Dead Man, One Battle After Another, and Pluribus. I was disappointed with A House of Dynamite, The Roses, Relay, and Good Fortune.&lt;/p&gt; &lt;p&gt;Legend:&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;The Criterion Channel = *&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;4K UHD = +&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;AppleTV+ = ^&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Netflix = ~&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&#34;october&#34;&gt;October&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;Peacemaker S2&lt;/li&gt; &lt;li&gt;&lt;strong&gt;K-Pop Demon Hunters&lt;/strong&gt;~&lt;/li&gt; &lt;li&gt;Task&lt;/li&gt; &lt;li&gt;&lt;strong&gt;The Parallax View&lt;/strong&gt; (1974)*&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Punch Drunk Love&lt;/strong&gt; (2002)*&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Mission: Impossible&lt;/strong&gt; (1996)+&lt;/li&gt; &lt;li&gt;&lt;strong&gt;A House of Dynamite&lt;/strong&gt; (2025)~&lt;/li&gt; &lt;li&gt;Platonic S2&lt;/li&gt; &lt;li&gt;&lt;strong&gt;The Roses&lt;/strong&gt; (2025)&lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&#34;november&#34;&gt;November&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;The Toxic Avenger&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;Chad Powers S1&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Panic Room&lt;/strong&gt; (2002)*&lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&#34;december&#34;&gt;December&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;Tron: Ares&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;A Man on the Inside (2024) S1~&lt;/li&gt; &lt;li&gt;Parish (2024)~&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Fatman&lt;/strong&gt; (2020)~&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Good Fortune&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;One Battle After Another&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Relay&lt;/strong&gt; (2024)&lt;/li&gt; &lt;li&gt;Mayor of Kingstown S1&lt;/li&gt; &lt;li&gt;&lt;strong&gt;The Running Man&lt;/strong&gt; (2025)&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Wake Up, Dead Man&lt;/strong&gt; (2025)~&lt;/li&gt; &lt;li&gt;&lt;strong&gt;F1&lt;/strong&gt;&lt;/li&gt; &lt;li&gt;Pluribus (S1)&lt;/li&gt; &lt;li&gt;The Copenhagen Test S1&lt;/li&gt; &lt;li&gt;&lt;strong&gt;Dogma&lt;/strong&gt; (2000)+&lt;/li&gt; &lt;/ul&gt;</description> <link>https://paulcutler.org/blog/2026/01/01/what-im-watching---q4-2025/</link> <pubDate>Wed, 01 Apr 2026 15:25:13 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2026/01/01/what-im-watching---q4-2025/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2026/01/01/what-im-watching---q4-2025.png" type="image/png" length="None" /> </item> <item> <title>Wiring Up Play Singles</title> <author>Paul Cutler</author> <description>&lt;p&gt;Earlier this year when I finished cataloging all my records in Discogs, I then put each one into a “folder”, a way for Discogs to separate different kinds of records. It’s setup by the user, so I created a folder for each type of music I have, and the number is the ID that Discogs assigned to each one:&lt;/p&gt; &lt;p&gt;all_folder = 0 lp_folder = 2162484 twelve_inch_folder = 2198941 ten_inch_folder = 2162486 seven_inch_folder = 2162483 cd_folder = 2162488 tape_folder = 2162487 digital_folder = 2198943&lt;/p&gt; &lt;p&gt;CD, Digital and Tape don’t currently have any items in them, but I hope to at least catalog my CD collection some day, but that’s a different story.&lt;/p&gt; &lt;p&gt;In the music controller, the method to get an album’s information looks like this:&lt;/p&gt; &lt;p&gt;@view_config(route_name=&#34;play&#34;, renderer=&#34;silversaucer:templates/play/play.pt&#34;) def play(_):&lt;/p&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;album_release_id = RandomRecordService.get_folder_count(2162484) release_data = RandomRecordService.get_album_data(album_release_id) return {&#34;release_info&#34;: release_data} &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Here I’ve hard coded the folder ID (2162484) from Discogs in the method as I only want to use the folder that has only full albums (no singles, 45s, etc.).&lt;/p&gt; &lt;p&gt;Now it’s time to wire up the code to play a single from one of three folders:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;7” or 45&lt;/li&gt; &lt;li&gt;10” EPs&lt;/li&gt; &lt;li&gt;12” singles / remixes&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;It looks similar to the method above, but I’ve added a random method to pick one of the three folders for me and pass the folder ID to the play service that I’ve already written for full albums:&lt;/p&gt; &lt;p&gt;def play_single(_):&lt;/p&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;random_folder = randint(0, 2) if random_folder == 0: single = 2162483 elif random_folder == 1: single = 2162486 else: single = 2198941 album_release_id = RandomRecordService.get_folder_count(single) print(album_release_id) release_data = RandomRecordService.get_album_data(album_release_id) print(release_data) return {&#34;release_info&#34;: release_data} &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;This is where I appear to have outsmarted myself. I thought I had blogged about this, but apparently not. At one point I had refactored the first play method above - I had added the play service to accept the folder ID and pass that. I was pretty proud of myself because after the refactoring, I figured this would work for exactly what I’m trying to do now - pass the folder ID of other folders. But no, it wasn’t meant to be as it errors out:&lt;/p&gt; &lt;p&gt;File &#34;/Users/prcutler/workspace/silversaucer/silversaucer/controllers/music_controller.py&#34;, line 35, in play_single release_data = RandomRecordService.get_album_data(album_release_id) File &#34;/Users/prcutler/workspace/silversaucer/silversaucer/services/play_service.py&#34;, line 178, in get_album_data discogs_main_id = release_json[&#34;master_id&#34;] KeyError: &#39;master_id&#39;&lt;/p&gt; &lt;p&gt;There is no &lt;code&gt;master_id&lt;/code&gt; returned because singles don’t have a master release! Only full albums have a master release to track all the regional releases and special pressings.&lt;/p&gt; &lt;p&gt;I need to think through this and decide if I’m going to write a new method in &lt;code&gt;play_service&lt;/code&gt; to handle getting back the release information for the single or if I should write an &lt;code&gt;if&lt;/code&gt; statement to account for the Key Error and return a different dictionary without the &lt;code&gt;master_id&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;I was so proud of myself for doing something I thought was Pythonic, too!&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2021/01/01/wiring-up-play-singles/</link> <pubDate>Wed, 01 Apr 2026 11:56:02 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2021/01/01/wiring-up-play-singles/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2021/01/01/wiring-up-play-singles.png" type="image/png" length="None" /> </item> <item> <title>Play Singles Part 2</title> <author>Paul Cutler</author> <description>&lt;p&gt;I was talking to my wife yesterday about my progress and she agreed that I should update my existing method used for albums to also work for singles - and that I shouldn’t just re-use that method and to avoid code duplication. It’s good to get confirmation that I’m on the right path.&lt;/p&gt; &lt;p&gt;But I’m stuck on how to do this.&lt;/p&gt; &lt;p&gt;If we look at the route to play a full album:&lt;/p&gt; &lt;div class=&#34;language-python highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-0-1&#34;&gt;&lt;a id=&#34;__codelineno-0-1&#34; name=&#34;__codelineno-0-1&#34; href=&#34;#__codelineno-0-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;nd&#34;&gt;@view_config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;route_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&#34;play&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;renderer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&#34;silversaucer:templates/play/play.pt&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-0-2&#34;&gt;&lt;a id=&#34;__codelineno-0-2&#34; name=&#34;__codelineno-0-2&#34; href=&#34;#__codelineno-0-2&#34;&gt;&lt;/a&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;play&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-0-3&#34;&gt;&lt;a id=&#34;__codelineno-0-3&#34; name=&#34;__codelineno-0-3&#34; href=&#34;#__codelineno-0-3&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-0-4&#34;&gt;&lt;a id=&#34;__codelineno-0-4&#34; name=&#34;__codelineno-0-4&#34; href=&#34;#__codelineno-0-4&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;album_release_id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RandomRecordService&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_folder_count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2162484&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-0-5&#34;&gt;&lt;a id=&#34;__codelineno-0-5&#34; name=&#34;__codelineno-0-5&#34; href=&#34;#__codelineno-0-5&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;release_data&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RandomRecordService&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_album_data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;album_release_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-0-6&#34;&gt;&lt;a id=&#34;__codelineno-0-6&#34; name=&#34;__codelineno-0-6&#34; href=&#34;#__codelineno-0-6&#34;&gt;&lt;/a&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&#34;release_info&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;release_data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Here I’m manually passing the folder ID for my Discogs Folder that contains full albums (2162484). That ID is passed to a method that gets a count of how many items are in that folder to pick one at random. Once that happens, it passes the release ID of the pick to get the album’s information.&lt;/p&gt; &lt;p&gt;Works great for full albums. As mentioned in my last blog post, it bombs if it’s a single, because there’s no concept of a main (or “master”) release associated with EPs and singles.&lt;/p&gt; &lt;p&gt;So how do I refactor my code to account for this?&lt;/p&gt; &lt;p&gt;I started by updating the route to play a full album, adding a &lt;code&gt;folder&lt;/code&gt; parameter to the &lt;code&gt;get_folder_count&lt;/code&gt; method instead of passing the folder number manually:&lt;/p&gt; &lt;div class=&#34;language-python highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-1-1&#34;&gt;&lt;a id=&#34;__codelineno-1-1&#34; name=&#34;__codelineno-1-1&#34; href=&#34;#__codelineno-1-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;nd&#34;&gt;@view_config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;route_name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&#34;play&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;renderer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&#34;silversaucer:templates/play/play.pt&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-1-2&#34;&gt;&lt;a id=&#34;__codelineno-1-2&#34; name=&#34;__codelineno-1-2&#34; href=&#34;#__codelineno-1-2&#34;&gt;&lt;/a&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;play&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-1-3&#34;&gt;&lt;a id=&#34;__codelineno-1-3&#34; name=&#34;__codelineno-1-3&#34; href=&#34;#__codelineno-1-3&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-1-4&#34;&gt;&lt;a id=&#34;__codelineno-1-4&#34; name=&#34;__codelineno-1-4&#34; href=&#34;#__codelineno-1-4&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;folder&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2162484&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-1-5&#34;&gt;&lt;a id=&#34;__codelineno-1-5&#34; name=&#34;__codelineno-1-5&#34; href=&#34;#__codelineno-1-5&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;album_release_id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RandomRecordService&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_folder_count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;folder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-1-6&#34;&gt;&lt;a id=&#34;__codelineno-1-6&#34; name=&#34;__codelineno-1-6&#34; href=&#34;#__codelineno-1-6&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;release_data&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RandomRecordService&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_album_data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;folder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;album_release_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-1-7&#34;&gt;&lt;a id=&#34;__codelineno-1-7&#34; name=&#34;__codelineno-1-7&#34; href=&#34;#__codelineno-1-7&#34;&gt;&lt;/a&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&#34;release_info&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;release_data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;And I tell that method to now return both the release ID and the folder:&lt;/p&gt; &lt;div class=&#34;language-python highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-2-1&#34;&gt;&lt;a id=&#34;__codelineno-2-1&#34; name=&#34;__codelineno-2-1&#34; href=&#34;#__codelineno-2-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;get_folder_count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;folder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-2&#34;&gt;&lt;a id=&#34;__codelineno-2-2&#34; name=&#34;__codelineno-2-2&#34; href=&#34;#__codelineno-2-2&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-3&#34;&gt;&lt;a id=&#34;__codelineno-2-3&#34; name=&#34;__codelineno-2-3&#34; href=&#34;#__codelineno-2-3&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;discogs_api&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;folder_url&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&#34;?=&#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;api_token&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-4&#34;&gt;&lt;a id=&#34;__codelineno-2-4&#34; name=&#34;__codelineno-2-4&#34; href=&#34;#__codelineno-2-4&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-5&#34;&gt;&lt;a id=&#34;__codelineno-2-5&#34; name=&#34;__codelineno-2-5&#34; href=&#34;#__codelineno-2-5&#34;&gt;&lt;/a&gt; &lt;span class=&#34;c1&#34;&gt;# TODO Add an if statement to check for a 200 or 404 response code and redirect on 404 to error page&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-6&#34;&gt;&lt;a id=&#34;__codelineno-2-6&#34; name=&#34;__codelineno-2-6&#34; href=&#34;#__codelineno-2-6&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-7&#34;&gt;&lt;a id=&#34;__codelineno-2-7&#34; name=&#34;__codelineno-2-7&#34; href=&#34;#__codelineno-2-7&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;requests&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;discogs_api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-8&#34;&gt;&lt;a id=&#34;__codelineno-2-8&#34; name=&#34;__codelineno-2-8&#34; href=&#34;#__codelineno-2-8&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-9&#34;&gt;&lt;a id=&#34;__codelineno-2-9&#34; name=&#34;__codelineno-2-9&#34; href=&#34;#__codelineno-2-9&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;record_json&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-2-10&#34;&gt;&lt;a id=&#34;__codelineno-2-10&#34; name=&#34;__codelineno-2-10&#34; href=&#34;#__codelineno-2-10&#34;&gt;&lt;/a&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;random_album_release_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;folder&lt;/span&gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Keeping with my goal of only using one method and passing the folder to it, I added an &lt;code&gt;if&lt;/code&gt; statement to check if it’s the folder with full albums. If it is, get the data I know is working. If not, don’t get the data that’s not included in an EP or single.&lt;/p&gt; &lt;div class=&#34;language-python highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-3-1&#34;&gt;&lt;a id=&#34;__codelineno-3-1&#34; name=&#34;__codelineno-3-1&#34; href=&#34;#__codelineno-3-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;nd&#34;&gt;@staticmethod&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-2&#34;&gt;&lt;a id=&#34;__codelineno-3-2&#34; name=&#34;__codelineno-3-2&#34; href=&#34;#__codelineno-3-2&#34;&gt;&lt;/a&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;get_album_data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;folder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;album_release_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-3&#34;&gt;&lt;a id=&#34;__codelineno-3-3&#34; name=&#34;__codelineno-3-3&#34; href=&#34;#__codelineno-3-3&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-4&#34;&gt;&lt;a id=&#34;__codelineno-3-4&#34; name=&#34;__codelineno-3-4&#34; href=&#34;#__codelineno-3-4&#34;&gt;&lt;/a&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;folder&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2162484&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-5&#34;&gt;&lt;a id=&#34;__codelineno-3-5&#34; name=&#34;__codelineno-3-5&#34; href=&#34;#__codelineno-3-5&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-6&#34;&gt;&lt;a id=&#34;__codelineno-3-6&#34; name=&#34;__codelineno-3-6&#34; href=&#34;#__codelineno-3-6&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;release_api&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-7&#34;&gt;&lt;a id=&#34;__codelineno-3-7&#34; name=&#34;__codelineno-3-7&#34; href=&#34;#__codelineno-3-7&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;discogs_url&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-8&#34;&gt;&lt;a id=&#34;__codelineno-3-8&#34; name=&#34;__codelineno-3-8&#34; href=&#34;#__codelineno-3-8&#34;&gt;&lt;/a&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&#34;releases/&#34;&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-9&#34;&gt;&lt;a id=&#34;__codelineno-3-9&#34; name=&#34;__codelineno-3-9&#34; href=&#34;#__codelineno-3-9&#34;&gt;&lt;/a&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;album_release_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-10&#34;&gt;&lt;a id=&#34;__codelineno-3-10&#34; name=&#34;__codelineno-3-10&#34; href=&#34;#__codelineno-3-10&#34;&gt;&lt;/a&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&#34;?&#34;&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-11&#34;&gt;&lt;a id=&#34;__codelineno-3-11&#34; name=&#34;__codelineno-3-11&#34; href=&#34;#__codelineno-3-11&#34;&gt;&lt;/a&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;discogs_user_token&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-12&#34;&gt;&lt;a id=&#34;__codelineno-3-12&#34; name=&#34;__codelineno-3-12&#34; href=&#34;#__codelineno-3-12&#34;&gt;&lt;/a&gt; &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-13&#34;&gt;&lt;a id=&#34;__codelineno-3-13&#34; name=&#34;__codelineno-3-13&#34; href=&#34;#__codelineno-3-13&#34;&gt;&lt;/a&gt; &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;release_api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-14&#34;&gt;&lt;a id=&#34;__codelineno-3-14&#34; name=&#34;__codelineno-3-14&#34; href=&#34;#__codelineno-3-14&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;requests&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;release_api&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-3-15&#34;&gt;&lt;a id=&#34;__codelineno-3-15&#34; name=&#34;__codelineno-3-15&#34; href=&#34;#__codelineno-3-15&#34;&gt;&lt;/a&gt; &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;But now my method to get the release information on an album broke, when it was working before:&lt;/p&gt; &lt;div class=&#34;language-python highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-4-1&#34;&gt;&lt;a id=&#34;__codelineno-4-1&#34; name=&#34;__codelineno-4-1&#34; href=&#34;#__codelineno-4-1&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;File&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&#34;/Users/prcutler/workspace/silversaucer/silversaucer/controllers/music_controller.py&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;line&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;play&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-4-2&#34;&gt;&lt;a id=&#34;__codelineno-4-2&#34; name=&#34;__codelineno-4-2&#34; href=&#34;#__codelineno-4-2&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;release_data&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RandomRecordService&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_album_data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;folder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;album_release_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-4-3&#34;&gt;&lt;a id=&#34;__codelineno-4-3&#34; name=&#34;__codelineno-4-3&#34; href=&#34;#__codelineno-4-3&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;File&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&#34;/Users/prcutler/workspace/silversaucer/silversaucer/services/play_service.py&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;line&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;114&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;get_album_data&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-4-4&#34;&gt;&lt;a id=&#34;__codelineno-4-4&#34; name=&#34;__codelineno-4-4&#34; href=&#34;#__codelineno-4-4&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;release_uri&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;release_json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&#34;uri&#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-4-5&#34;&gt;&lt;a id=&#34;__codelineno-4-5&#34; name=&#34;__codelineno-4-5&#34; href=&#34;#__codelineno-4-5&#34;&gt;&lt;/a&gt;&lt;span class=&#34;ne&#34;&gt;KeyError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&#39;uri&#39;&lt;/span&gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Digging into it with another print statement, I see that the value for &lt;code&gt;album_release_id&lt;/code&gt; is now returning the folder ID correctly, but `album_release_id is returning a tuple:&lt;/p&gt; &lt;p&gt;```Folder ID = 2162486 Album = (3200538, 2162486) &lt;class &#39;tuple&#39;&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-5-1&#34;&gt;&lt;a id=&#34;__codelineno-5-1&#34; name=&#34;__codelineno-5-1&#34; href=&#34;#__codelineno-5-1&#34;&gt;&lt;/a&gt;Which is why the API call below is screwed up: &lt;/span&gt;&lt;span id=&#34;__span-5-2&#34;&gt;&lt;a id=&#34;__codelineno-5-2&#34; name=&#34;__codelineno-5-2&#34; href=&#34;#__codelineno-5-2&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-5-3&#34;&gt;&lt;a id=&#34;__codelineno-5-3&#34; name=&#34;__codelineno-5-3&#34; href=&#34;#__codelineno-5-3&#34;&gt;&lt;/a&gt;``` python &lt;/span&gt;&lt;span id=&#34;__span-5-4&#34;&gt;&lt;a id=&#34;__codelineno-5-4&#34; name=&#34;__codelineno-5-4&#34; href=&#34;#__codelineno-5-4&#34;&gt;&lt;/a&gt; File &#34;/Users/prcutler/workspace/silversaucer/silversaucer/services/play_service.py&#34;, line 114, in get_album_data &lt;/span&gt;&lt;span id=&#34;__span-5-5&#34;&gt;&lt;a id=&#34;__codelineno-5-5&#34; name=&#34;__codelineno-5-5&#34; href=&#34;#__codelineno-5-5&#34;&gt;&lt;/a&gt; release_uri = release_json[&#34;uri&#34;] &lt;/span&gt;&lt;span id=&#34;__span-5-6&#34;&gt;&lt;a id=&#34;__codelineno-5-6&#34; name=&#34;__codelineno-5-6&#34; href=&#34;#__codelineno-5-6&#34;&gt;&lt;/a&gt;KeyError: &#39;uri&#39; &lt;/span&gt;&lt;span id=&#34;__span-5-7&#34;&gt;&lt;a id=&#34;__codelineno-5-7&#34; name=&#34;__codelineno-5-7&#34; href=&#34;#__codelineno-5-7&#34;&gt;&lt;/a&gt;https://api.discogs.com/releases/(1366279, 2198941)?&amp;amp;token= &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt; &lt;p&gt;But it’s a tuple! I can work with that. The &lt;code&gt;folder&lt;/code&gt; number is always returned as the second part of the tuple and the &lt;code&gt;album_release_id&lt;/code&gt; is always the first index. So let’s update the &lt;code&gt;release_api&lt;/code&gt; first to use the first index:&lt;/p&gt; &lt;div class=&#34;language-python highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-6-1&#34;&gt;&lt;a id=&#34;__codelineno-6-1&#34; name=&#34;__codelineno-6-1&#34; href=&#34;#__codelineno-6-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;n&#34;&gt;release_api&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-6-2&#34;&gt;&lt;a id=&#34;__codelineno-6-2&#34; name=&#34;__codelineno-6-2&#34; href=&#34;#__codelineno-6-2&#34;&gt;&lt;/a&gt; &lt;span class=&#34;n&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;discogs_url&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-6-3&#34;&gt;&lt;a id=&#34;__codelineno-6-3&#34; name=&#34;__codelineno-6-3&#34; href=&#34;#__codelineno-6-3&#34;&gt;&lt;/a&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&#34;releases/&#34;&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-6-4&#34;&gt;&lt;a id=&#34;__codelineno-6-4&#34; name=&#34;__codelineno-6-4&#34; href=&#34;#__codelineno-6-4&#34;&gt;&lt;/a&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;album_release_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-6-5&#34;&gt;&lt;a id=&#34;__codelineno-6-5&#34; name=&#34;__codelineno-6-5&#34; href=&#34;#__codelineno-6-5&#34;&gt;&lt;/a&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&#34;?&#34;&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-6-6&#34;&gt;&lt;a id=&#34;__codelineno-6-6&#34; name=&#34;__codelineno-6-6&#34; href=&#34;#__codelineno-6-6&#34;&gt;&lt;/a&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;discogs_user_token&lt;/span&gt; &lt;/span&gt;&lt;span id=&#34;__span-6-7&#34;&gt;&lt;a id=&#34;__codelineno-6-7&#34; name=&#34;__codelineno-6-7&#34; href=&#34;#__codelineno-6-7&#34;&gt;&lt;/a&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;And now everything works! I updated the &lt;code&gt;else&lt;/code&gt; statement to go get the data I need and not include the values that were returning a &lt;code&gt;KeyError&lt;/code&gt; and now with one method - that includes an &lt;code&gt;if / else&lt;/code&gt; statement, I can get back the release information for any record in my collection, no matter what folder it’s in. If and when I add other media - especially CDs or tapes that have a main release, I’ll need to update the &lt;code&gt;if&lt;/code&gt; statement, but future me can deal with that. I don’t even have that stuff cataloged yet. (But I made sure to add a &lt;code&gt;TODO&lt;/code&gt; to my code so I don’t forget.&lt;/p&gt; &lt;p&gt;And here’s a screenshot of a random 10” EP that was returned. Yay!&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;The Decemberists - Long LIve the King&#34; src=&#34;../../../2021-01-02-play-singles-part-2/decemberists-ep.png&#34; /&gt;&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2021/01/02/play-singles-part-2/</link> <pubDate>Wed, 01 Apr 2026 11:56:02 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2021/01/02/play-singles-part-2/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2021/01/02/play-singles-part-2.png" type="image/png" length="None" /> </item> <item> <title>June 2022 SilverSaucer Update</title> <author>Paul Cutler</author> <description>&lt;p&gt;As usual, I haven’t blogged my progress on SilverSaucer and I’ve had some major progress &lt;a href=&#34;https://paulcutler.org/posts/2022/01/silver-saucer-progress-january-2022/&#34;&gt;since January&lt;/a&gt;. &lt;/p&gt; &lt;p&gt;First, I did follow-up on that blog post by switching from a MatrixPortal with a 64x64 LED to a PyPortal Titano.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Liz Phair&#39;s self titled album displayed on a PyPortal&#34; src=&#34;../../../2022-06-17-silversaucer-update/lizphair-pyportal.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;In FastAPI, I did two things. I added a service that uses the &lt;code&gt;Pillow&lt;/code&gt; library to convert the image from the Discogs image URL to a small bitmap that the PyPortal can use.&lt;/p&gt; &lt;p&gt;The second thing is that sends a message using the MQTT protocol. The PyPortal is watching for that message, and when received displays the Bitmap like above. &lt;/p&gt; &lt;p&gt;async def get_discogs_image(release_image_url): image_dl = requests.get(release_image_url, stream=True).raw download = Image.open(image_dl) download.save(&#39;static/img/album-art/image_600.jpg&#39;)&lt;/p&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;img = Image.open(&#39;static/img/album-art/image_600.jpg&#39;) img.quantize(colors=16, method=2) smol_img = img.resize((320, 320)) convert = smol_img.convert(mode=&#34;P&#34;, palette=Image.WEB) convert.save(&#39;static/img/album-art/image_300.bmp&#39;) &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;async def publish_image(image_url): client = mqtt.Client() client.username_pw_set(config.mqtt_user, config.mqtt_pw) client.connect(&#34;mqtt.silversaucer.com&#34;, 1883) client.publish(&#34;albumart&#34;, &#34;Ping!&#34;) client.disconnect()&lt;/p&gt; &lt;p&gt;That was a pretty big breakthrough to get hardware working with the website back in April or May.&lt;/p&gt; &lt;p&gt;I then spent early last week playing with a command-line tool called &lt;code&gt;discotools&lt;/code&gt;. I help with a little of the documentation in the &lt;code&gt;python3-discogs-client&lt;/code&gt; library and one of the co-maintainers wrote &lt;code&gt;discodos&lt;/code&gt; for DJs to catalog their collection. It also has the ability to match Discogs Release ID to MusicBrainz ID and using it I was able to get about half (or 400) of my MusicBrainz IDs assigned into the database. (Which is a different story about doing unnatural things with tables).&lt;/p&gt; &lt;p&gt;Then in the last week or so, everything has started to come together. Last week I was struggling with understanding the different kind of objects database queries could return. I was having a hard time understanding and differentiating between &lt;code&gt;scalar, scalars, one_or_none, and lists&lt;/code&gt;. That finally clicked and in a couple of days I had re-written the random results page to mostly use the database and the page load time went from about 10 seconds down to about 2 seconds, a huge improvement. After the initial database import, which takes forever as Discogs rate limits you to one request per second, the &lt;code&gt;play-album&lt;/code&gt; page uses the database for all information except the genres and tracklist, which are still called using the Discogs API. It turns out that the Discogs API is very slow when querying an object in your personal collection. If you do a general query on a public release object, it’s way faster. The combination of those two things led to the speed improvement.&lt;/p&gt; &lt;p&gt;From there I was able to quickly re-factor the play-single method to match the play-album, so I can randomly return an EP or single to play. Understanding the database query, I also added a list result to the admin page showing a list of all releases that don’t have an associated MusicBrainz ID. Last night in just hours I created the template with form and the get and post methods to manually enter the ID and store it in the database. &lt;/p&gt; &lt;p&gt;I also wrote a method that for the existing database entries that have the MusicBrainz ID, to query the MusicBrainz API and get the release date for that album. It worked, but the dates the API has stored include the year, year and month, and year, month and date, which is what I really want. But just like that, over half the work to build the “On this Day” feature has been done and I just need to manipulate the dates above to get the one I want.&lt;/p&gt; &lt;p&gt;The finish line is in site.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2022/06/17/june-2022-silversaucer-update/</link> <pubDate>Wed, 01 Apr 2026 11:56:02 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2022/06/17/june-2022-silversaucer-update/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2022/06/17/june-2022-silversaucer-update.png" type="image/png" length="None" /> </item> <item> <title>SilverSaucer completed!</title> <author>Paul Cutler</author> <description>&lt;p&gt;Just like that, I crossed the finish line and SilverSaucer.com is finished. I wasn’t planning on finishing it so quick after my blog post, but on Friday it just clicked and the “On this day” feature was completed in just a few hours.&lt;/p&gt; &lt;p&gt;I always thought the “On this Day” feature was a pipe dream. I had no idea how I was going to tie a Discogs release to MusicBrainz and then import that date. But after discovering the discodos app a few weeks ago and working with the database having clicked last week, I was able to finish it up after discovering a key feature: the LIKE operator in SQL and SQLAlchemy. &lt;/p&gt; &lt;p&gt;``` today = pendulum.today(tz=&#34;America/Chicago&#34;) print(&#34;Today: &#34;, today, today.month, today.day)&lt;/p&gt; &lt;p&gt;if today.month &amp;lt; 10: search = &#34;0&#34; + str(today.month) + &#34;-&#34; + str(today.day) else: search = str(today.month) + &#34;-&#34; + str(today.day)&lt;/p&gt; &lt;p&gt;async with db_session.create_async_session() as session: query = ( select(Album) .filter(Album.mb_release_date.like(“%” + search)) .order_by(Album.mb_release_date) ) print(query)&lt;/p&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;results = await session.execute(query) query_results = results.scalars() &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;The Pendulum library comes to the rescue again as it is still my favorite way to work with datetime objects.&lt;/p&gt; &lt;p&gt;This returns a list of rows from the database where the results are today’s date, not including the year. (There is one bug related to 6/18 I can’t track down where it displays a different result - but not both - when an order_by is added to the query).&lt;/p&gt; &lt;p&gt;I’m pretty happy with how everything has turned out. I’ll probably do a couple more blog posts about the project, including reviewing the project goals.&lt;/p&gt; &lt;p&gt;It feels weird to be done. Not necessarily elation, not relief, just weird.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2022/06/19/silversaucer-completed/</link> <pubDate>Wed, 01 Apr 2026 11:56:02 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2022/06/19/silversaucer-completed/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2022/06/19/silversaucer-completed.png" type="image/png" length="None" /> </item> <item> <title>Talk Python Training - Consuming HTTP Services in Python Review</title> <author>Paul Cutler</author> <description>&lt;p&gt;Summary / tl;dr: Consuming HTTP Services in Python is a great addition to the training courses from Talk Python and Michael Kennedy. You&#39;ll come away with a thorough knowledge of the best way to get data from the internet using the requests module; you&#39;ll use real world examples and APIs from Basecamp, Github and a custom API Michael built just from the course; Michael will explain and show the concepts in an easy to learn manner with a little humor and recap each concept to make sure you understand.&lt;/p&gt; &lt;p&gt;In addition to being host of the well known Talk Python podcast, Michael Kennedy has also created a number of Python training courses. The first, &lt;a href=&#34;https://training.talkpython.fm/courses/details/python-language-jumpstart-building-10-apps&#34;&gt;Python Jumpstart by Building 10 Apps&lt;/a&gt;, &lt;a href=&#34;https://www.kickstarter.com/projects/mikeckennedy/python-jumpstart-by-building-10-apps-video-course&#34;&gt;launched its Kickstarter&lt;/a&gt; exactly a year ago this month, and was quickly followed later in the year with &lt;a href=&#34;https://training.talkpython.fm/courses/details/python-for-entrepreneurs-build-and-launch-your-online-business&#34;&gt;Python for Entrepreneurs&lt;/a&gt; on &lt;a href=&#34;https://www.kickstarter.com/projects/mikeckennedy/python-for-entrepreneurs-video-course?ref=profile_created&#34;&gt;Kickstarter&lt;/a&gt; and &lt;a href=&#34;https://training.talkpython.fm/courses/explore_pythonic_code/write-pythonic-code-like-a-seasoned-developer&#34;&gt;Write Pythonic Code Like a Seasoned Developer.&lt;/a&gt;&lt;/p&gt; &lt;p&gt;I &lt;a href=&#34;http://paulcutler.org/blog/2016/10/next-class-up-python-jumpstart-by-building-10-apps/&#34;&gt;started&lt;/a&gt; and finished Python Jumpstart by Building 10 Apps late last year and loved it. It was a very different &lt;a href=&#34;http://paulcutler.org/blog/2016/09/python-for-everybody-at-coursera-with-dr-chuck/&#34;&gt;learning experience than the University of Michigan&#39;s Python for Everybody class on Coursera&lt;/a&gt;. There is an assumption with the Talk Python training courses that you have some basic understanding of computer science or programming. I don&#39;t, so I typically go a little slower and take my time with the courses.&lt;/p&gt; &lt;p&gt;Looking back. there are a few things I liked about the Jumpstart by Building 10 Apps course and I was glad to see continue in this latest course:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Michael makes it very easy to follow along in the beginning of the courses. Everyone learns differently, but one of the ways I learn best is to follow along by typing the code as he does in the video, helping me commit it to memory.&lt;/li&gt; &lt;li&gt;After teaching you a core concept and coding it into one of the apps, Michael recaps what you&#39;ve just learned in its own &#34;Concept&#34; video. This summarizes the concept you just put into practice and reinforces what you&#39;ve learned.&lt;/li&gt; &lt;li&gt;Compared to some of the other online courses I&#39;ve taken, I really like that I know I&#39;m learning from someone well known in the community and I believe I&#39;m not just learning how to code, but coding best practices. I don&#39;t know if I&#39;m explaining this right, but as an example: A few of the online classes I&#39;ve taken haven&#39;t had me put the code into functions and then call them in a main(): function, for example.&lt;/li&gt; &lt;li&gt;The source code to the examples Michael teaches you is on Github. You can download it, star it, fork it ; but it&#39;s available if you want to follow along, code along as the course goes, or just save it for reference for the future.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;I&#39;ve shared my enthusiasm for the Talk Python training courses here and on Twitter and when Michael reached out to me last week asking if I was interested in having a sneak peek at his latest course, &lt;a href=&#34;https://training.talkpython.fm/courses/explore_http_reset_client_course/consuming-http-and-soap-services-in-python-with-json-xml-and-screen-scraping&#34;&gt;Consuming HTTP Services in Python&lt;/a&gt;, I jumped at it (after making sure he knew I was still a novice early in my Python learning curve). I took a look at the course overview and this is right in my wheelhouse of what I need to learn. A core part of the app I want to build is exactly what this course is about ; using the requests module to download at least a half dozen JSON feeds and then building my app around that. (My app is to build the scoring for a custom &lt;a href=&#34;http://mlbpool2.com/rules/nfl-pool-rules/&#34;&gt;NFL Pool league&lt;/a&gt; ; it&#39;s not a fantasy league, it&#39;s different. All of the data comes from &lt;a href=&#34;https://www.mysportsfeeds.com/&#34;&gt;MySportsFeeds,&lt;/a&gt; who provides sports data via JSON or XML which I will consume, store in a database, and then write a Python program to calculate the league and player scores to be displayed on the &lt;a href=&#34;https://nflpool.xyz&#34;&gt;league website&lt;/a&gt;.)&lt;/p&gt; &lt;p&gt;What I really liked about this course was that it was focused on one thing: consuming services. I&#39;ve taken a few different Python courses online as I try and learn Python, and most are throwing all the basics that you need to know ; everything you&#39;d expect in a beginner course, but it does get overwhelming. This was the first course I&#39;ve taken that was focused on getting you really good at one thing, and in a few different ways that you might need to do it.&lt;/p&gt; &lt;p&gt;Immediately, I learn something new. I only knew of requests from I learned using Google and Stack Overflow. When I started playing around and putting together the building blocks of my app, I wrote the following code. MySportsFeed currently using HTTP Basic Authentication, so I have a separate file called secret.py that stores my username and password ; I may be new to Python, but I&#39;m smart enough to have created that, import it and add it to my .gitignore file!&lt;/p&gt; &lt;p&gt;This code polls the Playoff Team Standings feed on MySportsFeeds and then I have &lt;a href=&#34;https://github.com/prcutler/nflpool/blob/master/team_rank_request.py&#34;&gt;some (ugly) Python code&lt;/a&gt; that runs a for loop to rank each of the two NFL Conferences teams from 1 to 16.&lt;/p&gt; &lt;p&gt;It&#39;s not a lot, it&#39;s just one line of code, but it&#39;s these little things. I had no idea the power of requests ; this is just one specific example of something I learned from this course. Another thing I learned? I should be taking the URL in the above eample, create a base_url variable and then append the feed name as another variable. This is covered in a later chapter of the course ; &lt;a href=&#34;https://training.talkpython.fm/player/start_chapter/3007&#34;&gt;Consuming RESTful HTTP services.&lt;/a&gt; This chapter has a ton of great examples I&#39;m going to be referencing when writing my app and using.&lt;/p&gt; &lt;p&gt;The Consuming RESTful HTTP services chapter is where the course really starts to take off. I ran into this with the Jumpstart course as well ; Michael does a great job in teaching you the building blocks and then the course seems to go from 0-60. This is where having previous programming experience is helpful as that jump from learning what each puzzle piece does to how you put the puzzle together clicks. For someone like me, without any programming experience, it&#39;s a big jump, but possible.&lt;/p&gt; &lt;p&gt;With that said, this chapter is fantastic. While I had a cursory knowledge of HTTP commands like GET and PUT, the API Michael built for the course is awesome. You have the opportunity to create your own examples and interact with the API and blog explorer app ; this isn&#39;t something you see with most online courses out there.&lt;/p&gt; &lt;p&gt;I also learned that I only want to use requests, and not built-ins. Though I do now have an understanding of the urlib built-in for Python 3.x if I&#39;m ever cornered and have to use it.&lt;/p&gt; &lt;p&gt;I will admit to skipping the chapter on SOAP. I&#39;m a hobbyist, not an enterprise developer who may encounter SOAP. But it&#39;s great this available for those who may need it as part of this course. This, combined with learning how to use JSON, XML, and screen scraping makes it a complete course.&lt;/p&gt; &lt;p&gt;The last chapter is on screen scraping. There are a ton of of tutorials and classes available on the web about screen scraping. I&#39;ve taken a few of them ; one of the challenges I have with my app is figuring out the playoff seeding and I thought about scraping NFL.com, but that&#39;s a different story. This chapter kicks off with an example of using a site&#39;s sitemap.xml ; an example I&#39;ve never seen before that makes so much sense once you learn about it. And if a website you want to scrape doesn&#39;t have a sitemap.xml, shame on them for not being search engine friendly. But if they don&#39;t, Michael goes through other ways to scrape a website using Beautiful Soup and does it in the most Pythonic way I&#39;ve seen yet in a course.&lt;/p&gt; &lt;p&gt;I enjoyed Consuming HTTP Services in Python. With the requests module and JSON being a cornerstone of the app I hope to write, it was great to learn about everything I need to know to make that happen. Michael&#39;s delivery is conversational and he makes it easy to follow along and do the code examples with him, if you choose to. If you have programming experience or are coming from a different language, the videos themselves will probably teach you what you need to do in Python. If you&#39;re like me, a complete novice to Python, you&#39;ll be able to follow along, but be prepared for the jump the course will make in the Consuming RESTful HTTP Services chapter ; this moves pretty quickly, but if you&#39;ve forked the Github repo you&#39;ll have access to the program Michael has written and you can (and should) write your own examples to interact with the API on the blog explorer. For $39, you&#39;re getting a well developed course from someone well known in the Python community teaching you the Pythonic way interact with services. While other online training sites might have &#34;sales&#34; that are cheaper, as someone new to Python who has taken some of those courses, trust me ; the Talk Python courses are well worth the money.&lt;/p&gt; &lt;p&gt;I&#39;m still early in my Python journey and the two courses I&#39;ve finished from Talk Python have been the best learning resources I&#39;ve used out of all the books and training I&#39;ve purchased (and it&#39;s a lot). I&#39;m still working my way through Python for Entrepreneurs and am really &lt;a href=&#34;https://training.talkpython.fm/courses/all&#34;&gt;looking forward to two of the upcoming courses using SQLAlchemy&lt;/a&gt; as this database stuff is way over my head right now. Thanks again to Michael for allowing me to have a preview of the Consuming HTTP Services course ; now it&#39;s time for me to take his advice from the last chapter of the course and write some code ; the best way to actually learn.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2017/02/23/talk-python-training---consuming-http-services-in-python-review/</link> <pubDate>Tue, 31 Mar 2026 20:14:09 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2017/02/23/talk-python-training---consuming-http-services-in-python-review/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2017/02/23/talk-python-training---consuming-http-services-in-python-review.png" type="image/png" length="None" /> </item> <item> <title>Introducing MLBPool2</title> <author>Paul Cutler</author> <description>&lt;p&gt;After learning Python and creating &lt;a href=&#34;https://nflpool.xyz&#34;&gt;NFLPool&lt;/a&gt;, it was time for another project. This time it was building the site for MLBPool2, which inspired NFLPool. MLBPool was the brain child of former commissioner Jason Theros who created the league and rules.  Sadly, MLBPool came to an end after the 2011 season. The original site was written in ASP and none of the code was available and for the last few years after my friend resurrected the league he did almost everything by hand. I had created a Google spreadsheet / form to get all of the player’s pool picks, but scoring was a manual process and he was only able to do it a handful of times throughout the season. I had a WordPress site but as I wasn’t playing in MLBPool2, I never really updated it. (&lt;a href=&#34;http://mlbpool2.com/&#34;&gt;It’s still up&lt;/a&gt; for another week or so ; and if you’re curious, you can look at the &lt;a href=&#34;http://mlbpool2.com/rules/mlbpool2-rules/&#34;&gt;rules on how to play&lt;/a&gt;).&lt;/p&gt; &lt;p&gt;That all changes with MLBPool2. Like NFLPool, the app is written in Python (3.6) and &lt;a href=&#34;https://trypyramid.com&#34;&gt;Pyramid&lt;/a&gt;. I debated about starting from scratch or just modifying NFLPool and opted for the latter. I’ve been hip deep in development for the last two months and the finish line is almost in sight. I should have been writing about the development more, but that takes away from my coding time. (Shame on me!)&lt;/p&gt; &lt;p&gt;The major difference from NFLPool is that players have the ability to change up to 14 of their picks at baseball’s All-Star Break. This was much more complex than I thought it would be and I realized if I was going to do it, I might as well add more and more functionality to make it easier for the player. This included:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Players can change their picks before the season starts without penalty&lt;/li&gt; &lt;li&gt;When changing a pick, the drop down menu defaults to the original pick a player made&lt;/li&gt; &lt;li&gt;I added a column when changing a pick that shows if the original pick was unique or not&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;The hardest part was all of the &lt;code&gt;datetime&lt;/code&gt; calculations around changing picks. If the season hadn’t started, let the user change their picks; if the season has started, redirect the user that it’s too late to change a pick; if it’s during the All-Star Break, let them change their pick and then have the system make that pick worth half the points; and if it’s after the All-Star Break, redirect them again that it’s too late to change anything.&lt;/p&gt; &lt;p&gt;For whatever reason, I have a hard time working with Python’s &lt;code&gt;datetime&lt;/code&gt; module. I had planned to use Kenneth Reitz’s &lt;a href=&#34;https://github.com/kennethreitz/maya&#34;&gt;Maya ; Datetime for Humans,&lt;/a&gt; but unfortunately the documentation is offline. I ended up going with the &lt;a href=&#34;https://pendulum.eustace.io/&#34;&gt;Pendulum&lt;/a&gt; module, which has been fantastic to work with and has &lt;em&gt;excellent&lt;/em&gt; documentation. (It’s so good I emailed the developer a couple weeks ago with a thank you note). I even created a service just to deal with the date and time manipulations, rather than have Pendulum instances throughout the code. A great side benefit is that it makes testing so much easier.&lt;/p&gt; &lt;p&gt;As you can see in the code above, I can just create one instance for testing and change the date to before the season starts, the All-Star Break or after the break. This also fixes an issue I had with NFLPool where I did not do the &lt;code&gt;datetime&lt;/code&gt; manipulation correctly because of timezone differences with my web server and a user was locked out of submitting picks before the deadline. This worked out so well I even added an alert to the page where you submit picks showing how much time is left until picks are due:&lt;/p&gt; &lt;p&gt;There are two major pieces of functionality that need to be finished. There are two complex SQL queries. One to update the unique picks and one to calculate the scoring. I couldn’t figure out how to do this in SQLAlchemy and my wife Kelly wrote direct SQL statements in the code. I was able to re-write the first one to calculate unique picks after the season started but haven’t figured out how to update it for after the All-Star Break. I don’t have the patience to learn SQL right now, so she is going to help me with those when she’s on spring break from the University of Minnesota next week. From there, it will be time for deployment ; and just in time, as players will have about ten days from deployment to when picks are due and the Major League Baseball season starts.&lt;/p&gt; &lt;p&gt;&lt;a href=&#34;https://trypyramid.com&#34;&gt;Pyramid&lt;/a&gt; is just a joy to work with and I’m so thankful for the &lt;a href=&#34;https://training.talkpython.fm/courses/explore_entrepreneurs/python-for-entrepreneurs-build-and-launch-your-online-business&#34;&gt;Talk Python course&lt;/a&gt; that taught me to use it. (I wish Pyramid had 20% of mindshare that Flask does. Maybe it does where it matters, but there is just so much on the web about Flask that it feels like it doesn’t).&lt;/p&gt; &lt;p&gt;The best part about writing MLBPool2 though is my confidence level in coding in Python has increased greatly. I’m doing things in MLBPool2 that I didn’t do in NFLPool ; from manipulating datetimes, string manipulation, a lot more if / else statements, Slack integration, and more advanced Chameleon templates. I’m sure there are lot of areas that are still not Pythonic enough, but I feel more confident and I know the learning won’t stop. I’ll try and write some more blog posts about what I’ve learned and how MLBPool2 differs from NFLPool (and what I want to add back into NFLPool.)&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2018/03/07/introducing-mlbpool2/</link> <pubDate>Tue, 31 Mar 2026 20:14:09 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2018/03/07/introducing-mlbpool2/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2018/03/07/introducing-mlbpool2.png" type="image/png" length="None" /> </item> <item> <title>MLBPool2 &amp; MySQL / MariaDB</title> <author>Paul Cutler</author> <description>&lt;p&gt;When I wrote yesterday introducing MLBPool2, I buried the lede. One of the biggest changes between NFLPool and MLBPool2 is the fact I’m now using MariaDB and MySQL as the backend instead of SQLite, which NFLPool uses. (I did look at PostgreSQL since so many Python developers seem to prefer it, but I’ve never been able to get a PostgreSQL server up and running on Linux or Mac. My sysadmin skills are nonexistent.)&lt;/p&gt; &lt;p&gt;Since I’m using SQLAlchemy for 90% of the SQL interactions, setting it up was pretty easy, I just needed to make sure when creating the tables I added things like string length where needed. A basic example that shows the difference between the two is the table that stores the division information. In football, it’s the NFC East, AFC North etc, and in baseball it’s the AL East, NL Central, etc.&lt;/p&gt; &lt;p&gt;In NFLPool it was:&lt;/p&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-0-1&#34;&gt;&lt;a id=&#34;__codelineno-0-1&#34; name=&#34;__codelineno-0-1&#34; href=&#34;#__codelineno-0-1&#34;&gt;&lt;/a&gt;class DivisionInfo(SqlAlchemyBase): &lt;/span&gt;&lt;span id=&#34;__span-0-2&#34;&gt;&lt;a id=&#34;__codelineno-0-2&#34; name=&#34;__codelineno-0-2&#34; href=&#34;#__codelineno-0-2&#34;&gt;&lt;/a&gt; __tablename__ = &#39;DivisionInfo&#39; &lt;/span&gt;&lt;span id=&#34;__span-0-3&#34;&gt;&lt;a id=&#34;__codelineno-0-3&#34; name=&#34;__codelineno-0-3&#34; href=&#34;#__codelineno-0-3&#34;&gt;&lt;/a&gt; division_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) &lt;/span&gt;&lt;span id=&#34;__span-0-4&#34;&gt;&lt;a id=&#34;__codelineno-0-4&#34; name=&#34;__codelineno-0-4&#34; href=&#34;#__codelineno-0-4&#34;&gt;&lt;/a&gt; division = sqlalchemy.Column(sqlalchemy.String) &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;And in MLBPool2:&lt;/p&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-1-1&#34;&gt;&lt;a id=&#34;__codelineno-1-1&#34; name=&#34;__codelineno-1-1&#34; href=&#34;#__codelineno-1-1&#34;&gt;&lt;/a&gt;class DivisionInfo(SqlAlchemyBase): &lt;/span&gt;&lt;span id=&#34;__span-1-2&#34;&gt;&lt;a id=&#34;__codelineno-1-2&#34; name=&#34;__codelineno-1-2&#34; href=&#34;#__codelineno-1-2&#34;&gt;&lt;/a&gt; __tablename__ = &#39;DivisionInfo&#39; &lt;/span&gt;&lt;span id=&#34;__span-1-3&#34;&gt;&lt;a id=&#34;__codelineno-1-3&#34; name=&#34;__codelineno-1-3&#34; href=&#34;#__codelineno-1-3&#34;&gt;&lt;/a&gt; division_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) &lt;/span&gt;&lt;span id=&#34;__span-1-4&#34;&gt;&lt;a id=&#34;__codelineno-1-4&#34; name=&#34;__codelineno-1-4&#34; href=&#34;#__codelineno-1-4&#34;&gt;&lt;/a&gt; division = sqlalchemy.Column(sqlalchemy.String(8)) &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Easy enough. But since SQLite is a persistent database, I learned the hard way that I need to close each session in MySQL with a &lt;code&gt;session.close()&lt;/code&gt; statement or I see lots of fun errors like this:&lt;/p&gt; &lt;p&gt;&lt;code&gt;OperationalError: (pymysql.err.OperationalError) (1040, &#39;Too many connections&#39;) (Background on this error at: http://sqlalche.me/e/e3q8)&lt;/code&gt;&lt;/p&gt; &lt;p&gt;It’s taken a lot of trial and error figuring out where I need these. I’ve learned they have to go before any &lt;code&gt;return&lt;/code&gt; statements and even when I think I have them in all the right places, it turns out I don’t. Yesterday I was entering all of the picks for everyone who played in 2017 to do some testing (to see if the app’s results and scores match what was done by hand) and after entering six player’s picks, I ran into it again. Sure enough, in the PlayerPicks service, I didn’t have any &lt;code&gt;session.close()&lt;/code&gt; statements when I returned all of the lists that make up the picks. I had just added &lt;a href=&#34;https://www.rollbar.com&#34;&gt;Rollbar&lt;/a&gt; functionality to the site to keep track of errors and I was pleasantly surprised to learn that when you connect Rollbar to your Github repo, it automatically opens an issue for you on Github with the error. (Pretty cool, Rollbar!)&lt;/p&gt; &lt;p&gt;I’m still a little worried that after I deploy and the site has been up for a while that the “Too many connections” error is going to happen.&lt;/p&gt; &lt;p&gt;The other thing I forget to share was a link to the &lt;a href=&#34;https://github.com/prcutler/mlbpool2&#34;&gt;Github repo for MLBPool2&lt;/a&gt;. It’s open source under the MIT X11 license. I originally had NFLPool under the GPL but changed it to MIT as well. I liked the idea of it being under the GPL in case anyone ever used it and I could have access to the changes, but let’s be honest, the chances that anyone is going to use the codebase is slim to none and I’d rather be more permissive (and I have issues with the Free Software Foundation, but no need to get into that.) The key takeaway is I’m a big believer in open source and I think making it more permissive is the right thing to do.&lt;/p&gt; &lt;p&gt;I’m undecided if I’m going to port NFLPool to MySQL. I think it’s probably a better option, but the few how-to’s I’ve read give me pause on how to import the data from SQLite to MySQL. I’m not sure if it’s worth the effort considering all of the features I want to back port and / or add to NFLPool. (But that’s a different discussion for a different blog post).&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2018/03/08/mlbpool2--mysql--mariadb/</link> <pubDate>Tue, 31 Mar 2026 20:14:09 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2018/03/08/mlbpool2--mysql--mariadb/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2018/03/08/mlbpool2--mysql--mariadb.png" type="image/png" length="None" /> </item> <item> <title>MLBPool2 – Letting a Player Change their Picks</title> <author>Paul Cutler</author> <description>&lt;p&gt;I’ve been blogging a little bit about MLBPool2 the last couple of weeks and now the last three months of work is complete.&lt;/p&gt; &lt;p&gt;I already touched on two of the biggest differences between NFLPool and MLBPool2 (the time service using Pendulum and using MySQL / MariaDB instead of SQLite).&lt;/p&gt; &lt;p&gt;The biggest difference between NFLPool and MLBPool2 though is players have the ability to change their picks. At the All-Star Break, MLBPool2 players can change up to 14 of their 37 picks, but those changes are only worth half points.&lt;/p&gt; &lt;p&gt;This required a major re-write in the way I capture and store each player’s picks. I also figured, based on how NFLPool went when I launched it, if a player was going to be able to change their picks at the All-Star Break, I might as well let them change their picks before the season starts. (I had a couple NFLPool players request to make a change, which required me to delete all their picks from the database and I just told them to do it again. But I &lt;strong&gt;hate&lt;/strong&gt; touching the database.). I wrote a &lt;a href=&#34;https://github.com/prcutler/mlbpool2/blob/master/mlbpool/services/gameday_service.py&#34;&gt;new service&lt;/a&gt; (&lt;code&gt;gameday_service.py&lt;/code&gt;) that the app uses to figure out a few different things:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Season start&lt;/li&gt; &lt;li&gt;When picks are due&lt;/li&gt; &lt;li&gt;The All-Star Break (48 hours before and after the All-Star Game)&lt;/li&gt; &lt;li&gt;Season end&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;I already was using two methods and one for the initial picks submission and one for changes in the &lt;a href=&#34;https://github.com/prcutler/mlbpool2/blob/master/mlbpool/services/playerpicks_service.py&#34;&gt;PlayerPicks service&lt;/a&gt;. If the player was changing their picks, I used an if / else statement that compared the current time to when the season started:&lt;/p&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-0-1&#34;&gt;&lt;a id=&#34;__codelineno-0-1&#34; name=&#34;__codelineno-0-1&#34; href=&#34;#__codelineno-0-1&#34;&gt;&lt;/a&gt;now_time = TimeService.get_time() &lt;/span&gt;&lt;span id=&#34;__span-0-2&#34;&gt;&lt;a id=&#34;__codelineno-0-2&#34; name=&#34;__codelineno-0-2&#34; href=&#34;#__codelineno-0-2&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-0-3&#34;&gt;&lt;a id=&#34;__codelineno-0-3&#34; name=&#34;__codelineno-0-3&#34; href=&#34;#__codelineno-0-3&#34;&gt;&lt;/a&gt;if GameDayService.season_opener_date() &amp;gt; now_time: &lt;/span&gt;&lt;span id=&#34;__span-0-4&#34;&gt;&lt;a id=&#34;__codelineno-0-4&#34; name=&#34;__codelineno-0-4&#34; href=&#34;#__codelineno-0-4&#34;&gt;&lt;/a&gt;&#34;&#34;&#34;Update the picks passed from change-picks. If the season start date is later than the current time, &lt;/span&gt;&lt;span id=&#34;__span-0-5&#34;&gt;&lt;a id=&#34;__codelineno-0-5&#34; name=&#34;__codelineno-0-5&#34; href=&#34;#__codelineno-0-5&#34;&gt;&lt;/a&gt;make the new changed picks equal to the original picks, making original_pick equal to the new pick.&#34;&#34;&#34; &lt;/span&gt;&lt;span id=&#34;__span-0-6&#34;&gt;&lt;a id=&#34;__codelineno-0-6&#34; name=&#34;__codelineno-0-6&#34; href=&#34;#__codelineno-0-6&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-0-7&#34;&gt;&lt;a id=&#34;__codelineno-0-7&#34; name=&#34;__codelineno-0-7&#34; href=&#34;#__codelineno-0-7&#34;&gt;&lt;/a&gt;Update Pick Type 1 &lt;/span&gt;&lt;span id=&#34;__span-0-8&#34;&gt;&lt;a id=&#34;__codelineno-0-8&#34; name=&#34;__codelineno-0-8&#34; href=&#34;#__codelineno-0-8&#34;&gt;&lt;/a&gt;Update the AL East Winner Pick - check to see if it has been changed &lt;/span&gt;&lt;span id=&#34;__span-0-9&#34;&gt;&lt;a id=&#34;__codelineno-0-9&#34; name=&#34;__codelineno-0-9&#34; href=&#34;#__codelineno-0-9&#34;&gt;&lt;/a&gt;if al_east_winner_pick != session.query(PlayerPicks).filter(PlayerPicks.user_id == user_id) \ &lt;/span&gt;&lt;span id=&#34;__span-0-10&#34;&gt;&lt;a id=&#34;__codelineno-0-10&#34; name=&#34;__codelineno-0-10&#34; href=&#34;#__codelineno-0-10&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.season == season) \ &lt;/span&gt;&lt;span id=&#34;__span-0-11&#34;&gt;&lt;a id=&#34;__codelineno-0-11&#34; name=&#34;__codelineno-0-11&#34; href=&#34;#__codelineno-0-11&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.pick_type == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-0-12&#34;&gt;&lt;a id=&#34;__codelineno-0-12&#34; name=&#34;__codelineno-0-12&#34; href=&#34;#__codelineno-0-12&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.rank == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-0-13&#34;&gt;&lt;a id=&#34;__codelineno-0-13&#34; name=&#34;__codelineno-0-13&#34; href=&#34;#__codelineno-0-13&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.league_id == 0) \ &lt;/span&gt;&lt;span id=&#34;__span-0-14&#34;&gt;&lt;a id=&#34;__codelineno-0-14&#34; name=&#34;__codelineno-0-14&#34; href=&#34;#__codelineno-0-14&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.division_id == 1): &lt;/span&gt;&lt;span id=&#34;__span-0-15&#34;&gt;&lt;a id=&#34;__codelineno-0-15&#34; name=&#34;__codelineno-0-15&#34; href=&#34;#__codelineno-0-15&#34;&gt;&lt;/a&gt;session.query(PlayerPicks).filter(PlayerPicks.user_id == user_id).filter(PlayerPicks.pick_type == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-0-16&#34;&gt;&lt;a id=&#34;__codelineno-0-16&#34; name=&#34;__codelineno-0-16&#34; href=&#34;#__codelineno-0-16&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.season == season) \ &lt;/span&gt;&lt;span id=&#34;__span-0-17&#34;&gt;&lt;a id=&#34;__codelineno-0-17&#34; name=&#34;__codelineno-0-17&#34; href=&#34;#__codelineno-0-17&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.rank == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-0-18&#34;&gt;&lt;a id=&#34;__codelineno-0-18&#34; name=&#34;__codelineno-0-18&#34; href=&#34;#__codelineno-0-18&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.league_id == 0) \ &lt;/span&gt;&lt;span id=&#34;__span-0-19&#34;&gt;&lt;a id=&#34;__codelineno-0-19&#34; name=&#34;__codelineno-0-19&#34; href=&#34;#__codelineno-0-19&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.division_id == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-0-20&#34;&gt;&lt;a id=&#34;__codelineno-0-20&#34; name=&#34;__codelineno-0-20&#34; href=&#34;#__codelineno-0-20&#34;&gt;&lt;/a&gt;.update({&#34;team_id&#34;: al_east_winner_pick, &#34;date_submitted&#34;: now_time, &lt;/span&gt;&lt;span id=&#34;__span-0-21&#34;&gt;&lt;a id=&#34;__codelineno-0-21&#34; name=&#34;__codelineno-0-21&#34; href=&#34;#__codelineno-0-21&#34;&gt;&lt;/a&gt;&#34;original_pick&#34;: al_east_winner_pick}) &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;And do a &lt;code&gt;session.update&lt;/code&gt; to update the database.&lt;/p&gt; &lt;p&gt;But if it’s during the All-Star Break we need to capture that the pick has been changed (changing the value in the PlayerPicks table from 0 to 1) where the UniquePicks service will credit the player with half points:&lt;/p&gt; &lt;div class=&#34;language-text highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span id=&#34;__span-1-1&#34;&gt;&lt;a id=&#34;__codelineno-1-1&#34; name=&#34;__codelineno-1-1&#34; href=&#34;#__codelineno-1-1&#34;&gt;&lt;/a&gt;else: &lt;/span&gt;&lt;span id=&#34;__span-1-2&#34;&gt;&lt;a id=&#34;__codelineno-1-2&#34; name=&#34;__codelineno-1-2&#34; href=&#34;#__codelineno-1-2&#34;&gt;&lt;/a&gt;&#34;&#34;&#34;If the season has started, update picks at the All-Star Break. Do not change the original pick column &lt;/span&gt;&lt;span id=&#34;__span-1-3&#34;&gt;&lt;a id=&#34;__codelineno-1-3&#34; name=&#34;__codelineno-1-3&#34; href=&#34;#__codelineno-1-3&#34;&gt;&lt;/a&gt;and update the changed column to 1.&#34;&#34;&#34; &lt;/span&gt;&lt;span id=&#34;__span-1-4&#34;&gt;&lt;a id=&#34;__codelineno-1-4&#34; name=&#34;__codelineno-1-4&#34; href=&#34;#__codelineno-1-4&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-1-5&#34;&gt;&lt;a id=&#34;__codelineno-1-5&#34; name=&#34;__codelineno-1-5&#34; href=&#34;#__codelineno-1-5&#34;&gt;&lt;/a&gt;Update the AL East Winner Pick &lt;/span&gt;&lt;span id=&#34;__span-1-6&#34;&gt;&lt;a id=&#34;__codelineno-1-6&#34; name=&#34;__codelineno-1-6&#34; href=&#34;#__codelineno-1-6&#34;&gt;&lt;/a&gt;for pick in session.query(PlayerPicks.team_id).filter(PlayerPicks.user_id == user_id) \ &lt;/span&gt;&lt;span id=&#34;__span-1-7&#34;&gt;&lt;a id=&#34;__codelineno-1-7&#34; name=&#34;__codelineno-1-7&#34; href=&#34;#__codelineno-1-7&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.season == season) \ &lt;/span&gt;&lt;span id=&#34;__span-1-8&#34;&gt;&lt;a id=&#34;__codelineno-1-8&#34; name=&#34;__codelineno-1-8&#34; href=&#34;#__codelineno-1-8&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.pick_type == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-1-9&#34;&gt;&lt;a id=&#34;__codelineno-1-9&#34; name=&#34;__codelineno-1-9&#34; href=&#34;#__codelineno-1-9&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.rank == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-1-10&#34;&gt;&lt;a id=&#34;__codelineno-1-10&#34; name=&#34;__codelineno-1-10&#34; href=&#34;#__codelineno-1-10&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.league_id == 0) \ &lt;/span&gt;&lt;span id=&#34;__span-1-11&#34;&gt;&lt;a id=&#34;__codelineno-1-11&#34; name=&#34;__codelineno-1-11&#34; href=&#34;#__codelineno-1-11&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.division_id == 1).first(): &lt;/span&gt;&lt;span id=&#34;__span-1-12&#34;&gt;&lt;a id=&#34;__codelineno-1-12&#34; name=&#34;__codelineno-1-12&#34; href=&#34;#__codelineno-1-12&#34;&gt;&lt;/a&gt; &lt;/span&gt;&lt;span id=&#34;__span-1-13&#34;&gt;&lt;a id=&#34;__codelineno-1-13&#34; name=&#34;__codelineno-1-13&#34; href=&#34;#__codelineno-1-13&#34;&gt;&lt;/a&gt;if pick != int(al_east_winner_pick): &lt;/span&gt;&lt;span id=&#34;__span-1-14&#34;&gt;&lt;a id=&#34;__codelineno-1-14&#34; name=&#34;__codelineno-1-14&#34; href=&#34;#__codelineno-1-14&#34;&gt;&lt;/a&gt;session.query(PlayerPicks).filter(PlayerPicks.user_id == user_id) \ &lt;/span&gt;&lt;span id=&#34;__span-1-15&#34;&gt;&lt;a id=&#34;__codelineno-1-15&#34; name=&#34;__codelineno-1-15&#34; href=&#34;#__codelineno-1-15&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.pick_type == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-1-16&#34;&gt;&lt;a id=&#34;__codelineno-1-16&#34; name=&#34;__codelineno-1-16&#34; href=&#34;#__codelineno-1-16&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.rank == 1).filter(PlayerPicks.league_id == 0) \ &lt;/span&gt;&lt;span id=&#34;__span-1-17&#34;&gt;&lt;a id=&#34;__codelineno-1-17&#34; name=&#34;__codelineno-1-17&#34; href=&#34;#__codelineno-1-17&#34;&gt;&lt;/a&gt;.filter(PlayerPicks.division_id == 1) \ &lt;/span&gt;&lt;span id=&#34;__span-1-18&#34;&gt;&lt;a id=&#34;__codelineno-1-18&#34; name=&#34;__codelineno-1-18&#34; href=&#34;#__codelineno-1-18&#34;&gt;&lt;/a&gt;.update({&#34;team_id&#34;: al_east_winner_pick, &#34;date_submitted&#34;: now_time, &#34;changed&#34;: 1, &#34;multiplier&#34;: 1}) &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;When the change picks form is submitted, all 37 picks are sent from the form to the POST and viewmodel. In the controller, I checked if the number of changes was greater than 14 and would redirect them to an error page. But if there were 14 or less changes, the above code would run. If the new pick was not equal to the pick stored in the database, we’d update the pick, capture the time the player submitted changes and flag that the pick was changed. The code works, but there are 36 blocks like the one above and I couldn’t figure out a method that would work where I could pass it parameters. (36, not 37 as the tiebreaker pick can never be changed). But the key is that it works.&lt;/p&gt; &lt;p&gt;The multiplier is reset to 1 and if their original pick had qualified for the &lt;a href=&#34;https://mlbpool2.com/rules&#34;&gt;double points bonus&lt;/a&gt;, it needs to reset back to one as the UniquePicks service is re-run after the All-Star Break and then will assign a multiplier of 2 if the new pick qualifies for the double points bonus.&lt;/p&gt; &lt;p&gt;I plan on porting the code to allow a player to change their picks before the season starts to NFLPool to make the player’s life easier. But really, it’s to make mine easier as I don’t want to have to manually delete their picks from the database.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2018/03/23/mlbpool2--letting-a-player-change-their-picks/</link> <pubDate>Tue, 31 Mar 2026 20:14:09 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2018/03/23/mlbpool2--letting-a-player-change-their-picks/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2018/03/23/mlbpool2--letting-a-player-change-their-picks.png" type="image/png" length="None" /> </item> </channel> </rss>