<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on Russell ‛Russ’ Frith</title><link>https://russfrith.com/posts/</link><description>Recent content in Posts on Russell ‛Russ’ Frith</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 04 Mar 2024 15:08:01 -0400</lastBuildDate><atom:link href="https://russfrith.com/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Scoop Over Chocolatey: A Windows Package Manager Transition Guide</title><link>https://russfrith.com/posts/chocolatey-to-scoop/</link><pubDate>Mon, 04 Mar 2024 15:08:01 -0400</pubDate><guid>https://russfrith.com/posts/chocolatey-to-scoop/</guid><description>Finding the right Windows package manager for your needs.</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>For the last decade, I have been using Chocolatey as my package manager on Windows. I recently needed to install software on a new machine, and decided that now was an ideal time to revisit some popular package managers and perhaps make a switch.</p>
<p>One of my favorite benefits of package managers is the ability to install software in a consistent way across a variety of different applications. Having a command line tool with remote repositories of current versions of software makes it easy to keep applications up to date. Finally, it makes them easy to remove in a unified way should they no longer be needed. When setting up a new machine, the ability to have a script that installs most if not all the applications that I will need on that machine using such a tool is also highly beneficial.</p>
<h2 id="package-managers">Package Managers</h2>
<p>A package manager is a tool or system that automates the processes of installing, upgrading, configuring, and using software, primarily aimed at developer tools. Developers specify their required tools, and the package manager installs and configures them based on these specifications. This not only saves time in setting up a development environment, but also ensures consistency in the versions of packages installed. Let&rsquo;s explore some of the most popular package managers and examine their respective strengths and weaknesses.</p>
<h3 id="winget">winget</h3>
<p>Windows Package Manager winget command-line tool is available on current versions of Windows as a part of the App Installer, launched by Microsoft to simplify application management on Windows systems. Announced in May 2020 at Microsoft Build, this command-line tool allows users to discover, install, upgrade, remove, and configure applications, filling a gap in Windows&rsquo; developer tools by providing functionality similar to package managers in Linux or macOS. Its introduction marks a significant step by Microsoft to enhance the Windows developer experience.</p>
<p>winget distinguishes itself from other popular Windows package managers in several ways:</p>
<ol>
<li>
<p><strong>Integration with Windows</strong>: As a Microsoft product, winget is closely integrated with the Windows ecosystem. This integration offers potential for more seamless updates and compatibility with Windows-specific features and updates.</p>
</li>
<li>
<p><strong>Official Microsoft Support</strong>: winget is developed and supported by Microsoft, which may provide a sense of security and stability for users and organizations, particularly in terms of long-term support and integration with other Microsoft products.</p>
</li>
<li>
<p><strong>Open Source and Community Contributions</strong>: Unlike Ninite or the discontinued AppGet, winget is open source, allowing the community to contribute to its development, suggest features, and improve the tool over time.</p>
</li>
<li>
<p><strong>Manifest-Based System</strong>: winget uses a manifest system for package definitions, which can be more standardized and consistent compared to some methods used in other package managers like Chocolatey or Scoop.</p>
</li>
<li>
<p><strong>Native Command-Line Experience</strong>: winget offers a native command-line experience that is consistent with other Windows command-line tools, which might be more familiar to Windows users than the interfaces provided by Chocolatey or Scoop.</p>
</li>
<li>
<p><strong>No Need for Third-Party Repository</strong>: While Chocolatey and Scoop rely heavily on community-maintained repositories, winget can pull software directly from the Microsoft Store in addition to its community repository, potentially offering more official and trusted sources for software.</p>
</li>
<li>
<p><strong>Scripted Installations and Batch Operations</strong>: winget allows for more complex scripted installations and batch operations, similar to Chocolatey and Scoop, but with the potential for deeper integration with Windows-specific features.</p>
</li>
<li>
<p><strong>Community-Driven Repositories</strong>: Like Scoop and Chocolatey, winget allows for community-driven repositories, but being a Microsoft product, it potentially has a broader reach and might garner a more extensive repository of packages.</p>
</li>
</ol>
<p>In summary, winget combines the familiarity and integration of a Microsoft product with the flexibility and community-driven approach of open-source package managers like Chocolatey and Scoop, while also providing a native and streamlined command-line interface for Windows users.</p>
<h3 id="chocolatey">Chocolatey</h3>
<p>Chocolatey is distinguished by its extensive repository encompassing a wide array of both open-source and proprietary software. It excels in offering advanced features like custom package creation, deep integration with Windows and PowerShell for enhanced scripting, and robust version control for precise software management. Particularly appealing to system administrators and power users, Chocolatey supports integration with configuration management tools like Puppet, Chef, and Ansible, facilitating automated, consistent configurations across multiple machines. Its command-line interface is comprehensive and user-friendly, catering to both seasoned scripters and those new to command-line operations. The community-driven approach in maintaining and updating its packages further adds to its appeal, ensuring a broad software selection and timely updates.</p>
<p>Chocolatey distinguishes itself from other Windows package managers through several unique features and capabilities:</p>
<ol>
<li>
<p><strong>Extensive Software Repository</strong>: Chocolatey offers a vast and diverse repository of both open-source and proprietary software, surpassing the variety found in other managers like winget, Scoop, or Ninite.</p>
</li>
<li>
<p><strong>Package Creation and Customization</strong>: Unlike Ninite or AppGet, Chocolatey allows users and organizations to create and host their own packages. This feature is particularly beneficial for businesses that need to manage internal software distributions or specific software configurations.</p>
</li>
<li>
<p><strong>Integration with Windows Features and PowerShell</strong>: Chocolatey&rsquo;s deep integration with Windows features, especially PowerShell, stands out compared to alternatives. This integration allows for more advanced scripting and automation, which is particularly appealing for system administrators and power users.</p>
</li>
<li>
<p><strong>Version Control and Software Management</strong>: Chocolatey excels in its ability to install specific versions of software and manage upgrades and downgrades, offering more control compared to winget and Ninite. This is crucial in environments where specific software versions are required for compatibility or testing purposes.</p>
</li>
<li>
<p><strong>Community-Driven Package Maintenance</strong>: The Chocolatey community plays a significant role in maintaining and updating packages. This community-driven approach often leads to quicker updates and a broader range of software than what might be found in more centrally managed repositories like winget or Ninite.</p>
</li>
<li>
<p><strong>Compatibility with Configuration Management Tools</strong>: Chocolatey uniquely integrates with configuration management tools like Puppet, Chef, and Ansible. This makes it a more versatile choice for enterprise environments where consistent and automated configuration across multiple Windows machines is essential.</p>
</li>
<li>
<p><strong>Comprehensive CLI Support</strong>: While Scoop and winget also offer command line interfaces, Chocolatey&rsquo;s CLI is more robust and feature-rich, providing a level of control and flexibility that is particularly beneficial in automated scripts and batch operations.</p>
</li>
<li>
<p><strong>Security and Compliance Features</strong>: For enterprise users, Chocolatey offers additional security and compliance features, like package internalization and private repositories, which are not as prominent in winget, Scoop, or Ninite.</p>
</li>
</ol>
<p>In summary, Chocolatey&rsquo;s unique combination of an extensive software repository, package creation and customization, deep Windows integration, advanced version control, community support, enterprise features, and powerful CLI distinguishes it from other Windows package managers like winget, Scoop, and Ninite.</p>
<h3 id="scoop">Scoop</h3>
<p>Scoop is a command-line package manager for Windows, known for its simplicity and focus on user convenience. It stands out for its straightforward setup and zero-configuration philosophy, appealing especially to developers and those who prefer a minimalistic approach. Scoop installs programs to the user&rsquo;s home directory, avoiding system-wide changes and making it non-intrusive compared to traditional Windows installers. This approach also facilitates easy backup and sync of installed applications. It primarily focuses on open-source and developer tools, offering a curated selection that simplifies the installation and update process for commonly used utilities and applications. Scoop&rsquo;s repository is community-maintained, ensuring a relevant and up-to-date software selection. Its ease of use, coupled with the ability to handle dependencies elegantly, makes it a favored choice for users seeking a lightweight and efficient package management solution on Windows.</p>
<p>Scoop has several unique features:</p>
<ol>
<li>
<p><strong>Focus on Developer Tools</strong>: Scoop is primarily focused on making it easy for developers to install tools they need. It&rsquo;s optimized for command-line users and simplifies the process of managing development tools.</p>
</li>
<li>
<p><strong>User Space Installation</strong>: Unlike some other package managers, Scoop installs programs in the user&rsquo;s space, not system-wide. This means no administrator privileges are needed for installation, and it doesn&rsquo;t add to the system PATH by default, reducing potential system conflicts.</p>
</li>
<li>
<p><strong>Portable Applications</strong>: Scoop is designed with a preference for portable applications. This means that applications are less likely to interfere with the rest of the system, as they are self-contained.</p>
</li>
<li>
<p><strong>Simplified App Management</strong>: Scoop provides a simple command line interface for installing applications, without the need for a GUI. It also allows for easy version management and updating of installed programs.</p>
</li>
<li>
<p><strong>Git-Powered</strong>: Scoop uses Git to manage its buckets (collections of applications), making it easy for users to add or create their own buckets. This contrasts with the centralized repositories of some other package managers.</p>
</li>
<li>
<p><strong>Manifest-Driven</strong>: In Scoop, each application has a manifest that describes how it should be installed. This makes it easier to add new applications to Scoop compared to some other package managers that may require more complex packaging processes.</p>
</li>
<li>
<p><strong>Focus on Avoiding Bloatware</strong>: Scoop tends to avoid applications that come bundled with unwanted extras, a practice sometimes seen in other free software distributions.</p>
</li>
<li>
<p><strong>Customizability and Scripting</strong>: Because of its command-line nature and use of PowerShell, Scoop is highly customizable and can be integrated into scripts for automation purposes.</p>
</li>
</ol>
<p>In contrast:</p>
<ul>
<li><strong>winget</strong> is Microsoft&rsquo;s own package manager, integrated with Windows and focusing more broadly on all types of software.</li>
<li><strong>Chocolatey</strong> is more feature-rich and caters to both end-users and enterprise needs with a focus on comprehensive package management solutions, including GUI support.</li>
<li><strong>Ninite</strong> is known for its simplicity and is often used for quick, bulk installations of popular applications, usually for setting up new systems.</li>
</ul>
<p>Each of these package managers serves different user needs and preferences, with Scoop being particularly favorable for developers and command-line enthusiasts who prefer a minimalistic and scriptable approach.</p>
<h3 id="ninite">Ninite</h3>
<p>Ninite is a streamlined package management solution for Windows, recognized for its user-friendly approach, particularly suited for non-technical users. It simplifies the process of installing and updating multiple applications with just a few clicks. Users select their desired software from the Ninite website, which then creates a customized installer to install all chosen programs in one go, without any further interaction. Ninite stands out for its automated, batch installation process that smartly avoids toolbars or extra junk, ensuring a clean and hassle-free setup. It primarily caters to popular, commonly-used software, making it a convenient choice for quick setup of new computers or routine maintenance. The service is especially popular for its reliability and security, as it automatically updates all installed programs to their latest versions, reducing the risk of security vulnerabilities. Ninite&rsquo;s appeal lies in its simplicity and effectiveness, making it an excellent choice for those who prefer a straightforward, no-fuss software management tool.</p>
<p>Ninite is unique in several ways:</p>
<ol>
<li>
<p><strong>User-Friendliness</strong>: Ninite is known for its exceptionally user-friendly interface. It allows users to select multiple applications from its website and creates a single installer to install all chosen apps at once. This simplicity contrasts with other package managers, which often require command-line interaction.</p>
</li>
<li>
<p><strong>Silent Installation</strong>: Ninite automates the installation process without requiring user interaction. It automatically declines toolbar installations and other add-ons, installs the latest version of the software, and sets up programs in their default locations. This automatic, &lsquo;silent&rsquo; approach is different from others that might require user input during installations.</p>
</li>
<li>
<p><strong>Batch Updates</strong>: Ninite can also be used for updating installed programs. It checks for and installs updates for all supported apps, again doing so silently and automatically. This is a distinctive feature that may not be as straightforward in other package managers.</p>
</li>
<li>
<p><strong>No Bundleware</strong>: Ninite is known for not including any bundleware or extra software in its installations, which is a significant advantage over some other package managers that might include additional offers or software in their installations.</p>
</li>
<li>
<p><strong>Focus on Free and Popular Software</strong>: Ninite primarily focuses on popular free software, making it a go-to for quickly setting up a new PC with essential tools and utilities. Other package managers may offer a wider range of software, including more niche or specialized tools.</p>
</li>
<li>
<p><strong>No Command-Line Interface (CLI)</strong>: Unlike winget, Chocolatey, and Scoop, which offer command-line interfaces for more control and scripting capabilities, Ninite operates almost entirely through a graphical interface. This makes it more accessible to casual users but less flexible for power users.</p>
</li>
<li>
<p><strong>Automatic Background Updates (Pro Version)</strong>: Ninite Pro offers additional features like automatic background updates and network deployment, which can be particularly useful in a corporate environment for maintaining multiple machines.</p>
</li>
</ol>
<p>These features make Ninite especially appealing for less technical users or those looking for a hassle-free way to install and update a standard set of applications on their PCs. However, it may be less suitable for users who need a broader range of software options or more control over the installation process.</p>
<h2 id="why-scoop-stands-out">Why Scoop Stands Out</h2>
<p>Scoop stands out for a developer like me due to its primary focus on developer tools, making it an ideal choice for those needing a straightforward, command-line-centric tool for managing development applications. The user space installations are a key feature for individuals without administrator rights or those who prefer not to make system-wide changes, enhancing both ease of use and system stability. Additionally, Scoop&rsquo;s preference for portable applications and its Git-powered, manifest-driven approach offer a level of flexibility and transparency that is highly valued in a developer&rsquo;s workflow. Moreover, its design philosophy to avoid bloatware, coupled with its capabilities for customization and automation through scripting, make it an attractive option for users seeking minimalistic and efficient package management. While winget, Chocolatey, and Ninite each have their own strengths, Scoop&rsquo;s specific focus on minimal system impact, developer-friendly features, and ease of use in a command-line environment sets it apart as the go-to choice for many developers and command-line users.</p>
<aside
  style="
    margin: 20px 0;
    padding: 0 14px;
    border-inline-start: 3px solid var(--primary);
  "
>
  <p>While one of Scoop&rsquo;s key features is its capability to install portable applications in a user-isolated space, Scoop also offers the option to install non-portable applications. This flexibility allows users who prefer to standardize on Scoop for application management to enjoy the convenience of using it for both portable and non-portable installations.</p>
</aside>

<h2 id="scoop-deep-dive">Scoop Deep Dive</h2>
<h3 id="installing-scoop">Installing Scoop</h3>
<p>Installing Scoop is a straightforward process. Here&rsquo;s a step-by-step guide:</p>
<ol>
<li>
<p><strong>Open PowerShell</strong>: First, you need to open PowerShell. You can do this by searching for PowerShell in the Start menu and running it as an administrator.</p>
</li>
<li>
<p><strong>Configure Execution Policy</strong> (if needed): Scoop requires the ability to execute scripts. In PowerShell, run the following command to allow script execution:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Set-ExecutionPolicy RemoteSigned -scope CurrentUser
</span></span></code></pre></div><p>This command changes the policy to allow the execution of scripts, while still protecting against the execution of scripts downloaded from the Internet, unless they are signed by a trusted publisher.</p>
<aside
  style="
    margin: 20px 0;
    padding: 0 14px;
    border-inline-start: 3px solid var(--primary);
  "
>
  <p><strong>Note on Security</strong>: Setting the PowerShell execution policy to <code>RemoteSigned</code> for the <code>CurrentUser</code> scope, as done with <code>Set-ExecutionPolicy RemoteSigned -Scope CurrentUser</code>, poses a moderate security risk. This policy allows locally created scripts to run without a digital signature, but requires scripts downloaded from the internet to be signed by a trusted publisher. While this setting prevents the execution of unsigned scripts from external sources, it doesn&rsquo;t safeguard against all risks. Locally created or modified scripts, which might include malicious code, can still run without any signature verification. Additionally, attackers could potentially exploit this policy by tricking users into downloading and locally modifying scripts, thereby bypassing the need for a digital signature. This setting strikes a balance between usability and security, but users should remain cautious of the scripts they run, especially those obtained from untrusted sources.</p>
</aside>

</li>
<li>
<p><strong>Install Scoop</strong>: To install Scoop, run the following command in PowerShell:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Invoke-RestMethod -Uri https<span style="color:#960050;background-color:#1e0010">:</span>//get.scoop.sh | Invoke-Expression
</span></span></code></pre></div><p>This command downloads the Scoop installation script and executes it. <code>Invoke-RestMethod</code> fetches the script directly from <code>https://get.scoop.sh</code>, and <code>Invoke-Expression</code> runs the script.</p>
</li>
<li>
<p><strong>Install Git (Recommended)</strong>: After installing Scoop, it is recommended to install Git. Many packages, including those in the &rsquo;extras&rsquo; bucket, require Git. To install Git, use the following Scoop command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop install git
</span></span></code></pre></div></li>
<li>
<p><strong>Verification</strong>: After the installation script completes, you can verify the installation by typing <code>scoop help</code> in PowerShell. This should display a list of Scoop commands, indicating that Scoop is installed correctly.</p>
</li>
<li>
<p><strong>Optional - Configure Scoop to use a Proxy</strong> (if needed): If you are behind a proxy, you may need to configure Scoop to use it. You can do this by setting the <code>http_proxy</code> and <code>https_proxy</code> environment variables in PowerShell:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>$env:http_proxy=<span style="color:#e6db74">&#34;http://yourproxy:port&#34;</span>
</span></span><span style="display:flex;"><span>$env:https_proxy=<span style="color:#e6db74">&#34;http://yourproxy:port&#34;</span>
</span></span></code></pre></div><p>Replace <code>yourproxy</code> and <code>port</code> with your proxy details.</p>
</li>
<li>
<p><strong>Optional - Associate <code>.git*</code> and <code>.sh</code> Files</strong>: After installing Git, you can optionally create file associations for <code>.git*</code> and <code>.sh</code> files. This can be done by running the <code>install-file-associations.reg</code> file, which is included in the Git installation via Scoop.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>cd path\to\git\installation
</span></span><span style="display:flex;"><span>&amp; .\install-file-associations.reg
</span></span></code></pre></div><p>Replace <code>path\to\git\installation</code> with the actual installation path of Git. This step is especially useful for developers who frequently work with Git and shell scripts.</p>
</li>
</ol>
<p>Scoop is designed to make the installation process easy and straightforward, particularly for command-line tools and developer utilities. If you encounter any issues during the installation, make sure you&rsquo;re running PowerShell as an administrator and that your internet connection is stable. Scoop&rsquo;s GitHub page and community forums can also be useful resources for troubleshooting.</p>
<h3 id="basic-commands">Basic Commands</h3>
<p>Scoop is a command-line package manager for Windows that offers a variety of commands for managing software installations. Here&rsquo;s a guide to some of the basic commands and their usage:</p>
<ol>
<li>
<p><strong>Install a Package</strong>: To install a package with Scoop, use the <code>install</code> command followed by the package name. For example, to install Visual Studio Code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop install vscode
</span></span></code></pre></div><aside
  style="
    margin: 20px 0;
    padding: 0 14px;
    border-inline-start: 3px solid var(--primary);
  "
>
  <p><strong>Note on Additional Customization</strong>: Some packages, like Visual Studio Code, offer additional setup options such as creating context menu entries (right-click menu options) or file associations. After installing such packages, you might find additional scripts or instructions in the package’s installation folder to enable these features. This can enhance your workflow by integrating the software more deeply into your Windows environment. However, it&rsquo;s important to note that using these .reg files or scripts might deviate from the standard Scoop practice of keeping programs self-contained within the user folder. For detailed steps and to understand the implications of these modifications, refer to the documentation of the specific package or check the installation folder.</p>
</aside>

</li>
<li>
<p><strong>List Installed Packages</strong>: To see a list of all packages that you&rsquo;ve installed with Scoop, use the <code>list</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop list
</span></span></code></pre></div></li>
<li>
<p><strong>Search for a Package</strong>: If you&rsquo;re not sure about the exact name of a package, you can search for it using the <code>search</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop search [package-name]
</span></span></code></pre></div><p>Replace <code>[package-name]</code> with the name or part of the name of the package you&rsquo;re looking for.</p>
</li>
<li>
<p><strong>Check for Updates</strong>: To check if any of your installed packages have updates available, use the <code>status</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop status
</span></span></code></pre></div></li>
<li>
<p><strong>Update a Package</strong>: If there&rsquo;s an update available for a package, you can update it using the <code>update</code> command. To update Git, for example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop update git
</span></span></code></pre></div><p>To update all your installed packages at once, use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop update *
</span></span></code></pre></div></li>
<li>
<p><strong>Uninstall a Package</strong>: To remove a package that you no longer need, use the <code>uninstall</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop uninstall [package-name]
</span></span></code></pre></div></li>
<li>
<p><strong>View Package Information</strong>: To view information about a specific package (like version, dependencies, etc.), use the <code>info</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop info [package-name]
</span></span></code></pre></div></li>
<li>
<p><strong>Add a Bucket</strong>: Buckets in Scoop are like repositories that contain manifests for installing software. To add a new bucket, use the <code>bucket add</code> command. For example, to add the &rsquo;extras&rsquo; bucket:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop bucket add extras
</span></span></code></pre></div></li>
<li>
<p><strong>List Buckets</strong>: To see a list of all the buckets you&rsquo;ve added, use the <code>bucket list</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop bucket list
</span></span></code></pre></div></li>
<li>
<p><strong>Cleanup Old Versions</strong>: Over time, you may accumulate older versions of packages. To remove these old versions and free up disk space, use the <code>cleanup</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop cleanup [package-name]
</span></span></code></pre></div><p>To cleanup old versions of all packages, simply run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop cleanup *
</span></span></code></pre></div></li>
</ol>
<p>These basic commands cover most of the everyday usage scenarios for Scoop. It&rsquo;s worth noting that Scoop is particularly popular among developers and power users who prefer a command-line interface for managing software installations. For more advanced commands and options, you can always refer to the Scoop documentation or use the <code>scoop help</code> command.</p>
<h3 id="managing-multiple-versions">Managing Multiple Versions</h3>
<p>Scoop provides additional commands and features that are particularly useful for managing multiple versions of the same software. This capability is especially valuable for developers who may need to switch between different versions of tools or programming languages for different projects. Here are some key commands and concepts related to managing multiple versions in Scoop:</p>
<ol>
<li>
<p><strong>Hold and Unhold a Version</strong>:</p>
<ul>
<li><strong>Hold</strong>: If you want to prevent a specific version of a package from being updated, you can &lsquo;hold&rsquo; it. This ensures that the package stays at the current version even when you update other packages.
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop hold [package-name]
</span></span></code></pre></div></li>
<li><strong>Unhold</strong>: To remove the hold and allow updates again, use &lsquo;unhold&rsquo;:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop unhold [package-name]
</span></span></code></pre></div></li>
</ul>
</li>
<li>
<p><strong>Install Specific Versions</strong>: Scoop allows you to install specific versions of a package (if available in the bucket). Use the <code>install</code> command with the <code>@[version]</code> syntax:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop install [package-name]@[<span style="color:#66d9ef">version</span>]
</span></span></code></pre></div><p>Replace <code>[version]</code> with the desired version number.</p>
</li>
<li>
<p><strong>Switch Between Versions</strong>: If you have multiple versions of a package installed, you can switch between them using the <code>reset</code> command. This command sets the specified version as the current active version.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop reset [package-name]@[<span style="color:#66d9ef">version</span>]
</span></span></code></pre></div></li>
<li>
<p><strong>List All Versions of a Package</strong>: To see all the installed versions of a particular package, use the <code>list</code> command with the package name:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop list [package-name]
</span></span></code></pre></div></li>
<li>
<p><strong>Check Available Versions</strong>: To see what versions of a package are available for installation, you can use the <code>checkver</code> command with the <code>-a</code> (all) flag:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop checkver [package-name] -a
</span></span></code></pre></div></li>
<li>
<p><strong>Remove Specific Versions</strong>: If you want to remove a specific version of a package, use the <code>uninstall</code> command with the specific version:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop uninstall [package-name]@[<span style="color:#66d9ef">version</span>]
</span></span></code></pre></div></li>
</ol>
<p>These commands enhance Scoop&rsquo;s versatility, making it a powerful tool for scenarios where version management is crucial. It&rsquo;s particularly useful in development environments where testing across multiple versions of languages, libraries, or tools is necessary. Remember that the availability of different versions depends on the package and the bucket it belongs to. Not all packages may have multiple versions available in Scoop&rsquo;s default buckets.</p>
<h3 id="buckets-and-repositories">Buckets and Repositories</h3>
<p>Customizing Scoop with buckets and repositories is a key aspect of its flexibility and power. Buckets in Scoop are analogous to repositories in systems like Git. They are collections of &ldquo;manifests&rdquo; (JSON files) that describe how to install each application. By adding and managing buckets, you can extend Scoop&rsquo;s range of available software and tailor it to your specific needs. Here&rsquo;s how to customize Scoop using buckets and repositories:</p>
<ol>
<li>
<p><strong>Understanding Buckets</strong>:</p>
<ul>
<li>A Scoop bucket is a Git repository containing application manifests. Scoop uses these manifests to install applications.</li>
<li>By default, Scoop comes with its main bucket, which contains a wide range of commonly used applications.</li>
</ul>
</li>
<li>
<p><strong>Adding Custom Buckets</strong>:</p>
<ul>
<li>To add a new bucket, use the <code>bucket add</code> command followed by the bucket&rsquo;s name and, optionally, the Git URL if it&rsquo;s a third-party bucket:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop bucket add [bucket-name] [bucket-url]
</span></span></code></pre></div></li>
<li>For well-known buckets, like &rsquo;extras&rsquo;, you don&rsquo;t need to specify the URL:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop bucket add extras
</span></span></code></pre></div></li>
<li>These commands add the specified bucket to your Scoop installation, making all the applications in that bucket available for installation.</li>
</ul>
</li>
<li>
<p><strong>Listing Available Buckets</strong>:</p>
<ul>
<li>You can list all the buckets currently added to your Scoop installation with the <code>bucket list</code> command:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop bucket list
</span></span></code></pre></div></li>
</ul>
</li>
<li>
<p><strong>Searching Across Buckets</strong>:</p>
<ul>
<li>Once you&rsquo;ve added a bucket, you can search for applications across all your added buckets using the <code>search</code> command:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop search [application-name]
</span></span></code></pre></div></li>
</ul>
</li>
<li>
<p><strong>Creating Your Own Bucket</strong>:</p>
<ul>
<li>If you have a set of applications or specific versions that aren&rsquo;t available in existing buckets, you can create your own. To do this, you need to:
<ul>
<li>Create a new Git repository.</li>
<li>Add application manifests in the form of JSON files to this repository.</li>
<li>Use the <code>bucket add</code> command to add your custom bucket to Scoop.</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Contributing to Existing Buckets</strong>:</p>
<ul>
<li>If you want to add a new application or update an existing one in a public bucket, you can fork the bucket&rsquo;s repository, make your changes, and then submit a pull request. This is common in open-source projects and allows for community contributions.</li>
</ul>
</li>
<li>
<p><strong>Removing Buckets</strong>:</p>
<ul>
<li>If you no longer need a specific bucket, you can remove it from your Scoop installation using the <code>bucket rm</code> command:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>scoop bucket rm [bucket-name]
</span></span></code></pre></div></li>
</ul>
</li>
</ol>
<p>Customizing Scoop with buckets allows you to significantly expand the range of software that you can manage with it. Whether you&rsquo;re adding existing third-party buckets, creating your own for unique needs, or contributing to the community, buckets provide a powerful way to tailor Scoop to your workflow.</p>
<h2 id="conclusion">Conclusion</h2>
<p>For my new machine, I&rsquo;ve chosen Scoop as the primary installation tool, primarily because it neatly isolates application installations within my User folder. While I&rsquo;m not strictly committed to using only one installer, I&rsquo;ve found Scoop&rsquo;s approach efficient for most applications.</p>
<p>However, for certain software not available in Scoop&rsquo;s buckets, I&rsquo;m open to using alternatives like winget or specific application installers. A case in point is Visual Studio, a crucial tool for some of my development work. For this, I utilized the Visual Studio Installer, which not only facilitates updating and managing different versions of Visual Studio but also simplifies the modification of workloads.</p>
<p>So far, my experience with Scoop has been positive. I anticipate that as I continue using it, I&rsquo;ll gain a deeper understanding of how separate installation paths can streamline software management on my machines. I encourage you to explore different package managers, especially if you&rsquo;ve been relying on the same one or haven&rsquo;t used one at all. Diversifying your toolkit can be both enlightening and practical.</p>
<h2 id="additional-resources">Additional Resources</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/windows/package-manager/winget/">Use the winget tool to install and manage applications</a></li>
<li><a href="https://chocolatey.org/">Chocolatey: The Package Manager for Windows</a></li>
<li><a href="https://docs.chocolatey.org/en-us/chocolatey-gui/">Chocolatey GUI</a></li>
<li><a href="https://scoop.sh">Scoop: A command-line installer for Windows</a></li>
<li><a href="https://ninite.com/">Ninite: Install and Update All Your Programs at Once</a></li>
</ul>
]]></content:encoded></item><item><title>Excel to CSV Using Python Pandas</title><link>https://russfrith.com/posts/excel-to-csv-using-python-pandas/</link><pubDate>Tue, 26 Jul 2022 15:08:01 -0400</pubDate><guid>https://russfrith.com/posts/excel-to-csv-using-python-pandas/</guid><description>How to read, transform, and write data using Python and Pandas</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Python Pandas is a powerful package used by data scientists to analyze data. My recent use case was far more pedestrian. I had one vendor that was providing a data file in Excel format, and another vendor that needed to consume that data in a completely different schema as a CSV file.</p>
<p>As is often the case, the file I was receiving was formed oddly. I’m sure you can relate. The primary problem was that the columns were not all defined. They were unnamed and formatted for some previously defined form, such that the first column might be a person’s name, the second column might be the first line of the address, or a second person’s name.</p>
<table>
  <thead>
      <tr>
          <th>Column 0</th>
          <th>Column 1</th>
          <th>Column 2</th>
          <th>Column 3</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Mary Smith</td>
          <td>1 Main St</td>
          <td>Newark, NJ</td>
          <td></td>
      </tr>
      <tr>
          <td>John Doe</td>
          <td>Jane Doe</td>
          <td>2 Main St</td>
          <td>Oakland, CA</td>
      </tr>
  </tbody>
</table>
<p>In the table above, column 0 is always a name. Column 1 may be a name, or an address, etc. The table above is a very simplified example of the problem, but we can use it to learn how Pandas can help us.</p>
<p>Our export requirement is to combine the names, charges, credits, descriptions, and balances into comma separated values, with pipe separated values in columns where multiple values exist as follows:</p>
<table>
  <thead>
      <tr>
          <th>Full Name</th>
          <th>Address</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Mary Smith</td>
          <td>1 Main St, Newark, NJ</td>
      </tr>
      <tr>
          <td>John Doe|Jane Doe</td>
          <td>2 Main St, Oakland, CA</td>
      </tr>
  </tbody>
</table>
<h3 id="pandas-series">Pandas Series</h3>
<p>A <em>pandas.Series</em> is a one-dimensional array with axis labels. A Series is equatable to an Excel column. The object supports both integer and label-based indexing and provides methods for performing operations involving the index.</p>
<h3 id="pandas-dataframe">Pandas DataFrame</h3>
<p>A <em>pandas.DataFrame</em> is a two-dimensional data structure, like a two-dimensional array, or a table with rows and columns. You can think of it as a dictionary for Series objects. A dataframe is one of the primary datas structures of a Pandas project.</p>
<h3 id="read-excel">Read Excel</h3>
<p>Pandas can open files and load them into a dataframe. In addition to reading and writing to Excel and CSV files, Pandas supports many other file formats, including JSON, XML, SQL, among others. We will open an XLSX files as shown below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>df_input <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>read_excel(open(<span style="color:#e6db74">&#39;input.xlsx&#39;</span>, <span style="color:#e6db74">&#39;rb&#39;</span>), sheet_name<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;Sheet2&#39;</span>)
</span></span></code></pre></div><h3 id="dataframe-iloc">DataFrame iloc</h3>
<p>Since the file I was given did not include headings, and because of the problem described above, such headings would be meaningless, we must access columns by the integer index. Pandas provides the <em>DataFrame.loc</em> method to access labeled axes, and the <em>DataFrame.iloc</em> method to access columns based on the integer index.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>mydict <span style="color:#f92672">=</span> [{<span style="color:#e6db74">&#39;a&#39;</span>: <span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#39;b&#39;</span>: <span style="color:#ae81ff">2</span>, <span style="color:#e6db74">&#39;c&#39;</span>: <span style="color:#ae81ff">3</span>, <span style="color:#e6db74">&#39;d&#39;</span>: <span style="color:#ae81ff">4</span>},
</span></span><span style="display:flex;"><span>          {<span style="color:#e6db74">&#39;a&#39;</span>: <span style="color:#ae81ff">100</span>, <span style="color:#e6db74">&#39;b&#39;</span>: <span style="color:#ae81ff">200</span>, <span style="color:#e6db74">&#39;c&#39;</span>: <span style="color:#ae81ff">300</span>, <span style="color:#e6db74">&#39;d&#39;</span>: <span style="color:#ae81ff">400</span>},
</span></span><span style="display:flex;"><span>          {<span style="color:#e6db74">&#39;a&#39;</span>: <span style="color:#ae81ff">1000</span>, <span style="color:#e6db74">&#39;b&#39;</span>: <span style="color:#ae81ff">2000</span>, <span style="color:#e6db74">&#39;c&#39;</span>: <span style="color:#ae81ff">3000</span>, <span style="color:#e6db74">&#39;d&#39;</span>: <span style="color:#ae81ff">4000</span> }]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>df <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>DataFrame(mydict)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(df)
</span></span><span style="display:flex;"><span>      a     b     c     d
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">0</span>     <span style="color:#ae81ff">1</span>     <span style="color:#ae81ff">2</span>     <span style="color:#ae81ff">3</span>     <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span>   <span style="color:#ae81ff">100</span>   <span style="color:#ae81ff">200</span>   <span style="color:#ae81ff">300</span>   <span style="color:#ae81ff">400</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2</span>  <span style="color:#ae81ff">1000</span>  <span style="color:#ae81ff">2000</span>  <span style="color:#ae81ff">3000</span>  <span style="color:#ae81ff">4000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(df<span style="color:#f92672">.</span>iloc[<span style="color:#ae81ff">0</span>])
</span></span><span style="display:flex;"><span>a    <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>b    <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>c    <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>d    <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(df<span style="color:#f92672">.</span>iloc[<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(df<span style="color:#f92672">.</span>iloc[[<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">2</span>], [<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">3</span>]])
</span></span><span style="display:flex;"><span>      b     d
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">0</span>     <span style="color:#ae81ff">2</span>     <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2</span>  <span style="color:#ae81ff">2000</span>  <span style="color:#ae81ff">4000</span>
</span></span></code></pre></div><h3 id="numpy-select">NumPy Select</h3>
<p>One option is to select what are names and what are addresses based on a list of conditions. Numpy provides the <em>numpy.select()</em> statement to achieve this. The <em>condlist</em> is a list of conditions that determine from which array in the choice list the output elements are taken. If multiple conditions are satisfied the first one in <em>condlist</em> is used. The <em>choicelist</em> is the list of arrays from which the output elements are taken. If all conditions evaluate to false, <em>default</em> value is returned.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>numpy<span style="color:#f92672">.</span>select(condlist, choicelist, default<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>)
</span></span></code></pre></div><h3 id="functions">Functions</h3>
<p>Unlike many popular programming languages that use braces (or “curly brackets”). Python uses indentation to indicate a block of code. Therefore, a Python function looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">my_function</span>():
</span></span><span style="display:flex;"><span>  print(<span style="color:#e6db74">&#34;Hello from a function&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>my_function()
</span></span></code></pre></div><h2 id="names--addresses">Names &amp; Addresses</h2>
<p>My first thought was to find a library that parsed addresses. I found a promising option called <a href="https://pypi.org/project/usaddress/">usaddress</a>. It provided all of the features I needed,  but it did not appear to be in active development, and was only compatible with Python 2.7. Since I had a limited data set and could assume that addresses would start with a house number, and that names would not, I was able to use NumPy to identify names vs addresses.</p>
<p>Using what we have seen above, we can combine this to solve the name and address problem. I find this solution to be a bit brute force and ugly, but my goal was to learn enough to solve the problem and move on. A deeper understanding of Python and these libraries may come later.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> numpy <span style="color:#66d9ef">as</span> np
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">is_name</span>(column):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#f92672">~</span>df_input<span style="color:#f92672">.</span>iloc[:, column]<span style="color:#f92672">.</span>str[:<span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>str<span style="color:#f92672">.</span>isnumeric()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>name_conditions <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    is_name(<span style="color:#ae81ff">0</span>) <span style="color:#f92672">&amp;</span> <span style="color:#f92672">~</span>is_name(<span style="color:#ae81ff">1</span>),
</span></span><span style="display:flex;"><span>    is_name(<span style="color:#ae81ff">0</span>) <span style="color:#f92672">&amp;</span> is_name(<span style="color:#ae81ff">1</span>) <span style="color:#f92672">&amp;</span> <span style="color:#f92672">~</span>is_name(<span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>names <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    df_input<span style="color:#f92672">.</span>iloc[:, <span style="color:#ae81ff">0</span>],
</span></span><span style="display:flex;"><span>    df_input<span style="color:#f92672">.</span>iloc[:, <span style="color:#ae81ff">0</span>] <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;|&#39;</span> <span style="color:#f92672">+</span> df_input<span style="color:#f92672">.</span>iloc[:, <span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>addresses <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    df_input<span style="color:#f92672">.</span>iloc[:, <span style="color:#ae81ff">1</span>] <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;, &#39;</span> <span style="color:#f92672">+</span> df_input<span style="color:#f92672">.</span>iloc[:, <span style="color:#ae81ff">2</span>],
</span></span><span style="display:flex;"><span>    df_input<span style="color:#f92672">.</span>iloc[:, <span style="color:#ae81ff">2</span>] <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;, &#39;</span> <span style="color:#f92672">+</span> df_input<span style="color:#f92672">.</span>iloc[:, <span style="color:#ae81ff">3</span>]
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>df_input[<span style="color:#e6db74">&#39;Full Name&#39;</span>] <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>select(name_conditions, names)
</span></span><span style="display:flex;"><span>df_input[<span style="color:#e6db74">&#39;Address&#39;</span>] <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>select(name_conditions, addresses)
</span></span></code></pre></div><h2 id="charges-and-credits">Charges and Credits</h2>
<p>In addition to the name and address challenge outlined above, I also needed to parse the transactions. In the file supplied by the source company, they had formatting built into the export file. Date, Description, Charge, Credit, etc. are all combined into one column, and we don’t know how many columns there will be. This was done so that the previous consumer could simply print the entire column, and it would be appropriately formatted. They were unwilling or unable to provide a cleaner export. Therefore, I needed to parse the column to separate the charges and the credits.</p>
<p>One small thing that made this easier was the fact that the unknown number of charges and credits were the final columns. Therefore, we could assume that if the first possible column with a charge or credit was at index 10, the remaining columns would all be credits, debits, or empty.</p>
<p>Imagine the column is 80 characters wide. The first 10 characters include the Date. Characters 15 through 25 include a Description. Characters 35 through 45 include the charge if applicable. Characters 50 through 60 include the credit if applicable. Finally, characters 70 through 80 include the subtotal. The columns could be parsed as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">transaction_date</span>(col_value):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> col_value[:<span style="color:#ae81ff">10</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">transaction_description</span>(col_value):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> col_value[<span style="color:#ae81ff">15</span>:<span style="color:#ae81ff">25</span>]<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">charge_amount</span>(col_value):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> col_value[<span style="color:#ae81ff">35</span>:<span style="color:#ae81ff">45</span>]<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">is_charge</span>(col_value):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> charge_amount(col_value) <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">credit_amount</span>(col_value):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> col_value[<span style="color:#ae81ff">50</span>:<span style="color:#ae81ff">60</span>]<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">is_credit</span>(col_value):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> credit_amount(col_value) <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">subtotal</span>(col_value):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> col_value[<span style="color:#ae81ff">70</span>:<span style="color:#ae81ff">80</span>]<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">charge_list</span>():
</span></span><span style="display:flex;"><span>    transaction_dates <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    transaction_amounts <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    transaction_descriptions <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    transaction_subtotals <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> row <span style="color:#f92672">in</span> range(len(df_input)):
</span></span><span style="display:flex;"><span>        transaction_amounts_list <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>        transaction_dates_list <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>        transaction_descriptions_list <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>        transaction_subtotal_list <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> column <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">10</span>, len(df_input<span style="color:#f92672">.</span>columns)):
</span></span><span style="display:flex;"><span>            col_value <span style="color:#f92672">=</span> df_input<span style="color:#f92672">.</span>iloc[row, column]
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> isinstance(col_value, str):
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> is_charge(col_value):
</span></span><span style="display:flex;"><span>                    transaction_amounts_list<span style="color:#f92672">.</span>append(charge_amount(col_value))
</span></span><span style="display:flex;"><span>                    transaction_dates_list<span style="color:#f92672">.</span>append(transaction_date(col_value))
</span></span><span style="display:flex;"><span>                    transaction_descriptions_list<span style="color:#f92672">.</span>append(transaction_description(col_value))
</span></span><span style="display:flex;"><span>                    transaction_subtotal_list<span style="color:#f92672">.</span>append(subtotal(col_value))
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">elif</span> is_credit(col_value):
</span></span><span style="display:flex;"><span>                    transaction_amounts_list<span style="color:#f92672">.</span>append(credit_amount(col_value))
</span></span><span style="display:flex;"><span>                    transaction_dates_list<span style="color:#f92672">.</span>append(transaction_date(col_value))
</span></span><span style="display:flex;"><span>                    transaction_descriptions_list<span style="color:#f92672">.</span>append(transaction_description(col_value))
</span></span><span style="display:flex;"><span>                    transaction_subtotal_list<span style="color:#f92672">.</span>append(subtotal(col_value))
</span></span><span style="display:flex;"><span>        separator <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;|&#39;</span>
</span></span><span style="display:flex;"><span>        transaction_amounts<span style="color:#f92672">.</span>append(separator<span style="color:#f92672">.</span>join(transaction_amounts_list))
</span></span><span style="display:flex;"><span>        transaction_dates<span style="color:#f92672">.</span>append(separator<span style="color:#f92672">.</span>join(transaction_dates_list))
</span></span><span style="display:flex;"><span>        transaction_descriptions<span style="color:#f92672">.</span>append(separator<span style="color:#f92672">.</span>join(transaction_descriptions_list))
</span></span><span style="display:flex;"><span>        transaction_subtotals<span style="color:#f92672">.</span>append(separator<span style="color:#f92672">.</span>join(transaction_subtotal_list))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    df_input[<span style="color:#e6db74">&#39;Charges&#39;</span>] <span style="color:#f92672">=</span> transaction_amounts
</span></span><span style="display:flex;"><span>    df_input[<span style="color:#e6db74">&#39;Dates&#39;</span>] <span style="color:#f92672">=</span> transaction_dates
</span></span><span style="display:flex;"><span>    df_input[<span style="color:#e6db74">&#39;Descriptions&#39;</span>] <span style="color:#f92672">=</span> transaction_descriptions
</span></span><span style="display:flex;"><span>    df_input[<span style="color:#e6db74">&#39;Balances&#39;</span>] <span style="color:#f92672">=</span> transaction_subtotals
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>charge_list()
</span></span></code></pre></div><h3 id="write-csv">Write CSV</h3>
<p>Once we make the necessary changes to our dataframe, we can export it to a new file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>df_input<span style="color:#f92672">.</span>to_csv(<span style="color:#e6db74">&#39;output.csv&#39;</span>)
</span></span></code></pre></div><h2 id="wrapping-up">Wrapping Up</h2>
<p>Python Pandas is great at manipulating data, but it can also be used to import., transform, and export data when the situation arises. I found it a relatively simple way to take a poorly planned dataset and manipulate it for use in another application. My use case combined with a smallish dataset made my brute-force approach usable. However, if you have a large dataset, or if you are using Pandas as intended, you should operate on a Series.</p>
<h3 id="pyinstaller">PyInstaller</h3>
<p>My script will need to be run regularly by someone else. Therefore, I need to create an executable version of my Python script that requires no dependencies. Two options are <a href="https://pypi.org/project/auto-py-to-exe/">auto-py-to-exe</a> and <a href="https://pypi.org/project/pyinstaller/">PyInstaller</a>. I chose PyInstaller for the command line interface.</p>
<pre tabindex="0"><code>pip install pyinstaller

pyinstaller --onefile name_of_script.py
</code></pre><p>Now anyone can run this executable on their Windows machine without the need for Python to be installed.</p>
<p>I generally avoid hacks, but as much as it hurt me to share this, it did solve my problem. This was not my favorite project, and it pains me to share it, but if this helps one person understand how Python Pandas can manipulate data when they have a simiar situation, then the pain was worth suffering.</p>
]]></content:encoded></item><item><title>Is Identity GUID or Is It Int?</title><link>https://russfrith.com/posts/is-identity-guid-or-is-it-int/</link><pubDate>Tue, 19 Apr 2022 13:31:33 -0400</pubDate><guid>https://russfrith.com/posts/is-identity-guid-or-is-it-int/</guid><description>Choosing a Primary Key Type and Exposing It Through Your API</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I have long waffled on whether a GUID or integer make for the best primary key. I wrote this with the goal of refining or challenging my current opinion.</p>
<p>The identity, or primary key, uniquely identifies an entity or record. To be clear, ‘identity’ is a Domain Driven Design concept that describes an immutable identifying attribute of an entity (not to be confused with a SQL Server identity column, which is an automatically incrementing integer). A ‘primary key’ is a database concept that serves the same purpose for a record and enforces referential integrity. In this post, I will use the term ‘primary key’, since we are focusing on the database. The concept of an identity attribute is more than just a database concern, it is a domain concern, but it is important to consider where these concerns intersect.</p>
<p>While my bias as a software developer guides me to focus on the domain model, the realities of database persistence cannot be overlooked. For this post, we are going to assume that we are using <strong>SQL Server</strong> for persistence. Therefore, its performance must be factored into the identifier decision. Consider that other databases are going to have similar issues.</p>
<aside
  style="
    margin: 20px 0;
    padding: 0 14px;
    border-inline-start: 3px solid var(--primary);
  "
>
  <p>Before talking about databases, let us discuss to what extent the domain model should be tied to the persistence layer. Often, we assume that we will be using a database, even a particular database such as SQL Server. When should we make this decision?[<a href="#references">1</a>] Should we make our preliminary decision based on performance, flexibility, licensing, or something else? Should we rely on the database to generate the primary key? I will admit that I typically select SQL Server as my database when starting a greenfield project, primarily because of its ubiquity with my clients, but these questions should remain in the back of your mind. During development, one should consider if another form of persistence, such as a NoSQL database, makes more sense in a given scenario.</p>
</aside>

<h2 id="guid">GUID</h2>
<p>From a domain perspective, I have an affinity for GUID (or <a href="https://www.rfc-editor.org/rfc/rfc4122">UUID</a>) as my identifier. It is almost assuredly unique in the world and therefore will not collide with other GUIDs should my system be distributed, replicated, merged, or otherwise interact with other systems or contexts. It also has the benefit that it can be created in the application, rather than being a side-effect of adding the entity to the database.</p>
<h3 id="column-size">Column Size</h3>
<p>The most basic and easy to understand issue with GUID is the size of the column, which is 16 bytes. Consider how keys will proliferate in your database. Not only will it consume space in the table where it is a primary key, but in every table where it is a foreign key. If every key in your database is a GUID, you can imagine the memory implications versus a 4-byte <em>int</em> or a 8-byte <em>bigint</em>. Consider further that memory usage will not only occur on disk, but in the RAM of the server.</p>
<h3 id="clustered-index-fragmentation">Clustered Index Fragmentation</h3>
<p>A common argument against the use of GUID for the primary key is fragmentation.[<a href="#references">2</a>] Clustered indexes sort and store the data rows based on their key values. There can be only one clustered index per table, because the data rows themselves can be stored in only one order. When setting a primary key for a table, SQL Server creates a clustered index on that column by default. That may be what you want initially. When you have an auto-incrementing integer as a clustering key, that key is in the same order as the clustered index. When you add a key that is not ordered, such as a GUID, fragmentation occurs. There are some options to mitigate fragmentation when using a GUID. COMB (for COMBined, abbreviated)[<a href="#references">3</a>] or sequential GUIDs allow for ordered identifiers as do integers, thus mitigating clustered index fragmentation. A COMB GUID has the benefit of being generated in code like a regular GUID using libraries such as <a href="https://github.com/richardtallent/RT.Comb">RT.Comb</a>. NEWSEQUENTIALID() is a Transact-SQL function built into SQL Server that creates a GUID that is greater than any GUID previously generated by this function on a specified computer since it was started. Both of these options allow for the creation of ordered GUIDs that mitigate fragmentation. There is also the option of declaring a separate primary key and clustering key. This way you could have a GUID as your primary key, and an integer as your clustering key. Other causes of fragmentation exist that have nothing to do with the key, but that is a topic for another time.</p>
<h3 id="index-structure-size">Index Structure Size</h3>
<p>Another strong argument against the use of GUID as the clustering key is the table size resulting from the aforementioned clustered index. The pointer from an index row in a nonclustered index to a data row is called a row locator. For a clustered table, the row locator is the clustered index key. Since a GUID is 16 bytes versus 4 bytes for an <em>int</em>, the index structure is going to be larger than using an integer. Since every nonclustered index will contain the clustering key, a larger clustering key will widen the nonclustered index. The result is that a clustering key of a GUID will make the index structure multiple times larger than will an integer.</p>
<h3 id="hard-to-remember">Hard to Remember</h3>
<p>Finally, GUIDs are just plain ugly. Integers are much easier to remember and type. If your table has 10,000 rows, it is far easier to remember a key of ‘8,711’ than it is to remember ‘55e7ad83-c81b-4148-a658-07766c221558’. You may be able to hold the former in your head, but the latter will require the use of the clipboard.</p>
<h2 id="api">API</h2>
<p>Occasionally, you will see an API that is hackable, either by design or accident. This probably is not what you want. If someone has order ‘100’, you don’t want them to have the ability to enter ‘99’ or ‘101’ to see the orders before and after them. GUIDs make this less likely. Simply encoding the GUID to a BASE64 string gives us something that is highly unlikely to be hacked in this manner.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cs" data-lang="cs"><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">string</span> GuidToBase64(Guid guid)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">string</span> base64 = Convert.ToBase64String(guid.ToByteArray());
</span></span><span style="display:flex;"><span>  base64 = base64.Replace(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#e6db74">&#34;_&#34;</span>).Replace(<span style="color:#e6db74">&#34;+&#34;</span>, <span style="color:#e6db74">&#34;-&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> base64.Substring(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">22</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> Guid Base64ToGuid(<span style="color:#66d9ef">string</span> base64)
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  Guid guid = <span style="color:#66d9ef">default</span>(Guid);
</span></span><span style="display:flex;"><span>  base64 = base64.Replace(<span style="color:#e6db74">&#34;-&#34;</span>, <span style="color:#e6db74">&#34;/&#34;</span>).Replace(<span style="color:#e6db74">&#34;_&#34;</span>, <span style="color:#e6db74">&#34;+&#34;</span>) + <span style="color:#e6db74">&#34;==&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>    guid = <span style="color:#66d9ef">new</span> Guid(Convert.FromBase64String(base64));
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">catch</span> (Exception ex) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> Exception(<span style="color:#e6db74">&#34;Failed to covert BASE64 string to GUID&#34;</span>, ex);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> guid;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Using the code above, a GUID of ‘55e7ad83-c81b-4148-a658-07766c221558’ would return a BASE64 string of ‘g63nVRvISEGmWAd2bCIVWA’. This string could then be used on your API. (Keep in mind that this ID is case sensitive.)</p>
<p>If one uses integers as their primary key, there are options to obfuscate that key on their API. One such library is <a href="https://hashids.org/">Hashids</a>, a small open-source library that generates short, unique, non-sequential IDs from numbers, which is available for a variety of programming languages.</p>
<p>While you may see tutorials or production websites that use hackable IDs, you will want to use a mechanism like those suggested above to decouple the keys of your database from your public API.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I began writing this with the premise of showing how a COMB or similar sequential GUID is a good default choice for an identity attribute or primary key. There remain many strong arguments in favor of the GUID. However, the table and index structure size of GUIDs make integers a wise choice. My recommendations are:</p>
<ul>
<li>If you choose GUID as a primary and clustering key, use some form of sequential GUID,</li>
<li>Choose GUID if you need to generate the key in code,</li>
<li>Choose GUID if you require its distributed benefits,</li>
<li>If using GUID, consider an <em>int</em> or <em>bigint</em> clustering key,</li>
<li>If performance is your top concern, use <em>int</em> or <em>bigint</em> as your primary and clustering key,</li>
<li>Only expose hashed keys to the outside world.</li>
</ul>
<p>This is an interesting topic to dive into deeper. I recommend reading the posts below, and if you use SQL Server, consume just about everything that Kimberly Tripp writes or says.</p>
<h2 id="references">References</h2>
<ol>
<li>Robert C. Martin. <a href="https://blog.cleancoder.com/uncle-bob/2012/05/15/NODB.html">No DB</a>, <em>The Clean Code Blog</em>, May 15, 2012.</li>
<li>Kimberly Tripp. <a href="https://www.sqlskills.com/blogs/kimberly/guids-as-primary-keys-andor-the-clustering-key/">GUIDs as PRIMARY KEYs and/or the clustering key</a>. <em>SQLskills</em>, March 5, 2009.</li>
<li>Jimmy Nilsson. <a href="https://www.informit.com/articles/article.aspx?p=25862">The Cost of GUIDs as Primary Keys</a>, <em>InformIT</em>, March 8, 2002.</li>
<li>Jeff Atwood. <a href="https://blog.codinghorror.com/primary-keys-ids-versus-guids/">Primary Keys: IDs versus GUIDs</a>, <em>Coding Horror</em>, March 19, 2007.</li>
<li>Tom Harrison. <a href="https://tomharrisonjr.com/uuid-or-guid-as-primary-keys-be-careful-7b2aa3dcb439">UUID or GUID as Primary Keys? Be Careful!</a>, <em>Tom Harrison’s Blog</em>, February 12, 2017.</li>
</ol>
]]></content:encoded></item><item><title>The Disposable Toothbrush</title><link>https://russfrith.com/posts/disposable-toothbrush/</link><pubDate>Wed, 30 Mar 2022 19:07:03 -0400</pubDate><guid>https://russfrith.com/posts/disposable-toothbrush/</guid><description>An Introduction to Me and My Inaction</description><content:encoded><![CDATA[<p>I know the inventor of the disposable toothbrush. They would stay at hotels that provided soap, shampoo, conditioner, etc., but a recurring issue was that they would forget their toothbrush, and hotels didn’t furnish a solution. It seemed obvious that hotels should have individually wrapped toothbrushes, just as they had other toiletries. Why didn&rsquo;t anyone make an inexpensive short-term use toothbrush with toothpaste already applied? So, this person invented it – only they didn&rsquo;t. They did in fact come up with the idea, as I&rsquo;m sure did many others, but they never acted on it. Someone else eventually did. To this day, every time this person sees one, they declare how it was their invention.</p>
<p>This is my long overdue blog. It will not be perfect. It probably will not be widely read. Upon reviewing old items to be donated or thrown away, I came across some books and articles about blogging – from twenty years ago! It was important to me then, but not important enough for me to get started. My thoughts have remained stuck in my head or written in personal notes. I will now endeavor to put some of them out into the world. They may be ugly, unpopular, or just plain wrong, but they will be authentically mine.</p>
<p>Inaction is easy. We can easily fall victim to the default effect. There is a reason that GDPR requires that websites affirmatively receive users’ consent before using unnecessary cookies, rather than just making the settings available. There is a reason that creating a new online account or installing new software requires you to opt-out of marketing and data collection options, rather than opt-in. There is a reason that we defend our viewpoints without thinking, naturally tend to resist change, and support the status quo. The reason in all these cases is that we tend to accept the default. An object at rest remains at rest, and an object in motion remains in motion in the same potentially unproductive direction that it has been moving in all along. We can let someone else start a company. We can let others write their blogs and novels. We can let yet another invent the disposable toothbrush.</p>
]]></content:encoded></item></channel></rss>