<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Personal Cite</title>
<link>https://your-website-url.example.com/</link>
<atom:link href="https://your-website-url.example.com/index.xml" rel="self" type="application/rss+xml"/>
<description>A blog built with Quarto</description>
<generator>quarto-1.9.22</generator>
<lastBuildDate>Thu, 02 Apr 2026 06:00:00 GMT</lastBuildDate>
<item>
  <title>More PoweRful</title>
  <dc:creator>Mitchell L. Mecham</dc:creator>
  <link>https://your-website-url.example.com/posts/using-python-in-R/</link>
  <description><![CDATA[ 





<section id="r-users-can-now-access-any-python-package-without-knowing-python" class="level1">
<h1>R Users Can Now Access Any Python Package Without Knowing Python</h1>
<p>A lot of valuable data tools are only available in Python. That is fine if you know Python, but a huge chunk of academics and statisticians live entirely in R and have no interest in learning a new language just to download a dataset.</p>
<p>That was the problem we ran into with <a href="https://github.com/Dewey-Data/deweypy/tree/main">Dewey Data</a>. Dewey hosts large academic datasets and has a Python downloader called deweypy. Great package. Useless for lots of the people who actually need the data.</p>
<p>So we built <a href="https://github.com/Coxabc/deweyr">deweyr</a>.</p>
<section id="the-idea" class="level2">
<h2 class="anchored" data-anchor-id="the-idea">The Idea</h2>
<p>The goal was simple. R users should be able to download Dewey data with a single function call and zero Python setup. No conda environments, no pip installs, no terminal commands they don’t understand.</p>
<p>The solution was <code>system2</code> and <code>uv</code>.</p>
<p><code>system2</code> is a base R function that lets you run shell commands from inside an R script. Most R users have never touched it. Combined with <code>uv</code>, a Python package and environment manager that is extremely fast and lightweight, you can spin up a fully isolated Python environment, install any Python package, run it, and capture the output back in R. The user never sees any of it.</p>
</section>
<section id="the-terminal-is-just-a-function-call" class="level2">
<h2 class="anchored" data-anchor-id="the-terminal-is-just-a-function-call">The Terminal is Just a Function Call</h2>
<p>Before getting into <code>system2</code> it helps to understand what is actually happening when you run something in the terminal.</p>
<p>When you run a Python script from the terminal it looks like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> file.py arg1 arg2 arg3</span></code></pre></div></div>
<p>That is it. You have a program (<code>python</code>), a file to run (<code>file.py</code>), and any arguments the script expects. Most command line tools follow this same pattern. The program name comes first, then everything else is arguments passed to it.</p>
<p>Say you had a simple Python script called <code>greet.py</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys</span>
<span id="cb2-2"></span>
<span id="cb2-3">name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sys.argv[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb2-4">language <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sys.argv[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb2-5"></span>
<span id="cb2-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> language <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"english"</span>:</span>
<span id="cb2-7">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Hello, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">!"</span>)</span>
<span id="cb2-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> language <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"spanish"</span>:</span>
<span id="cb2-9">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Hola, </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">!"</span>)</span></code></pre></div></div>
<p>You would run it from the terminal like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> greet.py Mitchell english</span>
<span id="cb3-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Hello, Mitchell!</span></span></code></pre></div></div>
<p><code>system2</code> just maps directly onto this. The first argument is the program, and <code>args</code> is everything that comes after it as a character vector:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(</span>
<span id="cb4-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"python"</span>,</span>
<span id="cb4-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">args =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"greet.py"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mitchell"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"english"</span>),</span>
<span id="cb4-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stdout =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span></span>
<span id="cb4-5">)</span>
<span id="cb4-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># "Hello, Mitchell!"</span></span></code></pre></div></div>
<p>You can wrap this in a clean R function so the user never thinks about the terminal:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">greet <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(name, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">language =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"english"</span>) {</span>
<span id="cb5-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(</span>
<span id="cb5-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"python"</span>,</span>
<span id="cb5-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">args =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"greet.py"</span>, name, language),</span>
<span id="cb5-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stdout =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span></span>
<span id="cb5-6">  )</span>
<span id="cb5-7">}</span>
<span id="cb5-8"></span>
<span id="cb5-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">greet</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Mitchell"</span>)</span>
<span id="cb5-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># "Hello, Mitchell!"</span></span></code></pre></div></div>
<p>That is the core idea. Anything you can run from a terminal you can run from R with <code>system2</code>. The R user just calls a function.</p>
</section>
<section id="from-scripts-to-packages" class="level2">
<h2 class="anchored" data-anchor-id="from-scripts-to-packages">From Scripts to Packages</h2>
<p>Most useful tools are not standalone files though. They are distributed as packages you install and run as a module. In the terminal that looks like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> deweypy <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--api-key</span> mykey <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--download-directory</span> ./data speedy-download folder123</span></code></pre></div></div>
<p>The <code>-m</code> flag tells Python to run a package as a module instead of a file. Everything after it is just arguments the package expects. <code>system2</code> maps onto this exactly the same way:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(</span>
<span id="cb7-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"python"</span>,</span>
<span id="cb7-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">args =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(</span>
<span id="cb7-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-m"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"deweypy"</span>,</span>
<span id="cb7-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--api-key"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mykey"</span>,</span>
<span id="cb7-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--download-directory"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"./data"</span>,</span>
<span id="cb7-7">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"speedy-download"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"folder123"</span></span>
<span id="cb7-8">  ),</span>
<span id="cb7-9">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stdout =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>,</span>
<span id="cb7-10">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stderr =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span></span>
<span id="cb7-11">)</span></code></pre></div></div>
<p>Because each argument is its own element in the character vector, adding optional arguments conditionally is clean and readable. Here is the actual core of how <code>deweyr</code> builds its command:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1">run_deweypy <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(python_path,</span>
<span id="cb8-2">                        api_key,</span>
<span id="cb8-3">                        download_path,</span>
<span id="cb8-4">                        folder_id,</span>
<span id="cb8-5">                        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">num_workers =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>,</span>
<span id="cb8-6">                        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">partition_key_before =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>,</span>
<span id="cb8-7">                        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">partition_key_after =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>) {</span>
<span id="cb8-8"></span>
<span id="cb8-9">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Build base arguments</span></span>
<span id="cb8-10">  args <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(</span>
<span id="cb8-11">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-m"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"deweypy"</span>,</span>
<span id="cb8-12">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--api-key"</span>, api_key,</span>
<span id="cb8-13">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--download-directory"</span>, download_path,</span>
<span id="cb8-14">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"speedy-download"</span>, folder_id</span>
<span id="cb8-15">  )</span>
<span id="cb8-16"></span>
<span id="cb8-17">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add optional arguments only if provided</span></span>
<span id="cb8-18">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.null</span>(num_workers)) {</span>
<span id="cb8-19">    args <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(args, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--num-workers"</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.character</span>(num_workers))</span>
<span id="cb8-20">  }</span>
<span id="cb8-21"></span>
<span id="cb8-22">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.null</span>(partition_key_before)) {</span>
<span id="cb8-23">    args <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(args, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--partition-key-before"</span>, partition_key_before)</span>
<span id="cb8-24">  }</span>
<span id="cb8-25"></span>
<span id="cb8-26">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.null</span>(partition_key_after)) {</span>
<span id="cb8-27">    args <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(args, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--partition-key-after"</span>, partition_key_after)</span>
<span id="cb8-28">  }</span>
<span id="cb8-29"></span>
<span id="cb8-30">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(python_path, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">args =</span> args, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stdout =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stderr =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb8-31">}</span></code></pre></div></div>
<p>You start with the arguments every call needs, then build up from there. Optional parameters only get added if the user passed them in. The command that actually runs in the terminal is whatever <code>args</code> looks like at the end. This is cleaner than hardcoding a Python one-liner as a string and makes it easy to add new arguments later without touching the <code>system2</code> call itself.</p>
<p>Wrap this in a user-facing function that handles the uv setup, path creation, and URL parsing first, and the R user just calls <code>dewey_download()</code> with their API key and folder ID. Everything else is invisible to them.</p>
</section>
<section id="understanding-system2" class="level2">
<h2 class="anchored" data-anchor-id="understanding-system2">Understanding system2</h2>
<p><code>system2</code> is a base R function that calls a system command the same way you would from a terminal. The basic signature looks like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(</span>
<span id="cb9-2">  command,   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the program to run</span></span>
<span id="cb9-3">  args,      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a character vector of arguments</span></span>
<span id="cb9-4">  stdout,    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># where to send standard output</span></span>
<span id="cb9-5">  stderr,    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># where to send standard error</span></span>
<span id="cb9-6">  input      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># optional: feed text into stdin</span></span>
<span id="cb9-7">)</span></code></pre></div></div>
<p>The thing that trips people up at first is that by default <code>stdout</code> and <code>stderr</code> are both <code>""</code>, which means the output goes to the console and is not captured in R. If you want to actually work with the output you need to set <code>stdout = TRUE</code>, which returns it as a character vector. Each line of output becomes one element of the vector.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This prints to console but returns nothing useful</span></span>
<span id="cb10-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uv"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">args =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--version"</span>))</span>
<span id="cb10-3"></span>
<span id="cb10-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This captures the output so you can use it in R</span></span>
<span id="cb10-5">version <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uv"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">args =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--version"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stdout =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb10-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># version is now something like "uv 0.5.1"</span></span></code></pre></div></div>
<p><code>stderr</code> works the same way. By default errors go to the console. Set <code>stderr = TRUE</code> to capture them, or <code>stderr = FALSE</code> to suppress them entirely. If you want both stdout and stderr together in one vector you can set <code>stderr = TRUE</code> alongside <code>stdout = TRUE</code> and they will be interleaved.</p>
<p>The return value when you are not capturing output is the exit code of the process. 0 means it ran fine, anything else means something went wrong. This is useful for checking if a command succeeded before moving on:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1">exit_code <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uv"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">args =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--version"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stdout =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stderr =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb11-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (exit_code <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stop</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uv is not installed or not on PATH"</span>)</span></code></pre></div></div>
<p>One other useful argument is <code>input</code>, which lets you pipe a string into the command’s stdin. This is how you can chain commands together without writing intermediate files.</p>
<p>The main thing to keep in mind is that <code>system2</code> is blocking. R waits for the command to finish before moving on. For long downloads that is fine. For something you want to run in the background you would need a different approach.</p>
</section>
<section id="how-it-works" class="level2">
<h2 class="anchored" data-anchor-id="how-it-works">How It Works</h2>
<p>When someone calls <code>dewey_download()</code> for the first time, here is what actually happens under the hood:</p>
<ol type="1">
<li>R checks if <code>uv</code> is installed. If not, it installs it automatically.</li>
<li><code>uv</code> downloads a lightweight Python 3.13 installation. No system Python needed.</li>
<li><code>uv</code> creates an isolated virtual environment and installs <code>deweypy</code> into it.</li>
<li><code>system2</code> runs the Python downloader with the user’s arguments.</li>
<li>The files land in whatever folder the user specified.</li>
</ol>
<p>From the R user’s perspective, they just called a function.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(deweyr)</span>
<span id="cb12-2"></span>
<span id="cb12-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dewey_download</span>(</span>
<span id="cb12-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">api_key =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"your-api-key"</span>,</span>
<span id="cb12-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">folder_id =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"your-folder-id"</span></span>
<span id="cb12-6">)</span></code></pre></div></div>
<p>That is it. No Python. No terminal. No environment to manage.</p>
</section>
<section id="signed-urls-and-duckdb" class="level2">
<h2 class="anchored" data-anchor-id="signed-urls-and-duckdb">Signed URLs and DuckDB</h2>
<p>One thing deweypy does that makes this even more useful is generate signed URLs for the files. Instead of downloading everything locally, you can pull the URLs back into R and read them directly with DuckDB. This is useful for large datasets where you only need a slice.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1">get_signed_urls <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(api_key, folder_id) {</span>
<span id="cb13-2">  result <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system2</span>(</span>
<span id="cb13-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"uv"</span>,</span>
<span id="cb13-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">args =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(</span>
<span id="cb13-5">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"--with"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"deweypy"</span>,</span>
<span id="cb13-6">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"python"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-c"</span>,</span>
<span id="cb13-7">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(</span>
<span id="cb13-8">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"import json; from dewey import Dewey; "</span>,</span>
<span id="cb13-9">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"d = Dewey('"</span>, api_key, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"'); "</span>,</span>
<span id="cb13-10">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"urls = d.get_file_urls('"</span>, folder_id, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"'); "</span>,</span>
<span id="cb13-11">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"print(json.dumps(urls))"</span></span>
<span id="cb13-12">      )</span>
<span id="cb13-13">    ),</span>
<span id="cb13-14">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stdout =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span></span>
<span id="cb13-15">  )</span>
<span id="cb13-16"></span>
<span id="cb13-17">  jsonlite<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fromJSON</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste</span>(result, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">collapse =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>))</span>
<span id="cb13-18">}</span></code></pre></div></div>
<p>Then in R you can pass those URLs straight to DuckDB:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb14-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(duckdb)</span>
<span id="cb14-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(DBI)</span>
<span id="cb14-3"></span>
<span id="cb14-4">urls <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_signed_urls</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">api_key =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"your-key"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">folder_id =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"your-folder"</span>)</span>
<span id="cb14-5"></span>
<span id="cb14-6">con <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dbConnect</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">duckdb</span>())</span>
<span id="cb14-7">df <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dbGetQuery</span>(con, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SELECT * FROM read_parquet('"</span>, urls[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"') LIMIT 1000"</span>))</span></code></pre></div></div>
<p>No full download needed. Just query what you need.</p>
</section>
<section id="the-bigger-point" class="level2">
<h2 class="anchored" data-anchor-id="the-bigger-point">The Bigger Point</h2>
<p>deweypy is just the example. This pattern works for any Python package that does not have an R equivalent. The only thing an R user needs to install is <code>uv</code>. Everything else is handled by the package. That is a low enough bar that you can actually ship it to non-technical users and expect it to work.</p>
<p>If you are building R packages and running into Python tools that do something yours cannot, <code>system2</code> plus <code>uv</code> is worth knowing.</p>
<p>The full package is on GitHub at <a href="https://github.com/Coxabc/deweyr">Coxabc/deweyr</a> if you want to see how it is all wired together.</p>


</section>
</section>

 ]]></description>
  <category>R</category>
  <category>Python</category>
  <category>UV</category>
  <category>devops</category>
  <guid>https://your-website-url.example.com/posts/using-python-in-R/</guid>
  <pubDate>Thu, 02 Apr 2026 06:00:00 GMT</pubDate>
</item>
<item>
  <title>From GitHub to PyPI: Publishing a Python Package with OIDC</title>
  <dc:creator>Mitchell L. Mecham</dc:creator>
  <link>https://your-website-url.example.com/posts/publishing-to-pypi/</link>
  <description><![CDATA[ 





<p>Once a package works well enough to share, the next step is getting it onto PyPI so anyone can install it with <code>pip install</code> or <code>uv add</code>. This post walks through how I published <a href="https://pypi.org/project/canvasconnector/">canvasconnector</a> — a Python package for connecting to the Canvas LMS API — starting from a package that only lived on GitHub, all the way to automated releases through GitHub Actions using OIDC trusted publishing.</p>
<section id="what-you-need-before-starting" class="level2">
<h2 class="anchored" data-anchor-id="what-you-need-before-starting">What You Need Before Starting</h2>
<p>Before touching PyPI, the package should be installable from GitHub and have a working <code>pyproject.toml</code>. The canvasconnector README already had installation instructions pointing at the GitHub repo:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> pip install git+https://github.com/byuirpytooling/canvasconnector.git</span></code></pre></div></div>
<p>That is fine for early development, but it is not discoverable and it requires users to know the exact GitHub URL. PyPI solves both of those problems.</p>
<p>You also need a PyPI account. Head to <a href="https://pypi.org">pypi.org</a>, sign up, and verify your email before continuing. If it gives you recovery codes, save them somewhere safe or risk losing access.</p>
</section>
<section id="the-first-upload-manual-with-twine" class="level2">
<h2 class="anchored" data-anchor-id="the-first-upload-manual-with-twine">The First Upload: Manual with Twine</h2>
<p>PyPI requires the package to exist before you can configure automated publishing. So the first upload is manual. Start by building the package with uv:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> build</span></code></pre></div></div>
<p>This creates a <code>dist/</code> folder containing two files: a <code>.whl</code> wheel file and a <code>.tar.gz</code> source distribution. Then install twine as a global tool:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb3-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> tool install twine</span></code></pre></div></div>
<section id="generating-an-api-token" class="level3">
<h3 class="anchored" data-anchor-id="generating-an-api-token">Generating an API Token</h3>
<p>Before uploading you need an API token. Since the package does not exist on PyPI yet, you cannot scope the token to a specific project. Go to your <strong>account settings</strong> (not a project page — the project does not exist yet) and find the <strong>API tokens</strong> section. Click <strong>Add API token</strong>, name it something like <code>canvasconnector</code>, set the scope to <strong>All projects</strong>, and copy the token immediately. You only see it once.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/pypi-api-token.png" class="img-fluid figure-img"></p>
<figcaption>The API tokens section in PyPI account settings showing a token scoped to all projects</figcaption>
</figure>
</div>
<p>Now upload:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">twine</span> upload dist/<span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">*</span></span></code></pre></div></div>
<p>Twine will prompt for a username and token. Enter <code>__token__</code> as the username (literally, that string) and paste your API token as the password.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/twine-upload-prompt.png" class="img-fluid figure-img"></p>
<figcaption>Twine prompting for the API token</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/twine-upload-success.png" class="img-fluid figure-img"></p>
<figcaption>Successful upload showing both the wheel and source distribution</figcaption>
</figure>
</div>
<p>Once it completes, the package is live. You can verify by going to your PyPI account page.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/pypi-first-release.png" class="img-fluid figure-img"></p>
<figcaption>canvasconnector appearing on PyPI with its first release</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/pypi-your-projects.png" class="img-fluid figure-img"></p>
<figcaption>The package listed under your PyPI projects</figcaption>
</figure>
</div>
<p>At this point <code>pip install canvasconnector</code> works for anyone in the world. The manual upload is done and the API token can be deleted since we are about to replace it with something better.</p>
</section>
</section>
<section id="setting-up-oidc-trusted-publishing" class="level2">
<h2 class="anchored" data-anchor-id="setting-up-oidc-trusted-publishing">Setting Up OIDC Trusted Publishing</h2>
<p>OIDC (OpenID Connect) lets PyPI verify that a publish request is coming from a specific GitHub Actions workflow in a specific repository, without any stored credentials. No token to manage, rotate, or accidentally leak.</p>
<section id="step-1-add-a-trusted-publisher-on-pypi" class="level3">
<h3 class="anchored" data-anchor-id="step-1-add-a-trusted-publisher-on-pypi">Step 1: Add a Trusted Publisher on PyPI</h3>
<p>Now that the package exists, go to your package on PyPI, click <strong>Manage</strong>, then <strong>Publishing</strong>. This is where you configure the GitHub connection — it lives under the project, not your account settings. Add a new trusted publisher under the GitHub tab and fill in:</p>
<ul>
<li><strong>Owner</strong>: your GitHub organization or username (for canvasconnector this is <code>byuirpytooling</code>)</li>
<li><strong>Repository</strong>: <code>canvasconnector</code></li>
<li><strong>Workflow name</strong>: <code>publishpypi.yml</code></li>
<li><strong>Environment name</strong>: <code>pypi</code></li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/pypi-add-publisher.png" class="img-fluid figure-img"></p>
<figcaption>The Add a new publisher form on PyPI with fields for owner, repository, workflow, and environment</figcaption>
</figure>
</div>
<p>Make sure the owner matches the actual GitHub organization that owns the repo, not just your personal username. If they differ, the OIDC exchange will fail with an <code>invalid-publisher</code> error.</p>
</section>
<section id="step-2-create-a-github-environment" class="level3">
<h3 class="anchored" data-anchor-id="step-2-create-a-github-environment">Step 2: Create a GitHub Environment</h3>
<p>GitHub Actions needs a named environment called <code>pypi</code> to match what PyPI expects. Go to your repository <strong>Settings &gt; Environments</strong> and create a new environment named <code>pypi</code>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/github-environment-add.png" class="img-fluid figure-img"></p>
<figcaption>Adding the pypi environment in GitHub repository settings</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/github-environments-settings.png" class="img-fluid figure-img"></p>
<figcaption>Both the pypi and github-pages environments configured</figcaption>
</figure>
</div>
<p>The environment acts as a verified identity context. When the workflow runs inside it, GitHub issues a token that PyPI can validate against the trusted publisher configuration you just set up.</p>
</section>
<section id="step-3-the-github-actions-workflow" class="level3">
<h3 class="anchored" data-anchor-id="step-3-the-github-actions-workflow">Step 3: The GitHub Actions Workflow</h3>
<p>Here is the full <code>publishpypi.yml</code> workflow:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode yaml code-with-copy"><code class="sourceCode yaml"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Upload Python Package</span></span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">on</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">release</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-5"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">types</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">published</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]</span></span>
<span id="cb5-6"></span>
<span id="cb5-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">permissions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-8"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">contents</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> read</span></span>
<span id="cb5-9"></span>
<span id="cb5-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">jobs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-11"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">release-build</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runs-on</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> ubuntu-latest</span></span>
<span id="cb5-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/checkout@v4</span></span>
<span id="cb5-15"></span>
<span id="cb5-16"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/setup-python@v5</span></span>
<span id="cb5-17"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">python-version</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"3.x"</span></span>
<span id="cb5-19"></span>
<span id="cb5-20"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Build release distributions</span></span>
<span id="cb5-21"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">        run</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">: </span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">|</span></span>
<span id="cb5-22">          pip install uv</span>
<span id="cb5-23">          uv build</span>
<span id="cb5-24"></span>
<span id="cb5-25"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Upload distributions</span></span>
<span id="cb5-26"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/upload-artifact@v4</span></span>
<span id="cb5-27"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-28"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> release-dists</span></span>
<span id="cb5-29"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist/</span></span>
<span id="cb5-30"></span>
<span id="cb5-31"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">  </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pypi-publish</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-32"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runs-on</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> ubuntu-latest</span></span>
<span id="cb5-33"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">needs</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> release-build</span></span>
<span id="cb5-34"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">environment</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-35"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pypi</span></span>
<span id="cb5-36"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">permissions</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-37"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">id-token</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> write</span></span>
<span id="cb5-38"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">    </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">steps</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-39"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Retrieve release distributions</span></span>
<span id="cb5-40"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> actions/download-artifact@v4</span></span>
<span id="cb5-41"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-42"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> release-dists</span></span>
<span id="cb5-43"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">path</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist/</span></span>
<span id="cb5-44"></span>
<span id="cb5-45"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">      </span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">name</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> Publish release distributions to PyPI</span></span>
<span id="cb5-46"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uses</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> pypa/gh-action-pypi-publish@release/v1</span></span>
<span id="cb5-47"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">        </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span></span>
<span id="cb5-48"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">          </span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">packages-dir</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">:</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;"> dist/</span></span></code></pre></div></div>
<p>The key parts are <code>id-token: write</code> in the permissions block, which enables OIDC, and <code>environment: name: pypi</code>, which scopes the workflow to the named environment. No password or secret is needed anywhere.</p>
</section>
</section>
<section id="publishing-a-new-release" class="level2">
<h2 class="anchored" data-anchor-id="publishing-a-new-release">Publishing a New Release</h2>
<p>With everything wired up, releasing a new version is a deliberate three-step process:</p>
<ol type="1">
<li>Bump the version in <code>pyproject.toml</code> (for example, <code>0.0.1</code> to <code>0.0.2</code>)</li>
<li>Commit and push the change</li>
<li>Go to your GitHub repo and find the <strong>Releases</strong> section in the right sidebar, then click <strong>Create a new release</strong></li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/github-create-release.png" class="img-fluid figure-img"></p>
<figcaption>The GitHub repo page with the Releases section highlighted in the sidebar</figcaption>
</figure>
</div>
<p>Create a tag matching the version (like <code>v0.0.2</code>), write release notes, and click <strong>Publish release</strong>. The workflow fires automatically from there.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/actions-success.png" class="img-fluid figure-img"></p>
<figcaption>GitHub Actions showing the release-build and pypi-publish jobs both succeeding</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://your-website-url.example.com/posts/publishing-to-pypi/images/pypi-two-releases.png" class="img-fluid figure-img"></p>
<figcaption>PyPI showing both 0.0.1 and 0.0.2 releases after the automated publish</figcaption>
</figure>
</div>
<p>The reason releases are manual rather than triggered on every push is intentional. A PyPI publish is permanent. You cannot overwrite or delete a version once it is live. Keeping the release step manual means you are making a conscious decision each time rather than accidentally shipping a half-finished commit.</p>
</section>
<section id="verifying-the-install" class="level2">
<h2 class="anchored" data-anchor-id="verifying-the-install">Verifying the Install</h2>
<p>After publishing, the fastest way to confirm everything works is to create a fresh project and install from PyPI:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mkdir</span> canvasconnector-test</span>
<span id="cb6-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">cd</span> canvasconnector-test</span>
<span id="cb6-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> init</span>
<span id="cb6-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> add canvasconnector</span></code></pre></div></div>
<p>Then run a quick import to confirm it loads correctly. For canvasconnector, a successful connection looks like this:</p>
<pre><code>https://byui.instructure.com
America/Denver
Connected successfully as: Mitchell Mecham</code></pre>
<p>If that works, the package is live, installable, and functioning correctly for any user who finds it.</p>
</section>
<section id="summary" class="level2">
<h2 class="anchored" data-anchor-id="summary">Summary</h2>
<p>The full process from GitHub-only to automated PyPI releases:</p>
<ol type="1">
<li>Build with <code>uv build</code> and upload manually with <code>twine upload dist/*</code> using an API token</li>
<li>Create a PyPI account and generate a temporary API token under <strong>account settings</strong> scoped to all projects</li>
<li>Once the package exists on PyPI, go to <strong>Manage &gt; Publishing</strong> on the project page to configure OIDC trusted publishing</li>
<li>Create a <code>pypi</code> environment in GitHub repository settings</li>
<li>Add the <code>publishpypi.yml</code> workflow with <code>id-token: write</code> permissions and no stored credentials</li>
<li>For each future release: bump the version, push, and create a GitHub Release to trigger the workflow</li>
</ol>
<p>The canvasconnector package is available at <a href="https://pypi.org/project/canvasconnector/">pypi.org/project/canvasconnector</a> and the full source is at <a href="https://github.com/byuirpytooling/canvasconnector">github.com/byuirpytooling/canvasconnector</a>.</p>


</section>

 ]]></description>
  <category>python</category>
  <category>pypi</category>
  <category>github-actions</category>
  <category>devops</category>
  <guid>https://your-website-url.example.com/posts/publishing-to-pypi/</guid>
  <pubDate>Mon, 23 Mar 2026 06:00:00 GMT</pubDate>
</item>
<item>
  <title>Canvas Connector React</title>
  <dc:creator>Mitchell L. Mecham</dc:creator>
  <link>https://your-website-url.example.com/posts/canvas-connector-react/</link>
  <description><![CDATA[ 





<section id="what-is-canvas-lms-connector" class="level2">
<h2 class="anchored" data-anchor-id="what-is-canvas-lms-connector">What is Canvas LMS Connector?</h2>
<p>Canvas LMS Connector is a full-stack web application I built to make Canvas data more accessible and social. Instead of digging through Canvas’s cluttered interface, users can see their courses and classmates in a clean, friendly UI.</p>
<p>The project lives at <a href="https://github.com/MLMecham/datathink-project">github.com/MLMecham/datathink-project</a>.</p>
</section>
<section id="the-stack" class="level2">
<h2 class="anchored" data-anchor-id="the-stack">The Stack</h2>
<p><strong>Frontend — React</strong> The UI is built in React, giving users a fast, responsive experience. It handles authentication input (your Canvas API token) and displays your courses and friends in a clean dashboard.</p>
<p><strong>Backend — FastAPI</strong> FastAPI serves as the bridge between the React frontend and Canvas. It exposes endpoints the frontend calls to fetch user data, and handles all the Canvas API communication server-side.</p>
<p><strong>The Secret Sauce — <code>canvasconnector</code></strong> The backend is powered by <a href="https://github.com/byuirpytooling/canvasconnector"><code>canvasconnector</code></a>, a Python library that wraps the Canvas REST API into clean, Pythonic calls — so FastAPI doesn’t have to deal with raw HTTP requests to Canvas directly.</p>
</section>
<section id="what-it-does" class="level2">
<h2 class="anchored" data-anchor-id="what-it-does">What It Does</h2>
<ul>
<li>🎓 Shows you your enrolled <strong>courses</strong></li>
<li>👥 Displays your Canvas <strong>friends and classmates</strong></li>
<li>🔑 Uses your personal <strong>Canvas API token</strong> to authenticate</li>
</ul>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s Next?</h2>
<p>An interactive version of this post using marimo is on the roadmap — imagine entering your Canvas API token right here on the page and seeing your courses load instantly. Stay tuned.</p>


</section>

 ]]></description>
  <category>react</category>
  <category>fastapi</category>
  <category>canvas</category>
  <category>python</category>
  <guid>https://your-website-url.example.com/posts/canvas-connector-react/</guid>
  <pubDate>Wed, 04 Mar 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title></title>
  <dc:creator>J. Hathaway</dc:creator>
  <link>https://your-website-url.example.com/posts/test/</link>
  <description><![CDATA[ undefined ]]></description>
  <guid>https://your-website-url.example.com/posts/test/</guid>
  <pubDate>Wed, 04 Mar 2026 07:00:00 GMT</pubDate>
</item>
<item>
  <title>Post With Code</title>
  <dc:creator>Harlow Malloc</dc:creator>
  <link>https://your-website-url.example.com/posts/post-with-code/</link>
  <description><![CDATA[ 





<p>This is a post with executable code.</p>



 ]]></description>
  <category>news</category>
  <category>code</category>
  <category>analysis</category>
  <guid>https://your-website-url.example.com/posts/post-with-code/</guid>
  <pubDate>Wed, 18 Feb 2026 07:00:00 GMT</pubDate>
  <media:content url="https://your-website-url.example.com/posts/post-with-code/image.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Welcome To My Blog</title>
  <dc:creator>Tristan O&#39;Malley</dc:creator>
  <link>https://your-website-url.example.com/posts/welcome/</link>
  <description><![CDATA[ 





<p>This is the first post in a Quarto blog. Welcome!</p>
<p><img src="https://your-website-url.example.com/posts/welcome/thumbnail.jpg" class="img-fluid"></p>
<p>Since this post doesn’t specify an explicit <code>image</code>, the first image in the post will be used in the listing page of posts.</p>



 ]]></description>
  <category>news</category>
  <guid>https://your-website-url.example.com/posts/welcome/</guid>
  <pubDate>Sun, 15 Feb 2026 07:00:00 GMT</pubDate>
</item>
</channel>
</rss>
