<?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>Wed, 29 Apr 2026 10:26:47 -0000</pubDate> <lastBuildDate>Wed, 29 Apr 2026 10:26:47 -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>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;../../../20260-04-07-moviebuff/moviebuff.png&#34; /&gt;&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2026/04/07/moviebuff/</link> <pubDate>Tue, 07 Apr 2026 12:52:44 +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>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 {&amp;quot;release_info&amp;quot;: 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 {&amp;quot;release_info&amp;quot;: 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;&amp;quot;play&amp;quot;&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;&amp;quot;silversaucer:templates/play/play.pt&amp;quot;&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;&amp;quot;release_info&amp;quot;&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;&amp;quot;play&amp;quot;&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;&amp;quot;silversaucer:templates/play/play.pt&amp;quot;&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;&amp;quot;release_info&amp;quot;&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;&amp;quot;?=&amp;quot;&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;&amp;quot;releases/&amp;quot;&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;&amp;quot;?&amp;quot;&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;&amp;quot;/Users/prcutler/workspace/silversaucer/silversaucer/controllers/music_controller.py&amp;quot;&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;&amp;quot;/Users/prcutler/workspace/silversaucer/silversaucer/services/play_service.py&amp;quot;&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;&amp;quot;uri&amp;quot;&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;&amp;#39;uri&amp;#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 &amp;quot;/Users/prcutler/workspace/silversaucer/silversaucer/services/play_service.py&amp;quot;, 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[&amp;quot;uri&amp;quot;] &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: &amp;#39;uri&amp;#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;&amp;quot;releases/&amp;quot;&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;&amp;quot;?&amp;quot;&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(&amp;#39;static/img/album-art/image_600.jpg&amp;#39;) img.quantize(colors=16, method=2) smol_img = img.resize((320, 320)) convert = smol_img.convert(mode=&amp;quot;P&amp;quot;, palette=Image.WEB) convert.save(&amp;#39;static/img/album-art/image_300.bmp&amp;#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__ = &amp;#39;DivisionInfo&amp;#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__ = &amp;#39;DivisionInfo&amp;#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;&amp;quot;&amp;quot;&amp;quot;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.&amp;quot;&amp;quot;&amp;quot; &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({&amp;quot;team_id&amp;quot;: al_east_winner_pick, &amp;quot;date_submitted&amp;quot;: 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;&amp;quot;original_pick&amp;quot;: 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;&amp;quot;&amp;quot;&amp;quot;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.&amp;quot;&amp;quot;&amp;quot; &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({&amp;quot;team_id&amp;quot;: al_east_winner_pick, &amp;quot;date_submitted&amp;quot;: now_time, &amp;quot;changed&amp;quot;: 1, &amp;quot;multiplier&amp;quot;: 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> <item> <title>NFLPool - The Work Continues</title> <author>Paul Cutler</author> <description>&lt;p&gt;For once in my life, I’m not procrastinating. With the NFL season just over four and a half months away, I’ve already started on working to update &lt;a href=&#34;https://nflpool.xyz&#34;&gt;NFLPool&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;I’m really enjoying working with Python and don’t want to let the few things I’ve learned get rusty. This includes back porting a number of updates from &lt;a href=&#34;https://www.mlbpool2.com&#34;&gt;MLBPool2&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;I&#39;ve updated the Standings page to display all seasons played (before it defaulted to just the current season). This uses traversal in Pyramid to create a GET request based on the season year and automatically creates the links. This feature within Pyramid is so cool, I guess I should expand on it:&lt;/p&gt; &lt;p&gt;I have a &lt;code&gt;standings_controller.py&lt;/code&gt; that creates all of the routes for the pages in Standings:&lt;/p&gt; &lt;p&gt;I call the StandingsService to get a list of all the seasons that exist in the database to create the index page and it shows a bullet list of each season. (Currently, this returns the one season that has been played, 2017). You can &lt;a href=&#34;https://nflpool.xyz/standings&#34;&gt;see it in action here&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;You can then click on the &lt;em&gt;2017&lt;/em&gt; bullet to see the player standings for the 2017 season. The code for the player standings in each season looks like:&lt;/p&gt; &lt;p&gt;The cool thing is &lt;code&gt;season = self.request.matchdict[&#39;id&#39;]&lt;/code&gt; above - when the user clicks on 2017, that’s passed to this method, that passes &lt;em&gt;2017&lt;/em&gt; and makes season equal to whatever bullet the user clicked on in the list on the Standings index page, which is 2017 in this example and returns the 2017 Standings. The rest of the code passes the &lt;code&gt;season&lt;/code&gt; variable (which is now 2017) to the database to show all of the players and their score for the 2017 season. I also updated the template that if the week is 17 or greater (there are only 17 weeks in an NFL season), the header on the page now says &lt;em&gt;2017 Final Standings&lt;/em&gt;, otherwise it would return &lt;em&gt;2017 Week x Standings&lt;/em&gt;, where x is equal to whatever week the standings have been updated through. This was helpful as last year there was an issue with how division standings were pulled from MySportsFeeds because they don’t calculate the tiebreakers in their API and they had to fix it - after they fixed it, I updated the stats from their API, but now it was “Week 18”, which technically doesn’t exist in the NFL. Now it just says final.&lt;/p&gt; &lt;p&gt;I also added Slack messaging that is present in MLBPool2. When a new user registers or submits their picks, I get a message in my NFLPool Slack channel.&lt;/p&gt; &lt;p&gt;A minor thing, but when players are selecting the NFL player they think will win in a category (such as who has the most passing yards), the dropdown box that shows a list of all NFL players now includes the player’s team and position in addition to the player’s name. Another small thing is the site administrator can update when a player has paid the annual league fee and when the admin updates to a new season the list players who have paid is reset.&lt;/p&gt; &lt;p&gt;Last, but not least, I’ve been working on documentation. I had already written the User Guide on how to play and make your picks, and over the last week I’ve added an Administrator’s Guide, including how to install, first time setup, how to update to a new season and how to manage NFLPool players. This is all written in reStructured Text and uses Sphinx to create the documentation and is hosted on &lt;a href=&#34;http://nflpool.readthedocs.io/&#34;&gt;ReadTheDocs&lt;/a&gt;. I still need to write the developer documentation - I have no idea how to write developer docs and am still looking for some good examples of developer documentation to crib from.&lt;/p&gt; &lt;p&gt;It’s a good thing I’m using &lt;a href=&#34;https://github.com/prcutler/nflpool/issues?utf8=%E2%9C%93&amp;amp;q=is%3Aissue+is%3Aall+&#34;&gt;Github’s issue tracker&lt;/a&gt; - a couple of those fixes above I had added last September after NFLPool launched and probably would have forgotten about. Between that and the documentation, it’s almost like I’m running a real open source project!&lt;/p&gt; &lt;p&gt;The work doesn’t end there, though. The big one I need to add the TimeService and GamedayService to make datetime management easier, especially for calculating when picks are due and displaying it on the pick submission page. I also have a couple of more bug fixes to get to and I’d like to add Twitter support to the app as well. The journey never ends.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2018/04/25/nflpool---the-work-continues/</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/04/25/nflpool---the-work-continues/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2018/04/25/nflpool---the-work-continues.png" type="image/png" length="None" /> </item> <item> <title>Password Validation in NFLPool and MLBPool2</title> <author>Paul Cutler</author> <description>&lt;p&gt;When I first learned to work with Pyramid thanks to the Talk Python course Python for Entrepreneurs, I used the account registration system directly from the course for NFLPool. When I wrote MLBPool2, I augmented it to require the user to use a much stronger password than the course taught. (You can see the original code from &lt;a href=&#34;https://github.com/prcutler/nflpool/blob/master/nflpool/viewmodels/register_viewmodel.py&#34;&gt;NFLPool here&lt;/a&gt;).&lt;/p&gt; &lt;p&gt;In MLBPool2, I required the user to use a password between 8 and 24 characters and it must have at least one lowercase letter, at least one upper case letter, a number, and a symbol:&lt;/p&gt; &lt;p&gt;A nice error message is shown each time the user doesn’t do it correctly. I was pretty proud of myself for figuring this out (and for using Stack Overflow to get some tips on how to do it).&lt;/p&gt; &lt;p&gt;But…. It hit me a couple weeks ago that it works great for user registration, but if they lose their password or want to reset it, none of that functionality is there. When resetting a password, the user clicks the reset password link which emails them a one-time link to reset their password. And none of the password requirements I was so proud of are in the methods for resetting a password. &lt;/p&gt; &lt;p&gt;It’s going to require a big re-write - right now it only has one input field for the new password. I’ll need to add a second field to require the user to enter the new password twice (similar to registration) and then add all of the requirements and the error messages to the viewmodel. So I’ll be working on that over the next week or so.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2018/04/27/password-validation-in-nflpool-and-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/04/27/password-validation-in-nflpool-and-mlbpool2/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2018/04/27/password-validation-in-nflpool-and-mlbpool2.png" type="image/png" length="None" /> </item> <item> <title>Learning pytest using continuous integration with Azure Pipelines (or SSH key hell) - Part 1</title> <author>Paul Cutler</author> <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt; &lt;p&gt;I’m still on my quest to learn more Python and at the top of that list is learning &lt;a href=&#34;https://docs.pytest.org/en/latest/&#34;&gt;pytest&lt;/a&gt;. I just &lt;a href=&#34;https://paulcutler.org/blog/learning-how-to-test-in-python-and-pyramid/&#34;&gt;can’t wrap my head around testing&lt;/a&gt; and I know my two Pyramid apps aren’t “complete” until there are tests. (I did write docs, so I have that going for me).&lt;/p&gt; &lt;p&gt;A couple months ago I (very easily) added continuous integration to NFLPool using Microsoft’s &lt;a href=&#34;https://azure.microsoft.com/en-us/services/devops/pipelines/&#34;&gt;Azure Pipelines&lt;/a&gt;. I, like many other people, have been blown away by the right turn Microsoft made a few years back to embrace open source, and wanted to give Azure Pipelines a try. Every time I make a commit or &lt;a href=&#34;https://pyup.io/&#34;&gt;Pyup.io&lt;/a&gt; submits a pull request to update a Python package, Azure Pipelines builds NFLPool. Of course it fails, because the tests I’ve written so far fail. To end this cycle, I really need to learn how to write tests!&lt;/p&gt; &lt;p&gt;I have a domain I don’t use, silversaucer.com. I have a few different ideas for some projects for the domain. I’m going to build another web app using &lt;a href=&#34;https://www.trypyramid.com&#34;&gt;Pyramid&lt;/a&gt; to start, with a goal of properly using classes in Pyramid (and not Pyramid handlers) to start. I’ve also been reading &lt;a href=&#34;https://www.obeythetestinggoat.com/&#34;&gt;Test-Driven Development with Python, 2nd Edition&lt;/a&gt;, that I received from a recent Humble Bundle filled with Python books. &lt;/p&gt; &lt;p&gt;I was thinking that this was a perfect opportunity to learn &lt;code&gt;pytest&lt;/code&gt;. I would create a new project in Pyramid and use TDD to write my tests as I write my code. If I’m going to do that, I might as well set up continuous integration right away and pretend I’m a real developer.&lt;/p&gt; &lt;h2 id=&#34;setting-up-pyramid&#34;&gt;Setting up Pyramid&lt;/h2&gt; &lt;p&gt;Since I’m going to be using a Test Driven Development philosophy, all I’m going to do is create a Pyramid project and not make any changes to it yet. I used &lt;a href=&#34;https://docs.pylonsproject.org/projects/pyramid/en/1.10-branch/narr/project.html#project-narr&#34;&gt;Pyramid’s cookiecutter to create the project&lt;/a&gt; and then committed it to my Github repository. That’s it!&lt;/p&gt; &lt;h2 id=&#34;hooking-up-azure-pipelines&#34;&gt;Hooking up Azure Pipelines&lt;/h2&gt; &lt;p&gt;Here is where the fun starts. I’m not going to go through this process as Microsoft has great documentation for Azure and &lt;a href=&#34;#&#34;&gt;set up&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Walk through the setup and connect to your repository on Github. Azure Pipelines will create the needed YAML file, commit it and run your first build.&lt;/p&gt; &lt;p&gt;Here is where my build failed for Silver Saucer. I was getting the following error:&lt;/p&gt; &lt;p&gt;Obtaining silversaucer from git+git@github.com:prcutler/silversaucer.git@75932f389536b59993fa780b281170849ff92238#egg=silversaucer (from -r requirements.txt (line 38)) Cloning git@github.com:prcutler/silversaucer.git (to revision 75932f389536b59993fa780b281170849ff92238) to ./src/silversaucer Running command git clone -q git@github.com:prcutler/silversaucer.git /home/vsts/work/1/s/src/silversaucer Host key verification failed. fatal: Could not read from remote repository.&lt;/p&gt; &lt;p&gt;Here is where I first lost hours over the course of a few days. Azure is pulling my repository using git, not https. I would compare this to my Azure Pipeline for NFLPool, which for some reason pulls my repository using https and works fine. I know a little bit about SSH keys. I’m no expert, but all of my Digital Ocean and servers at home use SSH key authentication to log in and not passwords (yay me for good opsec!) and I have my SSH key on multiple computers without any issues.&lt;/p&gt; &lt;p&gt;Lots of search queries later, I learned how you can &lt;a href=&#34;https://help.github.com/en/articles/changing-a-remotes-url#switching-remote-urls-from-https-to-ssh&#34;&gt;change a git repository’s remote URL&lt;/a&gt;, but this appears just to be on your local machine. I’m sure I’m missing something simple to make it a global change, but I never figured it out.&lt;/p&gt; &lt;p&gt;Ok, let’s add my SSH key to Azure Pipelines. Again, Microsoft has &lt;a href=&#34;https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops#configuration&#34;&gt;good developer documentation on how to do this&lt;/a&gt;. &lt;/p&gt; &lt;ul&gt; &lt;li&gt;Step 1: Add your public key to your Azure profile.&lt;/li&gt; &lt;li&gt;Step 2: In your projects in Azure Pipelines, go to Pipelines -&amp;gt; Library and choose Secure files. Add your private key (usually &lt;code&gt;id_rsa&lt;/code&gt;). &lt;/li&gt; &lt;li&gt;Step 3: Add the SSH Task to Azure Pipelines and make sure you authorize the private key - follow &lt;a href=&#34;https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/install-ssh-key?view=azure-devops&#34;&gt;Microsoft’s developer documentation for the SSH Task&lt;/a&gt;. Update your YAML file:&lt;/li&gt; &lt;/ul&gt; &lt;h1 id=&#34;install-ssh-key&#34;&gt;Install SSH Key&lt;/h1&gt; &lt;h1 id=&#34;install-an-ssh-key-prior-to-a-build-or-release&#34;&gt;Install an SSH key prior to a build or release&lt;/h1&gt; &lt;ul&gt; &lt;li&gt;task: InstallSSHKey@0 inputs: hostName: sshPublicKey: #sshPassphrase: # Optional sshKeySecureFile: &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;The hostname input confused me at first, but here you’re going to go into your &lt;code&gt;~/.ssh&lt;/code&gt; directory and copy and paste the Github entry in your &lt;code&gt;known_hosts&lt;/code&gt; file. (This is a hidden directory in your home folder on macOS or Linux. I’m not sure where it is on Windows, sorry!) Paste your public key in &lt;code&gt;sshPublicKey:&lt;/code&gt; and the name of your private key that you uploaded in Step 2 above. If your repository is public on Github, you are not going to want to add your sshPassphrase to your YAML file.&lt;/p&gt; &lt;h2 id=&#34;my-ssh-key-fails&#34;&gt;My SSH key fails&lt;/h2&gt; &lt;p&gt;Again, I lost hours here. I have no idea why, but the SSH key I’ve been using for the last couple of years will not work. I had to create a second SSH key for Azure Pipelines and delete my &lt;code&gt;known_hosts&lt;/code&gt; file, clone the repository again (which then updated &lt;code&gt;known_hosts&lt;/code&gt;) and paste in the new fingerprint from &lt;code&gt;known_hosts&lt;/code&gt; to the YAML file. The new key works fine, but I’ll be damned if I can figure out why my normal key doesn’t work. I’ve compared the fingerprint of my usual key, used it to clone other repositories, but Azure Pipelines refuses to work with it as I would just receive the above error over and over again.&lt;/p&gt; &lt;p&gt;I’m not thrilled with having a second SSH key, and now I have to go to the other two computers I work with and copy it over and add it to my keyring. But it works.&lt;/p&gt; &lt;h2 id=&#34;whats-next&#34;&gt;What’s next&lt;/h2&gt; &lt;p&gt;Coming up in Part 2: Pytest works in Azure Pipelines in Python 3.6 (but not Python 3.7!)&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2019/06/14/learning-pytest-using-continuous-integration-with-azure-pipelines-or-ssh-key-hell---part-1/</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/2019/06/14/learning-pytest-using-continuous-integration-with-azure-pipelines-or-ssh-key-hell---part-1/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2019/06/14/learning-pytest-using-continuous-integration-with-azure-pipelines-or-ssh-key-hell---part-1.png" type="image/png" length="None" /> </item> <item> <title>Displaying the Album Information</title> <author>Paul Cutler</author> <description>&lt;p&gt;In my &lt;a href=&#34;https://paulcutler.org/posts/2020/12/iterating-through-the-folder-dictionary/&#34;&gt;last blog post&lt;/a&gt;, I was able to pass the folder to Discogs to get a list of albums, and then pick one at random and give me information back about the album picked. That information was returned as a dictionary and now it is time to wire it up to the Chameleon template in Pyramid that will display the HTML.&lt;/p&gt; &lt;p&gt;It was way easier than expected. (And if you know me, you know that the random record picked above is perfect, as I&#39;m a huge Japandroids fan.) The first thing I did was connect the image URI that the Discogs API passes in the Chameleon template:&lt;/p&gt; &lt;p&gt;&lt;code&gt;&amp;lt;img class=&#34;mb-5&#34; src=&#34;${release_info.release_image_uri}&#34; alt=&#34;${release_info.release_title}&#34; /&amp;gt;&lt;/code&gt;&lt;/p&gt; &lt;p&gt;That worked, which was awesome. The whole image is shown, and I don&#39;t have to worry about parsing any of the URL.&lt;/p&gt; &lt;p&gt;I quickly added the values to show the &lt;em&gt;Artist&lt;/em&gt;, &lt;em&gt;Album&lt;/em&gt;, and &lt;em&gt;Release Date&lt;/em&gt; information:&lt;/p&gt; &lt;p&gt;Artist: ${release_info.artist_name} Album: ${release_info.release_title} Released: ${release_info.release_date}&lt;/p&gt; &lt;p&gt;It looks like this:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Japandroids - Massey Fucking Hall&#34; src=&#34;../../../2020-12-23-displaying-album-info/japandroids.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;Looking at Debbie Gibson&#39;s &lt;em&gt;Electric Youth&lt;/em&gt;, the &lt;em&gt;Genre&lt;/em&gt; key in the dictionary returns a list:&lt;/p&gt; &lt;p&gt;&lt;code&gt;&#39;genres&#39;: [&#39;Electronic&#39;, &#39;Pop&#39;]&lt;/code&gt;&lt;/p&gt; &lt;p&gt;With some trial and error I was able to display the list, but it showed up as code, looking like: &lt;code&gt;[&#39;Electronic&#39;, &#39;Pop&#39;]&lt;/code&gt;. No one wants to see that. The challenge is that Chameleon templates use something like a shorthand for Python code. I revisited two of my former projects where I would iterate over a list and show the results, but after lots of trial and error I felt like I was going backwards. Some more search engine queries and Stack Overflow searching I did what I always do when I get stuck: I asked my wife. It took her about 15 minutes to figure out how to loop over the list and show it in bullets:&lt;/p&gt; &lt;p&gt;&lt;code&gt;&amp;lt;p&amp;gt;&amp;lt;li tal:repeat=&#34;item release_info.genres&#34; tal:content=item/&amp;gt;&lt;/code&gt;&lt;/p&gt; &lt;p&gt;I was so close - she pointed out the mistake I made with having two &lt;code&gt;tal:repeat&lt;/code&gt; methods in the template. That’s what’s hard about the trial and error method of finding the solution, especially as I wasn’t writing down the methods that kind of worked. (Oops. Usually I have a note going in Bear to capture things like this, but sometimes you get in the zone and just keep trying things as you get closer. Documentation is good!)&lt;/p&gt; &lt;p&gt;Lastly, I changed the image size of the album image returned from Discogs, shrinking it from 400x400 to 300x300 and it seems to fit in the Bootstrap container better. You can see the difference with Debbie Gibson&#39;s &lt;em&gt;Electric Youth&lt;/em&gt; below at 400x400 and Japandroids at 300x300 above.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Debbie Gibson - Electric Youth&#34; src=&#34;../../../2020-12-23-displaying-album-info/debbie-gibson.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;Now I have lots of cleanup to do. I need to turn the text into links, for example if you click on the artist, Japandroids, it takes you to their page on Discogs. I also need to fix how it justifies and draws the bullets. I think almost all of this is done in CSS and I don&#39;t know CSS at all... (I had to cheat to make the text color white to show up on the page already.)&lt;/p&gt; &lt;p&gt;Random thoughts and musings:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Every time the page loads, it refreshes the API call. So if you click a link to the artist page on Discogs, for example, and then click the back button, that album data has been replaced. Not sure how to work around that, though I have some ideas. Probably a good question in IRC with the Pyramid team.&lt;/li&gt; &lt;li&gt;I like Chameleon templates. Not only is it the first template language I&#39;ve learned, I&#39;ve looked at Jinja templates and they look harder to use. They&#39;re used much more widely and that seems to lead to better documentation, but I don&#39;t have plans to change from Chameleon.&lt;/li&gt; &lt;li&gt;I had more thoughts, but I&#39;m finishing up this blog post almost a week later as my internet went out.&lt;/li&gt; &lt;li&gt;The &lt;em&gt;Release Date&lt;/em&gt; returned from Discogs has the same problem I&#39;ve already talked about for the future &#34;On This Day&#34; feature I want to add. It could be just the year or the full release date, you never know what you&#39;re going to get. Look at the two screenshots above! I don&#39;t know if I want to add all the functionality to return the &#34;correct&#34; release date yet. I&#39;m worried about how long the API calls take to load the page. (Even if it&#39;s just for me and I know how long it takes, I don&#39;t want it taking long.)&lt;/li&gt; &lt;/ul&gt;</description> <link>https://paulcutler.org/blog/2020/12/23/displaying-the-album-information/</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/2020/12/23/displaying-the-album-information/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2020/12/23/displaying-the-album-information.png" type="image/png" length="None" /> </item> <item> <title>Next class up - Python Jumpstart by Building 10 Apps</title> <author>Paul Cutler</author> <description>&lt;p&gt;I’ve completed the &lt;a href=&#34;https://www.coursera.org/specializations/python&#34;&gt;Python For Everybody&lt;/a&gt; course taught by Dr. Charles Severance at the University of Michigan on Coursera. All that’s left is the capstone project to put into practice what I’ve learned, but as I’m doing this to learn Python and not for the official certificate, I’m going to skip it. The course is taught in Python 2.7 and I want to shift to Python 3.x.&lt;/p&gt; &lt;p&gt;Python For Everybody was great. The pace and the exercises were perfect for the class. I wish I had realized sooner that there were additional exercises in the textbook that were not part of the required Coursera class. The fourth class, Python and Databases, was intense. The speed of the class was accelerated with teaching you SQL and how Python connects to databases (SQLlite specifically). The homework was much more simple in Python for Databases compared to the first three sessions. You usually had to only make some minor changes in the SQL syntax to complete the grade.&lt;/p&gt; &lt;p&gt;The two things I’m going to need to focus on to have success in building the two apps I want to build are dictionaries (from importing statistics via JSON) and databases. If I walked away from one thing from the Python for Databases class is that I’m going to need to spend some time with paper and pencil and plan my information architecture and database models if I’m going to be successful.&lt;/p&gt; &lt;p&gt;Next I’m going to start &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; by Michael Kennedy of the Talk Python podcast. I supported the Kickstarter earlier this year and am excited now that I hope I have enough of a base understanding of Python to tackle this. This will be taught in Python 3.x (yay!) and I’m hoping now that I have that base knowledge, building these apps along with the tutorials included will give the practice I need to later build a real app. It’s also going to go into a little more detail than what I’ve learned so far on list comprehension (which makes my head hurt), BeautifulSoup for web scraping, and Classes.&lt;/p&gt; &lt;p&gt;I also supported Mr. Kennedy’s next Kickstarter, &lt;a href=&#34;https://training.talkpython.fm/courses/explore_entrepreneurs/python-for-entrepreneurs-build-and-launch-your-online-business&#34;&gt;Python for Entrepreneurs&lt;/a&gt;. This also has me excited as the second phase of building my fantasy sports app will be deploying it on the web. The description looks perfect for what I’ll need, in addition to learning the web framework Pyramid:&lt;/p&gt; &lt;p&gt;&lt;strong&gt;You will learn to build and design your web app&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;&lt;strong&gt;This course will teach you how to build a data-driven web application in Python.&lt;/strong&gt; &lt;/p&gt; &lt;p&gt;&lt;em&gt;We will&lt;/em&gt;:&lt;/p&gt; &lt;p&gt;Build our web app with the Pyramid web framework, &#34;the Python web framework that supports your decisions, by artisans for artisans.&#34;&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;Create and connect to our database using SQLAlchemy, the most popular data access layer in Python** &lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;Learn the core elements of web design including CSS and front-end frameworks such as Bootstrap.&lt;/p&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Time to get to work.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2016/10/06/next-class-up---python-jumpstart-by-building-10-apps/</link> <pubDate>Mon, 30 Mar 2026 23:20:12 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2016/10/06/next-class-up---python-jumpstart-by-building-10-apps/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2016/10/06/next-class-up---python-jumpstart-by-building-10-apps.png" type="image/png" length="None" /> </item> <item> <title>On this one, I&#39;m with Colin Powell</title> <author>Paul Cutler</author> <description>&lt;p&gt;Last Thursday, the Minneapolis Star-Tribune &lt;a href=&#34;http://www.startribune.com/stories/587/3909247.html&#34;&gt;ran an article saying Sen. Mark Dayton, with Sen. Coleman, would support the Amendment to ban flag-burning&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;I am absolutely outraged that Sen. Dayton, would change his mind on this topic.&lt;/p&gt; &lt;p&gt;So Sen. Mark Dayton has hopped on the flag-burning-amendment bandwagon (Star Tribune, May 30).&lt;/p&gt; &lt;p&gt;&lt;a href=&#34;http://www.startribune.com/stories/563/3910875.html&#34;&gt;Ben Seymour, in a letter to the editor in the Star Tribune, put it best:&lt;/a&gt;&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Dayton is echoing the rhetoric used to stifle criticism of our government. &amp;#8220;There&amp;#8217;s something in this country that ought to be above government and all the debates in the parties,&amp;#8221; he says.&lt;/p&gt; &lt;p&gt;In a democracy, no idea with broad support is above debate. We should question the motives of these politicians.&lt;/p&gt; &lt;p&gt;It&amp;#8217;s pathetic when they return from their dead soldier pilgrimages with &amp;#8220;patriotic&amp;#8221; epiphanies, which in reality are ill-reasoned, American-ideal-desecrating attempts at popularity boosting, at best. &lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;As you can see &lt;a href=&#34;http://www.aclu.org/FreeSpeech/FreeSpeech.cfm?ID=9969&amp;amp;c=50&#34;&gt;here on the ACLU&amp;#8217;s page against this amendment&lt;/a&gt;, even Secretary of State Colin Powell, former Chairman of the Joint Chief&amp;#8217;s of Staff, agrees with me on this one:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&amp;#8220;The First Amendment exists to insure that freedom of speech and expression applies not just to that with which we agree or disagree, but also that which we find outrageous,&amp;#8221; he said. &amp;#8220;I would not amend that great shield of democracy to hammer a few miscreants. The flag will be flying proudly long after they have slunk away.&amp;#8221; &lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;Cuba jails dissidents over flag desecration. Is this what we have sunk to in the name of &amp;#8220;patriotism&amp;#8221;?&lt;/p&gt; &lt;p&gt;Email, fax, or write your members in Congress.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2003/06/02/on-this-one-im-with-colin-powell/</link> <pubDate>Mon, 30 Mar 2026 22:51:37 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2003/06/02/on-this-one-im-with-colin-powell/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2003/06/02/on-this-one-im-with-colin-powell.png" type="image/png" length="None" /> </item> <item> <title>Favorite Albums of 2025</title> <author>Paul Cutler</author> <description>&lt;p&gt;&lt;img alt=&#34;Favorite Albums of 2025&#34; src=&#34;../../../2025-12-31-favorite-albums-of-2025/2025-albums.jpeg&#34; /&gt;&lt;/p&gt; &lt;p&gt;Like &lt;a href=&#34;https://paulcutler.org/posts/2022/12/favorite-albums-of-2022/&#34;&gt;2022&lt;/a&gt;, &lt;a href=&#34;https://www.paulcutler.org/posts/2023/12/favorite-albums-of-2023/&#34;&gt;2023&lt;/a&gt;, and &lt;a href=&#34;https://www.paulcutler.org/blog/2025/01/01/favorite-albums-of-2024/&#34;&gt;last year&lt;/a&gt;, here’s my favorite albums and releases of 2025 in no particular order:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Album of the year: Haim, &lt;em&gt;I Quit&lt;/em&gt;: This album was released at the perfect time and became my go to album of the summer. Fantastic from start to finish.&lt;/li&gt; &lt;li&gt;Best reissue: Mother Love Bone&#39;s &lt;em&gt;Shine&lt;/em&gt; and &lt;em&gt;Apple&lt;/em&gt;. Not only are they newly remastered, they sounded great streaming and then the records came out and sounded just as good. If you don&#39;t know the history of Pearl Jam, look up Mother Love Bone and give them a listen.&lt;/li&gt; &lt;li&gt;Best Soundtrack: Nine Inch Nails, &lt;em&gt;Tron: Ares&lt;/em&gt;. It was like peanut butter and chocolate - NIN and Tron go perfectly together and they nailed (no pun intended) the soundtrack.&lt;/li&gt; &lt;li&gt;Favorite Surprise: Craig Finn, &lt;em&gt;Always Been&lt;/em&gt;. As much as I love, love, love The Hold Steady, I&#39;ve never been able to get into Craig Finn&#39;s solo work. But this album - Craig&#39;s stories combined with The War on Drugs as his backing band was really something special.&lt;/li&gt; &lt;li&gt;Favorite throwback: Momma, &lt;em&gt;Welcome To My Blue Sky&lt;/em&gt;. Every review you read of Momma&#39;s first album or this follow-up mentions their throwback sound to the 90s indie scene. And they&#39;re right - and it works.&lt;/li&gt; &lt;li&gt;Favorite local release: Poliça, &lt;em&gt;Dreams Go&lt;/em&gt;. The band&#39;s seventh record, and final album with bassist Chris Bierden, who is unable to play with his brain cancer diagnosis, did not disappoint.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;That&#39;s just a few of my favorite albums and releases of 2025. Here&#39;s to 2026.&lt;/p&gt;</description> <link>https://paulcutler.org/blog/2025/12/31/favorite-albums-of-2025/</link> <pubDate>Wed, 31 Dec 2025 14:26:49 +0000</pubDate> <source url="https://paulcutler.org/feed_rss_updated.xml">PaulCutler.org</source><guid isPermaLink="true">https://paulcutler.org/blog/2025/12/31/favorite-albums-of-2025/</guid> <enclosure url="https://paulcutler.org/assets/images/social/blog/2025/12/31/favorite-albums-of-2025.png" type="image/png" length="37007" /> </item> </channel> </rss>