{
    "version": "https://jsonfeed.org/version/1",
    "title": "Stephen A. Fuqua Blog",
    "home_page_url": "https://blog.safnet.com/",
    "description": "Stephen A. Fuqua writes about technology, the natural world, and the Bahá'í Faith",
    "items": [
        {
            "id": "https://blog.safnet.com/2026/09/07/sandboxes",
            "content_html": "<p>TL;DR summary: Docker Sandboxes are a convenient and effective means of safely running a coding harness in permissions bypass mode.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"yolo-is-now-dangerously-so-the-norm\">YOLO is now (dangerously so) the norm<a href=\"https://blog.safnet.com/2026/09/07/sandboxes#yolo-is-now-dangerously-so-the-norm\" class=\"hash-link\" aria-label=\"Direct link to YOLO is now (dangerously so) the norm\" title=\"Direct link to YOLO is now (dangerously so) the norm\" translate=\"no\">​</a></h2>\n<p>Running coding harnesses in YOLO (bypass permissions) mode has become the new norm:</p>\n<ul>\n<li class=\"\">Configuring the right permissions is \"hard\" (read: tedious), especially when you throw in git worktrees that cause compound commands (<code>cd &lt;worktree&gt; &amp;&amp; &lt;take an action&gt;</code>).</li>\n<li class=\"\">Approving permissions demands attention: it is stultifying and ruins much of the benefit of using a coding agent - letting it run while you go do something else.</li>\n</ul>\n<p>In YOLO mode, the coding harnesses in summer 2026 seem to be very good at preventing the agents from reaching files outside the code repository; in all of 2026 I do not think I have denied permission to anything, and my spot-checking of the logs revealed nothing concerning.</p>\n<p>Then again, they might just be very good at hiding their tracks... see recent news about agents \"escaping\" to crack into other companies.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"probing-the-limits-of-permissions-and-worktrees\">Probing the limits of permissions and worktrees<a href=\"https://blog.safnet.com/2026/09/07/sandboxes#probing-the-limits-of-permissions-and-worktrees\" class=\"hash-link\" aria-label=\"Direct link to Probing the limits of permissions and worktrees\" title=\"Direct link to Probing the limits of permissions and worktrees\" translate=\"no\">​</a></h2>\n<p>Assume for a moment that the agent won't do anything to trash your computer. What about production systems? Across dozens of repositories, I have two <code>.env</code> files with production system information. Attempting to deny access to read those files is tricky, at least with Claude Code: the system can often find a way using indirect access through an approved rule. For example, denying <code>\"Read(./.env.*)\"</code> will work, but will that prevent it from calling <code>cat .env</code> from a Bash command? And did you remember to restore access to a file like <code>.env.example</code> so that the agent can insert new environment variables?</p>\n<p><img decoding=\"async\" loading=\"lazy\" alt=\"This might work...\" src=\"https://blog.safnet.com/assets/images/2026-09-06-env-permissions-5c6d51e0f2cdb4b1815ddf8748382544.jpg\" width=\"373\" height=\"293\" class=\"img_ev3q\"></p>\n<p>Running the agent on a <a href=\"https://github.blog/ai-and-ml/github-copilot/what-are-git-worktrees-and-why-should-i-use-them/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">git worktree</a> will help: the worktree won't have the environment files. But what about <em>environment variables</em>? Or signed-in tools? Do you want the tool to run your cloud provider's CLI tool on your behalf to check on production settings?</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"getting-smart\">Getting smart<a href=\"https://blog.safnet.com/2026/09/07/sandboxes#getting-smart\" class=\"hash-link\" aria-label=\"Direct link to Getting smart\" title=\"Direct link to Getting smart\" translate=\"no\">​</a></h2>\n<p>I have a personal project, building a web application for volunteer management. The deployment is set up on Azure. A few weeks ago, I tasked Claude Code with inspecting deployment scripts to try to diagnose and correct a production error.</p>\n<p>Guiltily, I recognized my guard was down: were there previous occasions in which Claude Code, in bypass mode, was accessing my Azure subscription without adequate safeguards? That was a wake up call.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requirements\">Requirements<a href=\"https://blog.safnet.com/2026/09/07/sandboxes#requirements\" class=\"hash-link\" aria-label=\"Direct link to Requirements\" title=\"Direct link to Requirements\" translate=\"no\">​</a></h2>\n<p>Goal: select an approach for safely running in YOLO mode, satisfying this non-exhaustive list of requirements:</p>\n<ul>\n<li class=\"\">Run in Windows and/or WSL</li>\n<li class=\"\">Allow network access</li>\n<li class=\"\">No access to my filesystem or Windows operations, other than the code repository I am working in</li>\n<li class=\"\">No access to environment variables</li>\n<li class=\"\">Works for multiple coding harness - in particular, Claude Code and GitHub Copilot</li>\n<li class=\"\">Must be able to install dev tools, such as Node.js and the .NET SDK</li>\n<li class=\"\">Can connect to GitHub for read/write access to pull requests</li>\n<li class=\"\">\"Easy\" to manage, with low impact on the operating environment</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"a-quick-survey-of-solutions\">A quick survey of solutions<a href=\"https://blog.safnet.com/2026/09/07/sandboxes#a-quick-survey-of-solutions\" class=\"hash-link\" aria-label=\"Direct link to A quick survey of solutions\" title=\"Direct link to A quick survey of solutions\" translate=\"no\">​</a></h2>\n<p>Simplistic thinking: git worktree and WSL. Let's test that theory:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">$ az account list</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"cloudName\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"AzureCloud\"</span><span class=\"token plain\">,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"homeTenantId\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"*****\"</span><span class=\"token plain\">,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"id\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"*****\"</span><span class=\"token plain\">,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"isDefault\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> true,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"name\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"*****\"</span><span class=\"token plain\">,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"state\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Enabled\"</span><span class=\"token plain\">,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"tenantDefaultDomain\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"*****.onmicrosoft.com\"</span><span class=\"token plain\">,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"tenantDisplayName\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Default Directory\"</span><span class=\"token plain\">,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"tenantId\"</span><span class=\"token builtin class-name\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token plain\">*****</span><span class=\"token string\" style=\"color:#e3116c\">\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"</span><span class=\"token plain\">user</span><span class=\"token string\" style=\"color:#e3116c\">\": {</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      \"</span><span class=\"token plain\">name</span><span class=\"token string\" style=\"color:#e3116c\">\": \"</span><span class=\"token plain\">*****</span><span class=\"token string\" style=\"color:#e3116c\">\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      \"</span><span class=\"token builtin class-name\">type</span><span class=\"token string\" style=\"color:#e3116c\">\": \"</span><span class=\"token plain\">user\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><br></div></code></pre></div></div>\n<p>Nope, <code>az</code> is accessing the same session authentication as seen from Windows (outside of WSL).</p>\n<p>Next: Claude Code has a number of <a href=\"https://code.claude.com/docs/en/sandbox-environments\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">ideas for sandboxing</a>.</p>\n<table><thead><tr><th>Approach</th><th>Meets Requirements</th></tr></thead><tbody><tr><td>Sandboxed Bash</td><td>❌ no - Claude specific</td></tr><tr><td>Sandbox runtime</td><td>❌ no - Claude specific</td></tr><tr><td>Dev container</td><td>✅ looks intriguing</td></tr><tr><td>Custom container</td><td>✅ let's try this</td></tr><tr><td>Virtual machine</td><td>❌ no - too intensive</td></tr><tr><td>Claude Code on the web</td><td>⚠️ yes - but see below</td></tr></tbody></table>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Assigning work to Claude Code on the web, or GitHub Copilot Coding Agent, can be a productive and safe experience. In fact, I delegate most final implementations to remote agents. But I prefer doing my planning locally; my planning artifacts are often at the wrong level of detail for long-term storage, so I prefer never to add them to the repository, as would occur if working only online.</p><p>The repository should have requirements (PRD) and architectural designs (ADRs and related documents), not low-level or interim implementation planning documents. The implementation plan that derives from a planning session is what I hand off to the remote agent.</p></div></div>\n<p>What about <a href=\"https://containers.dev/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">dev containers</a>? Has promise, but I've yet to use a dev container in any setting: there will be a learning curve. Not rejected, but not yet ready to commit to it.</p>\n<p>And custom containers? I am <em>very</em> used to those. To jumpstart, a search: \"use docker as a sandbox for claude code\".</p>\n<p>Oh! <a href=\"https://docs.docker.com/ai/sandboxes/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Docker Sandboxes</a> are a thing. A product. Pre-configured and free. What's the catch? You must sign-in; corporate governance tools are available but require a paid account. Out of the box, it meets all requirements, though #5 (Must be able to install dev tools) will require a little effort. Why build my own custom container when I can use this product?</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solution\">Solution<a href=\"https://blog.safnet.com/2026/09/07/sandboxes#solution\" class=\"hash-link\" aria-label=\"Direct link to Solution\" title=\"Direct link to Solution\" translate=\"no\">​</a></h2>\n<p>Two weeks later: I won't say this is <em>the best</em> solution. Having passed on trying out other options, that claim would be disingenuous. But it <em>is</em> working for me.</p>\n<p>As a regular Docker user, the command line interface is familiar and easy to learn. The containers run with a small footprint micro VM architecture, and the solution is completely separate from Docker Desktop (which is not required).</p>\n<p>At last, I feel that I'm practicing safe permission bypass. That is, assuming the agent doesn't do anything crazy with my GitHub token 😉.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"tips\">Tips<a href=\"https://blog.safnet.com/2026/09/07/sandboxes#tips\" class=\"hash-link\" aria-label=\"Direct link to Tips\" title=\"Direct link to Tips\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">\n<p>Plan to create and use a GitHub token with minimal permissions required to get the job done.</p>\n</li>\n<li class=\"\">\n<p>git clone mode is the safer option instead of directly mounting the directory; otherwise the sandbox will have access to local environment files. Clone mode is not the same as a worktree, but accomplishes the same goal.</p>\n</li>\n<li class=\"\">\n<p>The runtime environment can be customized with a <a href=\"https://docs.docker.com/ai/sandboxes/customize/templates/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Dockerfile-based template</a>; for example, I have a template called <a href=\"https://github.com/stephenfuqua/sandboxes-with-pwsh-and-dotnet\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">sandboxes-with-pwsh-and-dotnet</a>. Drawback that I can live with: To get the latest versions I will need to remember to rebuild the image from time to time.</p>\n<p>Example command, running in clone mode with a custom template:</p>\n<div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sbx run claude </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--clone</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--template</span><span class=\"token plain\"> stephenfuqua/sbx-claude-pwsh-dotnet</span><br></div></code></pre></div></div>\n</li>\n<li class=\"\">\n<p>Plan to have a separate sandbox instance for every repository. While it is possible to mount multiple repositories into a sandbox, the session will always default to the first mounted directory; switching to another is tedious. Even after switching directories, you risk the agent reading files from the original repository by mistake.</p>\n</li>\n</ul>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>warning</div><div class=\"admonitionContent_BuS1\"><p>Separate sandbox containers for each harness, spanning dozens of repositories, could eventually eat a lot of disk space. Will need to keep an eye on that going forward.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"postscript-future-direction\">Postscript: Future Direction<a href=\"https://blog.safnet.com/2026/09/07/sandboxes#postscript-future-direction\" class=\"hash-link\" aria-label=\"Direct link to Postscript: Future Direction\" title=\"Direct link to Postscript: Future Direction\" translate=\"no\">​</a></h2>\n<p>While this is working well, eventually I may get frustrated at some quirks to this approach. Perhaps I will come around to building my own container containing the compiler / runtime tools I need, along with both coding harnesses that I actively use. Which starts to sound a lot like a devcontainer...</p>\n<hr>\n<p><em>This blog post was handcrafted and conceived, with minor only minor updates from Copilot Review Agent and a Claude Code + Sonnet 5 editorial review.</em></p>",
            "url": "https://blog.safnet.com/2026/09/07/sandboxes",
            "title": "Sandboxing for YOLO Safety",
            "summary": "TL;DR summary: Docker Sandboxes are a convenient and effective means of safely running a coding harness in permissions bypass mode.",
            "date_modified": "2026-09-07T00:00:00.000Z",
            "tags": [
                "programming",
                "technology"
            ]
        },
        {
            "id": "https://blog.safnet.com/2026/08/13/agentic-code-review",
            "content_html": "<p>From time to time I pop in to peek at one of the dozens of pull requests that come through my mailbox daily, furthering the work of the Ed-Fi Alliance to help connect student data. What feature, improvement, or bug fix are the teams working on? How has this engineer approached a given problem, what tests have they written, what documentation have they provided, are the I/O seams sufficiently fault tolerant? Did they run the GitHub Copilot Code Review Agent, and fix the issues detected there?</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p><strong>TL;DR</strong> Created a team of specialist reviewers <a href=\"https://github.com/Ed-Fi-Alliance-OSS/AI-Tools-for-Ed-Fi-SDLC/blob/main/skills/team-pr-review/SKILL.md\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">skill</a> and use it on anything non-trivial. After its analysis, it offers to post a nicely-formatted review comment on the PR. I then go back and edit that review comments, to avoid slop and/or excessive cognitive load.</p></div></div>\n<p>As a product manager — barely a part-time engineer — there is little time for this. And there is a lot to review, given that agentic coding work shifts the bottleneck from coding to review and test. So here too I have tried to use my coding harnesses well.</p>\n<p>The Copilot code review helps, but it is insufficient. I see it as a minimum bar: once that review has been cleared, the pull request (PR) is ready for another look. I scan with my own eyes, sampling bits of code to see the null and exception handling, log messages (and levels), and database connectivity. Only then (ideally) do I ask my coding harness (Claude Code, GitHub Copilot, etc.) to review the PR.</p>\n<p>It started simply: switch to the repo's local directory, open coding harness, and prompt \"review PR 123 and post your comments back to the PR.\" So far so good. Run it again: different results each time. OK, how can I be a bit more thorough? Specialization and subagents: \"review PR 123 with parallel agents covering security, coding quality, and maintainability. Summarize the subagents and report results back to me.\"</p>\n<p>After a few times of this, it had grown to five agents: security, functionality (with a Jira ticket to read), maintainability, usability, and test coverage. Five — if I remembered to use all of them. And how do we remember to use the same instructions? <strong><a href=\"https://agentskills.io/home\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">A skill</a>!</strong> So I turned to the <a href=\"https://github.com/obra/superpowers\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Superpowers</a> skill-building skill to capture more detailed notes on my Team of Specialists, creating: <a href=\"https://github.com/Ed-Fi-Alliance-OSS/AI-Tools-for-Ed-Fi-SDLC/blob/main/skills/team-pr-review/SKILL.md\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">team-pr-review</a>.</p>\n<div class=\"language-yml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">---</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">description</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> Launches a fleet of five specialist subagents to deeply review a pull request across security</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> functionality</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> maintainability</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> usability</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> and test coverage. Provide the PR number and related Issue number to get a severity</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">ranked summary of all findings.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> team</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">pr</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">review</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">---</span><br></div></code></pre></div></div>\n<p>The harness and I had a conversation to fill in additional repeatable detail. It came up with a summary format to my liking. We added yet more detail on each specialist. I refined it a few times, most notably telling it to post the comment using a markdown file (<code>gh pr comment -F &lt;file&gt;</code>) instead of posting the comment inline (<code>gh pr comment -b \"&lt;comment&gt;\"</code>) — this resolved an issue where the harness kept escaping the backtick character incorrectly, leading to slightly malformed comments.</p>\n<p>And this is working well. But it risks throwing slop back at the programmer.</p>\n<p>There are typically 10+ review comments, especially on AI-produced commits that were very good, but not quite perfect. Occasionally I disagree with the severity or even the finding. So I began editing the review, <em>after</em> posting it. I don't want to waste tokens on the edits, so I do this by hand. I move items around in severity assessment, delete them or offer additional commentary, and now completely remove the \"nits\" section or bump those items up to low severity if I want to see them be resolved. And I add a note at the top mentioning that I have reviewed and edited the agent's review, so that the reader will know that I am not (one hopes!) forcing them to spend time analyzing something that I was unwilling to analyze myself.</p>\n<p>And if I'm really smart, I might ask the coding harness to look at the PR for updates to make to <code>AGENTS.md</code>. But be careful with that: you don't want to overwhelm that file.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Wait until you have performed several detailed reviews this way. Then start a coding session with something like this prompt:</p><blockquote>\n<p>Review the past 5 pull requests and look for patterns in the review comments.</p>\n<p>When you find a repeated pattern, draft a proposed update to <code>AGENTS.md</code>. Do not immediately edit the file; present the recommendation to me with an offer to edit the file.</p>\n</blockquote></div></div>\n<hr>\n<p><em>AI usage disclosure: GitHub Copilot improved the title and provided a few editorial tips on writing clarity and grammar. In other words, the text is all mine.</em></p>",
            "url": "https://blog.safnet.com/2026/08/13/agentic-code-review",
            "title": "From Ad-Hoc Prompts to a Repeatable Agentic PR Review Workflow",
            "summary": "From time to time I pop in to peek at one of the dozens of pull requests that come through my mailbox daily, furthering the work of the Ed-Fi Alliance to help connect student data. What feature, improvement, or bug fix are the teams working on? How has this engineer approached a given problem, what tests have they written, what documentation have they provided, are the I/O seams sufficiently fault tolerant? Did they run the GitHub Copilot Code Review Agent, and fix the issues detected there?",
            "date_modified": "2026-08-13T00:00:00.000Z",
            "tags": [
                "programming",
                "technology"
            ]
        },
        {
            "id": "https://blog.safnet.com/2026/04/10/cli-ai-harness",
            "content_html": "<p>For all of last year, I wondered why anyone would use the CLI version of Claude Code and similar tools. I asked a colleague about this a couple of times, and still had trouble getting it.</p>\n<p>Until I sat down and used the CLI to perform my work. As a knowledge worker, if you haven't used <code>copilot</code> or <code>claude</code> or (favorite tool) in the terminal, you are truly missing out.</p>\n<p>🔝 Top 3 reasons I like this experience:</p>\n<p><img decoding=\"async\" loading=\"lazy\" alt=\"Claude Code CLI\" src=\"https://blog.safnet.com/assets/images/claude-code-cli-6461821d1a36953653bbbd3a26dc6a17.jpg\" width=\"1591\" height=\"857\" class=\"img_ev3q\"></p>\n<ol>\n<li class=\"\">VS Code chat panel puts my conversation in the bottom right corner in a tiny box (by default). You can change this - but I hadn't thought to change it. The terminal is front-and-center, wherever I want it.</li>\n<li class=\"\">These CLI tools are powerful with amazingly rich interfaces.</li>\n<li class=\"\">We should all be shifting from \"software engineer assisted by AI\" to \"software engineering assisting AI\". Call it \"agentic engineering\" or \"AI orchestrator\" or whatever. The CLI makes you focus on the conversation with the model, without the distraction of the file explorer or a big blank space where you would normally be typing into a file.</li>\n</ol>\n<p>As with anything, there is a learning curve. You can always ask the tool directly for help. And make sure you read the tool's documentation in order to get the most out of it.</p>\n<hr>\n<p>These words above <a href=\"https://www.linkedin.com/posts/stephenfuqua_for-all-of-last-year-i-wondered-why-anyone-activity-7443341515805925376-FJzO\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">went viral on LinkedIn</a>, much to my surprise.</p>\n<p>I had made a provocative statement about moving from \"AI assisting the engineer\" to \"engineer assisting the AI\". This is, of course, overly simplistic. The statement is intended to spur conversation. 🧔‍♂️🛟🤖</p>\n<p>A case in point: Claude Code developed a simple analytics report for me with queries against a Cosmos DB collection. I have never personally learned SQL syntax for Cosmos DB. While testing, I found several mistakes in the scripts. After the tool corrected several of those mistakes, I asked Claude (Sonnet 4.6 model) to review the syntax again for any further errors: all good! it said.</p>\n<p>Then I asked it \"is <code>VALUE</code> a reserved word?\" At which point it realized that yes, it had made a mistake, and it corrected it.</p>\n<p>My deep history of writing SQL queries allowed me to guide the AI tool to fix its mistake. I still did not write any code by hand here - I let the CLI tool handle it - though I did use my editor to open and read the file in question. Engineer assisting the AI.</p>",
            "url": "https://blog.safnet.com/2026/04/10/cli-ai-harness",
            "title": "Falling in Love with the CLI AI Harness",
            "summary": "For all of last year, I wondered why anyone would use the CLI version of Claude Code and similar tools. I asked a colleague about this a couple of times, and still had trouble getting it.",
            "date_modified": "2026-04-10T00:00:00.000Z",
            "tags": [
                "programming",
                "technology"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/12/30/inward-turn",
            "content_html": "<p><a class=\"\" href=\"https://blog.safnet.com/tags/bahai-faith\">Bahá'u'lláh's teachings</a> focus, at their core, on the concept of the essential unity of humanity: that we are biologically one \"species\", we should expand our notions of belonging and compassion to the entirety of humanity, and that doing so is the road to peace. [1]</p>\n<p>Another of His teachings is the concept of moderation [2]. One manifestation of that moderation is in the balancing act between \"units\" of humanity: individual, family, community, institutions, government. Each unit has its prerogatives [3], its spheres of action, overlapping one another, seeking to avoid infringement on the others whilst simultaneously protecting the harmony of all.</p>\n<p>I was stirred into contemplation of \"the individual\" within these overlapping contexts on reading the following quote from Karl Jaspers, in The Origin and Goal of History [4]:</p>\n<blockquote>\n<p>\"Man proved capable of contrasting himself inwardly with the entire universe. He discovered within himself the origin from which to raise himself above his own self and the world.\"</p>\n</blockquote>\n<p>Thus the \"axial age\" (roughly 8th to 2nd centuries BCE), as Jaspers calls it, saw the rise of individuals distinct from tribe and distinct from the rest of the universe (\"nature\"); unique, soul-embodied, self-reflecting, self-aware, and (potentially) self-absorbed. This inward turn led me back to a favorite quotation from Baha'u'llah (<em>The Hidden Words</em>, <a href=\"https://www.bahai.org/r/511421653\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">number 13</a> from the Arabic):</p>\n<blockquote>\n<p>\"O SON OF SPIRIT!</p>\n<p>I created thee rich, why dost thou bring thyself down to poverty? Noble I made thee, wherewith dost thou abase thyself? Out of the essence of knowledge I gave thee being, why seekest thou enlightenment from anyone beside Me? Out of the clay of love I molded thee, how dost thou busy thyself with another? Turn thy sight unto thyself, that thou mayest find Me standing within thee, mighty, powerful and self-subsisting.\"</p>\n</blockquote>\n<p>Among the rich possibilities for interpretation and application, this short passage induces feelings of individual worth (nobility) and awareness of two larger spiritual truths: the <a class=\"\" href=\"https://blog.safnet.com/2010/12/26/thou_and_thee_in_sacred_literature\">personal \"thou\"</a> is addressed to each human separately and simultaneously, and we are profoundly connected by finding our spiritual grounding (God, if you will) as an intrinsic element of self, which does not require extrinsic imposition or validation. In other words, as individuals we can know not only our selves (\"know thyself\") but also God (whatever we might mean by that word).</p>\n<p>Thus we see unity − a balance and moderation − instead of paradox when considering that the mechanism that Jaspers argues \"raise[s] (man) above his own self and the world\" may also lead deeper into spiritual communion.</p>\n<p>Returning to the idea of moderation, and the balance between individual and collective, a moment's reflection brings to mind several unities of opposites, each worthy of future further consideration:</p>\n<table><thead><tr><th>Individual</th><th>Communal</th></tr></thead><tbody><tr><td>Investigation and acceptance of truth</td><td>Consultation as search for truth</td></tr><tr><td>Obligatory prayers</td><td>Communal (non-congregational) worship</td></tr><tr><td>Interpretation and application</td><td>Authoritative interpretation and elucidation</td></tr></tbody></table>\n<hr>\n<p>[1] See, for example, `Abdu'l-Bahá's <a href=\"https://www.bahai.org/library/authoritative-texts/abdul-baha/tablets-hague-abdul-baha/1\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">First Tablet to the Hague</a>. The entire letter is on this theme, but see in particular:</p>\n<blockquote>\n<p>\"O&nbsp;peoples of the world! The Sun of Truth hath risen to illumine the whole earth, and to spiritualize the community of man. Laudable are the results and the fruits thereof, abundant the holy evidences deriving from this grace. This is mercy unalloyed and purest bounty; it is light for the world and all its peoples; it is harmony and fellowship, and love and solidarity; indeed it is compassion and unity, and the end of foreignness; it is the being at one, in complete dignity and freedom, with all on earth.\"</p>\n</blockquote>\n<p>[2] One of my favorite examples of moderation is in <a href=\"https://www.bahai.org/r/586586494\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\"><em>Gleanings from the Writings of Bahá'u'lláh</em>, CLXIV</a>: \"Whoso cleaveth to justice, can, under no circumstances, transgress the limits of moderation.\" The passage continues by describing the need for moderation in \"civilization\" (technological development).</p>\n<p>[3] 'Abdu'l-Bahá explains his views on the family <a href=\"https://www.bahai.org/library/authoritative-texts/abdul-baha/promulgation-universal-peace/12#500673226\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">during a talk</a>, views which find their echo in other commentary on individuals, communities, and institutions:</p>\n<blockquote>\n<p>\"According to the teachings of Bahá'u'lláh the family, being a human unit, must be educated according to the rules of sanctity. All the virtues must be taught the family. The integrity of the family bond must be constantly considered, and the rights of the individual members must not be transgressed. The rights of the son, the father, the mother—none of them must be transgressed, none of them must be arbitrary. Just as the son has certain obligations to his father, the father, likewise, has certain obligations to his son. The mother, the sister and other members of the household have their certain prerogatives. All these rights and prerogatives must be conserved, yet the unity of the family must be sustained. The injury of one shall be considered the injury of all; the comfort of each, the comfort of all; the honor of one, the honor of all.\"</p>\n</blockquote>\n<p>[4] The quote from Jaspers came to my attention through the book <a href=\"https://human-as-media.com/books-digital-future-in-the-rearview-mirror/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\"><em>Digital Future in the Rearview Mirror</em></a> (p 176), by Andrey Mir.</p>",
            "url": "https://blog.safnet.com/2025/12/30/inward-turn",
            "title": "The Inward Turn",
            "summary": "Bahá'u'lláh's teachings focus, at their core, on the concept of the essential unity of humanity: that we are biologically one \"species\", we should expand our notions of belonging and compassion to the entirety of humanity, and that doing so is the road to peace. [1]",
            "date_modified": "2025-12-30T00:00:00.000Z",
            "tags": [
                "discourse",
                "bahai-faith"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/10/23/program-birth",
            "content_html": "<p>Today we are celebrating the second of the Twin Holy days in the Bahá'í\ncalendar: the birth of Bahá'u'lláh, founder of the Bahá'í Faith, in 1817.\nYesterday we celebrated the birth of the Báb, born in 1819, who as an\nindependent messenger from God both declared a new religion as a break from\nIslam, and prophesied that another, greater revelation would come soon after\nHis own. Both were born in Persia, which is today known as Iran. In the lunar\ncalendar used by the Muslim world, their births fell on consecutive days.\nConsequently, Bahá'ís around the world celebrate them as the \"Twin Holy Days\".</p>\n<div class=\"image\"><img alt=\"Entrance to the Bahjí Pilgrim House\" src=\"https://blog.safnet.com/img/2025-10-23/shrine-pilgrim-house.jpg\"><p>Entrance to the Bahjí Pilgrim House, near the Shrine of Bahá'u'lláh (<a href=\"https://media.bahai.org/detail/3833733/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">source</a>)</p></div>\n<p><a class=\"\" href=\"https://blog.safnet.com/birth-of-bahaullah/\">Continue...</a></p>",
            "url": "https://blog.safnet.com/2025/10/23/program-birth",
            "title": "Commemorating the Birth of Bahá'u'lláh",
            "summary": "Today we are celebrating the second of the Twin Holy days in the Bah&aacute;&#39;&iacute;",
            "date_modified": "2025-10-23T00:00:00.000Z",
            "tags": [
                "bahai-faith"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/06/17/copilot-commit-signing",
            "content_html": "<p>GitHub Copilot <a href=\"https://docs.github.com/en/copilot/using-github-copilot/coding-agent\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Coding\nAgent</a> is\nan impressive and powerful tool for \"autonomously\" completing development tasks\nrunning in GitHub's environment. Like any tool, there is a learning curve ahead\nbefore any of us become truly high functioning, beginning with the statement of\na well crafted prompt.</p>\n<p>⚠️ But I have a concern: lack of commit signing. Should that stop us from adopting\nCoding Agent? TL;DR conclusion: time to relax the commit signing requirement.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-is-commit-signing\">What is commit signing?<a href=\"https://blog.safnet.com/2025/06/17/copilot-commit-signing#what-is-commit-signing\" class=\"hash-link\" aria-label=\"Direct link to What is commit signing?\" title=\"Direct link to What is commit signing?\" translate=\"no\">​</a></h2>\n<p>Git commit signing uses public/private key encryption to validate the identity\nof the individual who created a given commit. This approach may help reduce the\nrisk and opportunity for malicious actors to attack the <a href=\"https://docs.github.com/en/code-security/supply-chain-security\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">software supply\nchain</a>; without\nsigning, it is trivial to spoof the commit as coming from a trusted source.\nGitHub's <a href=\"https://wellarchitected.github.com/library/governance/recommendations/governance-policies-best-practices\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Well-Architected\nFramework</a>\nreflects on this: \"Initiate and impose commit signing whenever possible. This\nwill deter malicious actors from creating a commit with malicious code and help\nprevent a possible supply chain attack.\" My organization has adopted this\npractice across our repositories.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"spoofing-a-commit\">Spoofing a commit<a href=\"https://blog.safnet.com/2025/06/17/copilot-commit-signing#spoofing-a-commit\" class=\"hash-link\" aria-label=\"Direct link to Spoofing a commit\" title=\"Direct link to Spoofing a commit\" translate=\"no\">​</a></h2>\n<p>Take a look at <a href=\"https://github.com/Ed-Fi-Alliance-OSS/Ed-Fi-Actions/pull/142/commits\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">this pull\nrequest</a> I\ncreated.</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"Fake signed commit\" src=\"https://blog.safnet.com/assets/images/github-copilot-fake-signed-commit-80e93e081cfe6c4a4134eaef909236d9.webp\" width=\"1176\" height=\"477\" class=\"img_ev3q\"></p></div>\n<p>Looks like Copilot created that commit, doesn't it? But I was the originator.\nNote the lack of a <a href=\"https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">green \"VERIFIED\"\nbadge</a>.\nAfter using Coding Agent on an issue in this repository, I fetched the\nrepository, looked closely at the committer in the ref log, and used that info\nto spoof the author of my new commit.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"github-copilot-agent-modes-limitation\">GitHub Copilot Agent Mode's limitation<a href=\"https://blog.safnet.com/2025/06/17/copilot-commit-signing#github-copilot-agent-modes-limitation\" class=\"hash-link\" aria-label=\"Direct link to GitHub Copilot Agent Mode's limitation\" title=\"Direct link to GitHub Copilot Agent Mode's limitation\" translate=\"no\">​</a></h2>\n<p>I had to disable the signed commit requirement before I could accept the Coding\nAgent pull request. There is no way (that I can find) of configuring Coding\nAgent to sign commits - even though other bots, such as Dependabot, are able to\ndo this. And there is no way to set a branch rule that lets Copilot bypass the\ncommit signing requirement.</p>\n<p>What do others do? Microsoft, for example, <a href=\"https://github.com/dotnet/aspire/pull/9816/commits\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">does not require signed\ncommits</a>. The final merge\ninto the <code>main</code> branch <em>is</em> signed by GitHub, so that's something. If this is\ngood enough for Microsoft, is it good enough for us?</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"balancing-productivity-and-security\">Balancing productivity and security<a href=\"https://blog.safnet.com/2025/06/17/copilot-commit-signing#balancing-productivity-and-security\" class=\"hash-link\" aria-label=\"Direct link to Balancing productivity and security\" title=\"Direct link to Balancing productivity and security\" translate=\"no\">​</a></h2>\n<p>Commit signing is ultimately about trust: do you trust the committer, and by\nextension, the committed code? This trust is (or should be) backed up with\nquality code reviews, static application security testing (SAST), and other\ndefensive measures, regardless of who created the commit.</p>\n<p>Is the productivity benefit of Coding Agent worth the tradeoff on commit\nsigning? We have solid engineering practices, <a href=\"https://slsa.dev/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">SLSA level 2</a>\ncompliance, and receive high marks from security auditors for the strength of\nour build pipeline. Commit signing is the least of our security measures.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"conclusion\">Conclusion<a href=\"https://blog.safnet.com/2025/06/17/copilot-commit-signing#conclusion\" class=\"hash-link\" aria-label=\"Direct link to Conclusion\" title=\"Direct link to Conclusion\" translate=\"no\">​</a></h2>\n<p>Coding Agent can be a valuable and productive tool for resolving well documented\nissues quickly, while allowing my team to remain more focused on feature\ndevelopment (with or without Coding Agent). On balance, lifting the enforcement\nof code signing to allow use of Code Agent seems worth the tradeoff.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>warning</div><div class=\"admonitionContent_BuS1\"><p>Please review your organization's security posture carefully before changing\nyour commit signing policy.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"postscript-1-open-source-problem\">Postscript 1: Open-Source problem<a href=\"https://blog.safnet.com/2025/06/17/copilot-commit-signing#postscript-1-open-source-problem\" class=\"hash-link\" aria-label=\"Direct link to Postscript 1: Open-Source problem\" title=\"Direct link to Postscript 1: Open-Source problem\" translate=\"no\">​</a></h2>\n<p>I failed to mention that commit signing is great for regular contributors to an\nopen-source repository, but presents significant friction for new / casual\ncontributors, as described <a href=\"https://github.com/orgs/community/discussions/69706\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">in this feature\nrequest</a>.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"postscript-2-additional-sample-repositories\">Postscript 2: Additional sample repositories<a href=\"https://blog.safnet.com/2025/06/17/copilot-commit-signing#postscript-2-additional-sample-repositories\" class=\"hash-link\" aria-label=\"Direct link to Postscript 2: Additional sample repositories\" title=\"Direct link to Postscript 2: Additional sample repositories\" translate=\"no\">​</a></h2>\n<p>More examples of prominent open source projects that do not require signed\ncommits on a pull request:</p>\n<ul>\n<li class=\"\"><a href=\"https://github.com/langchain-ai\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">LangChain</a></li>\n<li class=\"\"><a href=\"https://github.com/apache/airflow/pull/51853/commits\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Apache Airflow</a></li>\n<li class=\"\"><a href=\"https://github.com/redis/redis/pull/14133/commits\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Redis</a></li>\n<li class=\"\"><a href=\"https://github.com/kubernetes/kubernetes/pull/132352/commits\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Kubernetes</a></li>\n</ul>",
            "url": "https://blog.safnet.com/2025/06/17/copilot-commit-signing",
            "title": "GitHub Copilot and Signed Commits",
            "summary": "GitHub Copilot [Coding",
            "date_modified": "2025-06-17T00:00:00.000Z",
            "tags": [
                "programming",
                "architecture",
                "security",
                "technology"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/05/20/initial-experiment-gh-copilot-agent",
            "content_html": "<p>GitHub CoPilot recently promoted <a href=\"https://github.blog/ai-and-ml/github-copilot/copilot-ask-edit-and-agent-modes-what-they-do-and-when-to-use-them/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Agent mode as a full\nfeature</a>\nin VS Code. Many writers and commenters in the blogosphere think competitors are\nstill better; perhaps they're right. But this is the one I have, and Microsoft\nis innovating rapidly - seeing that first hand at Microsoft Build even as I type\nthis. So in recent days I've performed three initial experiments that I want to\nshare. Not because I'm doing anything brilliant: just trying to find tasks that\nmight be a good fit, learn how to interact with the tool, and share a bit to\nhelp others in my proximity.</p>\n<p>Lesson: be patient, and be in a learning mode. Not just waiting for it to finish\n(might take several minutes), but also in getting the right results. I remarked\non a failure (below) to my colleague Jason Hoekstra; he suggested that I simply\nundo and try again. It was the right advice. Experiment and build an intuition.</p>\n<p>Below I describe three experiments and outcomes.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"converting-json-to-yaml\">Converting JSON to YAML<a href=\"https://blog.safnet.com/2025/05/20/initial-experiment-gh-copilot-agent#converting-json-to-yaml\" class=\"hash-link\" aria-label=\"Direct link to Converting JSON to YAML\" title=\"Direct link to Converting JSON to YAML\" translate=\"no\">​</a></h2>\n<p>A giant JSON file: almost 4 MB. \"Self: would this be smaller as YAML? Why not\nask GitHub CoPilot to solve this.\". Prompt, in Agent mode:</p>\n<div class=\"language-none codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-none codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Convert this JSON file to YAML</span><br></div></code></pre></div></div>\n<p>Result: it tried to run a PowerShell command. It failed, saying that\n<code>ConvertTo-Yaml</code> doesn't exist. I suspected that I needed to install from the\nPowerShell Gallery. Tried that - manually; I didn't yet have the presence of\nmind to ask CoPilot to fix it for me. I found it in the Gallery, installed,\nre-ran the command, and it worked.</p>\n<p>New file size: something like 3.1 MB. Nice.</p>\n<p>Assessment: Small goof at first, but easily resolved. It found a simple set of\ncommands to run in PowerShell. I gave no instructions on how to solve this.\nPretty cool that it picked PowerShell on its own.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"replace-log4net-with-serilog\">Replace log4net with Serilog<a href=\"https://blog.safnet.com/2025/05/20/initial-experiment-gh-copilot-agent#replace-log4net-with-serilog\" class=\"hash-link\" aria-label=\"Direct link to Replace log4net with Serilog\" title=\"Direct link to Replace log4net with Serilog\" translate=\"no\">​</a></h2>\n<p>Our Admin API application has log4net, and we have talked about converting to\nSerilog. But this bit of tech debt was a low priority for the development team.</p>\n<p>This morning...💡see what GitHub CoPilot can do in Agent mode!</p>\n<div class=\"language-none codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-none codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Please convert this application from using log4net to using serilog.</span><br></div></code></pre></div></div>\n<p>Couple of minutes later... success... of a sort. Reviewing the changes, I see\nthat it removed all calls to the logger!</p>\n<p>The chat window shows me the steps it took. I see that the first step was to\nremove log4net. It seems to have been overzealous about that removal. Probably\nremoved those lines because the logger was missing, the code couldn't build, and\nit automatically adjusted to fix the code. Oops, undo.</p>\n<p>Next, I copied the <code>dotnet add</code> commands that installed Serilog (found in the\nchat window) and then reran them. Then I took a smaller step, with something\nlike this:</p>\n<div class=\"language-none codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-none codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Add serilog initialization and configuration to this solution.</span><br></div></code></pre></div></div>\n<p>This was basically the second step that CoPilot ran before. Note that I didn't\ntell it to remove log4net. But, it was in the same chat history, so CoPilot had\na memory of removing log4net in the past. It went ahead and removed log4net\n<em>after</em> installing Serilog. Mostly got it right this time.There was one place\nwhere is still removed something that I wanted, so I asked it:</p>\n<blockquote>\n<p>Write a WARNING message to the log when CORS is not enabled.</p>\n</blockquote>\n<p>That worked... but it inefficiently called the ASP.NET builder to create a\n<code>LoggerFactory</code>, then use that factor to create a <code>Logger</code>. Next:</p>\n<div class=\"language-none codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-none codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Refactor so that the logger factory is injected into the method signature.</span><br></div></code></pre></div></div>\n<p>Done. It helpfully asks me if it should update any call sites.</p>\n<div class=\"language-none codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-none codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Yes, update the call sites to pass an ILoggerFactory.</span><br></div></code></pre></div></div>\n<p>Done. After this, I entered additional prompts to help convert\nthe log4net format to a serilog template and to finally remove log4net.</p>\n<p>Assessment: Small bites helped. But I might have just tried the original request\nwith slightly more useful instructions, and it may have gotten there on its own.\nMaybe I should have described what the problem was and asked CoPilot to write a\nbetter prompt for me 😁.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"fix-a-warning\">Fix a warning<a href=\"https://blog.safnet.com/2025/05/20/initial-experiment-gh-copilot-agent#fix-a-warning\" class=\"hash-link\" aria-label=\"Direct link to Fix a warning\" title=\"Direct link to Fix a warning\" translate=\"no\">​</a></h2>\n<p>While testing the log response, I noticed a Warning that occurs on startup. Hey GitHub CoPilot...</p>\n<div class=\"language-none codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-none codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">My application logs the following warning at runtime. Can you help me solve it?</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">```</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WARNING Microsoft.EntityFrameworkCore.Query [(null)] - Compiling a query which loads related collections for more than one collection navigation, either via 'Include' or through projection, but no 'QuerySplittingBehavior' has been configured. By default, Entity Framework will use 'QuerySplittingBehavior.SingleQuery', which can potentially result in slow query performance. See &lt;https://go.microsoft.com/fwlink/?linkid=2134277&gt; for more information. To identify the query that's triggering this warning call 'ConfigureWarnings(w =&gt; w.Throw(RelationalEventId.MultipleCollectionIncludeWarning))</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">```</span><br></div></code></pre></div></div>\n<p>A short time later, the problem was fixed. The chat told me about how it setup\n\"query splitting\" behavior in Entity Framework. I wondered to myself, \"why isn't\nthis the default? Is it safe?\" Switching to \"ask\" mode instead of \"agent\":</p>\n<div class=\"language-none codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-none codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">are there any downsides to switching to this query splitting behavior?</span><br></div></code></pre></div></div>\n<p>The answer provided me with pros and cons, and a summary of why it feels that\nthis is safe \"for most cases.\" This application does not have heavy load and I\nagree that this looks safe, so I kept the changes.</p>\n<p>Assessment: Thanks, CoPilot! Another bit of tech debt quickly solved.</p>",
            "url": "https://blog.safnet.com/2025/05/20/initial-experiment-gh-copilot-agent",
            "title": "Initial Experiments with GitHub Copilot in Agent Mode",
            "summary": "GitHub CoPilot recently promoted [Agent mode as a full",
            "date_modified": "2025-05-20T00:00:00.000Z",
            "tags": [
                "programming",
                "architecture",
                "dotnet",
                "technology"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/04/03/docker-as-dev-env",
            "content_html": "<p>Containerization of an application benefits operations of the application by\nsolving the problem of \"it works on my machine\" (at least, for the application\nitself). The container holds the operating system and all needed components.\nOnce you have Docker on a host - whether localhost, on-prem data center, or in\nthe Cloud - you can run the application with greater confidence, knowing that\nthe application will execute the same in all environments.</p>\n<p>But the benefits of containerization can also shift left in the development\nlifecycle. For example: have you ever needed to revisit an older application,\nand realized that you don't have the SDK on your machine? Instead of installing\nthe SDK locally, you may be able to run the SDK in a <a href=\"https://www.docker.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Docker\ncontainer</a>.</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"The first henbit of the season\" src=\"https://blog.safnet.com/assets/images/henbit-2025-9691dbffed80035fa53e004820c1b99b.webp\" width=\"454\" height=\"279\" class=\"img_ev3q\"></p><p><a href=\"https://en.wikipedia.org/wiki/Lamium_amplexicaule\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\"><em>Lamium amplexicaule</em></a> aka\nhenbit, the first flower to appear in my yard this year.</p></div>\n<p><em>This is article is part 1 in a planned series on using Docker containers in the\nsoftware development lifecycle (SDLC).</em></p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"revisiting-a-net-core-31-application\">Revisiting a .NET Core 3.1 Application<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#revisiting-a-net-core-31-application\" class=\"hash-link\" aria-label=\"Direct link to Revisiting a .NET Core 3.1 Application\" title=\"Direct link to Revisiting a .NET Core 3.1 Application\" translate=\"no\">​</a></h2>\n<p>The <a href=\"https://github.com/Ed-Fi-Alliance-OSS/Ed-Fi-ODS-AdminApp/tree/v2.1.1\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Ed-Fi ODS Admin\nApp</a>\n(v2.1.1) is a web application for managing the client credentials needed for\nconnecting to the <a href=\"https://docs.ed-fi.org/reference/ods-api-platform/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Ed-Fi\nODS/API</a> for HTTP-based\nexchange of educational data. Version 2.1.1 specifically targeted Ed-Fi ODS/API\n3.x, and it was built with .NET Core 3.1. These products were previously\nsupported by my development teams, and now have been deprecated.</p>\n<p>.NET Core natively works in Linux, and Microsoft has provided SDK and runtime\nimages for Docker.We can quickly create a development environment for this\napplication, starting with the correct SDK tag:\n<code>mcr.microsoft.com/dotnet/core/sdk:3.1</code>. Then, we want to support the following\ntasks:</p>\n<ol>\n<li class=\"\">Build the solution and run unit tests</li>\n<li class=\"\">Run (database) integration tests</li>\n<li class=\"\">Run end-to-end tests</li>\n<li class=\"\">Run the application for exploratory testing</li>\n</ol>\n<!-- -->\n<p>Since this article is about shifting left, we will not include containerization\n<em>of the application itself</em>.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Review <a href=\"https://mcr.microsoft.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Microsoft Container Registry</a> to find the\nSDK, runtime, or other .NET images you may need.</p></div></div>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>warning</div><div class=\"admonitionContent_BuS1\"><p>Many users, including your author, have had trouble pulling images from\nMicrosoft's registry, receiving error messages like this: <code>ERROR: failed to do request: Head \"https://mcr.microsoft.com/v2/dotnet/aspnet/manifests/8.0\": EOF</code>.\nThis appears to be an unresolved bug in Docker Desktop or in Microsoft's web\nserver configuration, related to IPv6. <a href=\"https://github.com/microsoft/containerregistry/issues/165\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Turning off\nIPv6</a> on your network\nconnection resolves the issue.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-build-environment\">The Build Environment<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#the-build-environment\" class=\"hash-link\" aria-label=\"Direct link to The Build Environment\" title=\"Direct link to The Build Environment\" translate=\"no\">​</a></h2>\n<p>We need a .NET Core 3.1 SDK image, and we need to load the application files\ninto the image. Here is a good starting point, using Ubuntu 20. All commands\nbelow will be shown relative to the <code>Ed-Fi-ODS-AdminApp/eng</code> directory.</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker run </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">it </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">Application:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/Application:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    mcr</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">microsoft</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">com/dotnet/core/sdk:3</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">403-focal@sha256:b51325cd8b3eeb14099ca5db2a40bccc126bbb6fcaeec64fd208a73b8f800eec</span><br></div></code></pre></div></div>\n<p>This command runs in interactive mode (<code>-it</code>), dropping you into an active Bash\nprompt. On exit, the container will be removed automatically (<code>--rm</code>). The\napplication source files are mapped using read-write mode (<code>:rw</code>)</p>\n<p>so that the <code>bin</code> and\n<code>obj</code> directories that will be written by the build process.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>warning</div><div class=\"admonitionContent_BuS1\"><p>When using this statement in PowerShell scripts, replace <code>$pwd</code> with <code>$PSScriptRoot</code>.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"build-and-unit-test\">Build and Unit Test<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#build-and-unit-test\" class=\"hash-link\" aria-label=\"Direct link to Build and Unit Test\" title=\"Direct link to Build and Unit Test\" translate=\"no\">​</a></h2>\n<p>Next, we'll want a script that runs the build and the unit tests. It will also\nbe nice to send the console log output to a file for easier review. Since this\nis a .NET image, it contains PowerShell Core. Writing this in Bash would be\nsimple, but let's stick with PowerShell:</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">Set-Location</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/Application</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"../logs/build-$(Get-Date -Format \"</span><span class=\"token plain\">yyyyMMddHHmm</span><span class=\"token string\" style=\"color:#e3116c\">\").log\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">dotnet build </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">nologo </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">Tee-Object</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">FilePath </span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">dotnet test </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">nologo </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token keyword\" style=\"color:#00009f\">filter</span><span class=\"token plain\"> FullyQualifiedName~UnitTests </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">Tee-Object</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">FilePath </span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><br></div></code></pre></div></div>\n<p>Save this to a file (<code>run-build.ps1</code>), and map it into the running container.\nAlso, it will be convenient to create volume mounts for the logs and for the\nNuGet packages; otherwise, both will disappear when the container exits. At the\nend of the command we execute the file via <code>pwsh</code>. Finally, remove <code>-it</code> so that\nthe command belows runs the script and exits, instead of simply dropping you\ninto a terminal.</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker run </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$pwd</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">Application:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/Application:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$pwd</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">run-build</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/run-build</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1:ro `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$pwd</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">packages:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">root/</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">nuget/packages:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$pwd</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">logs:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/logs:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    mcr</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">microsoft</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">com/dotnet/core/sdk:3</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">403-focal@sha256:b51325cd8b3eeb14099ca5db2a40bccc126bbb6fcaeec64fd208a73b8f800eec `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    pwsh </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">File </span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/run-build</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Future consideration:</p><ol>\n<li class=\"\">The reader may want to explore using a <a href=\"https://github.com/microsoft/vstest/blob/main/docs/report.md#available-test-loggers\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\"><code>dotnet test</code>\nlogger</a>\nfor alternative output types.</li>\n<li class=\"\">The <code>run-build.ps1</code> script can also be used directly in a CI environment, so\nthat you are running the exact same command in both the localhost and the\nautomated environments.</li>\n</ol></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"integration-tests\">Integration Tests<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#integration-tests\" class=\"hash-link\" aria-label=\"Direct link to Integration Tests\" title=\"Direct link to Integration Tests\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"postgresql-setup\">PostgreSQL Setup<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#postgresql-setup\" class=\"hash-link\" aria-label=\"Direct link to PostgreSQL Setup\" title=\"Direct link to PostgreSQL Setup\" translate=\"no\">​</a></h3>\n<p>This application's integration tests require <a href=\"https://hub.docker.com/r/microsoft/mssql-server\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">SQL Server 2019 (Docker\nimages)</a> or <a href=\"https://hub.docker.com/_/postgres\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">PostgreSQL\n13</a>, and we will need to install the database\ntables. We will use PostgreSQL.</p>\n<p>The integration test container will need to communicate with the database\ncontainer. To this end, we can create a docker network and run both containers\ninside that network.</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker network create adminapp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker run </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">name postgres `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">hostname postgres `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">network adminapp `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">e POSTGRES_PASSWORD=mysecretpassword `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">d `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    postgres:13</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">20-alpine3</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">21@sha256:236985828131e95a12914071b944d0e0d21da5281312292747e222845f0ea670</span><br></div></code></pre></div></div>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"table-installation\">Table Installation<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#table-installation\" class=\"hash-link\" aria-label=\"Direct link to Table Installation\" title=\"Direct link to Table Installation\" translate=\"no\">​</a></h3>\n<p>Next, we need to install the AdminApp database tables. Fortunately, this\napplication was previously containerized. The pre-built <a href=\"https://github.com/Ed-Fi-Alliance-OSS/Ed-Fi-ODS-Docker/blob/v1.1.1/DB-Admin/Dockerfile\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">database\nimage</a>\nis tagged as <code>edfialliance/ods-api-db-admin:v1.1.0</code>.</p>\n<p>In development, we may need to modify the database. The Admin App SQL scripts\nare <em>migration</em> scripts. Any modifications we make will be new migrations, which\ncan run on top of the existing database. Furthermore, the Admin App repository\ncontains PowerShell scripts for installing the SQL migration scripts. With a\nlittle experimentation, I was able to run the database migrations by executing\nthe following script inside the build container.</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">Set-Location</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$config</span><span class=\"token plain\"> =</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    @</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"engine\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"PostgreSQL\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databaseServer\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"postgres\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databasePort\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"5432\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databaseUser\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"postgres\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databasePassword\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"mysecretpassword\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"useIntegratedSecurity\"</span><span class=\"token plain\"> = </span><span class=\"token boolean\" style=\"color:#36acaa\">$false</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"adminDatabaseName\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"EdFi_Admin\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"./logs/dbup-$(Get-Date -Format \"</span><span class=\"token plain\">yyyyMMddHHmm</span><span class=\"token string\" style=\"color:#e3116c\">\").log\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">eng/run-dbup-migrations</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1 </span><span class=\"token variable\" style=\"color:#36acaa\">$config</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">Tee-Object</span><span class=\"token plain\"> </span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>warning</div><div class=\"admonitionContent_BuS1\"><p>You may find small problems when running a PowerShell script in Linux that was\nwritten and historically used in Windows: watch out for proper casing on file\nnames, since Linux cares about these things; change <code>\\</code> to <code>/</code> or use\n<code>Join-Path</code> to create file paths; and don't use <code>.exe</code> as an extension on\ndownloaded tools. I had to modify one of the existing PowerShell scripts as follows:</p></div></div>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token variable\" style=\"color:#36acaa\">$exePath</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token string variable\" style=\"color:#36acaa\">$ToolsPath</span><span class=\"token string\" style=\"color:#e3116c\">/</span><span class=\"token string variable\" style=\"color:#36acaa\">$toolName</span><span class=\"token string\" style=\"color:#e3116c\">.exe\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token namespace\" style=\"opacity:0.7\">[System.Environment]</span><span class=\"token plain\">::OSVersion</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Platform </span><span class=\"token operator\" style=\"color:#393A34\">-eq</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Unix\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token variable\" style=\"color:#36acaa\">$exePath</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token string variable\" style=\"color:#36acaa\">$ToolsPath</span><span class=\"token string\" style=\"color:#e3116c\">/</span><span class=\"token string variable\" style=\"color:#36acaa\">$toolName</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"test-execution\">Test Execution<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#test-execution\" class=\"hash-link\" aria-label=\"Direct link to Test Execution\" title=\"Direct link to Test Execution\" translate=\"no\">​</a></h3>\n<p>The solution contains two integration test projects:\n<code>EdFi.Ods.AdminApp.Management.Tests</code> and\n<code>EdFi.Ods.AdminApp.Management.Azure.IntegrationTests</code>. We will skip the Azure\ntests for now, focusing only on the \"on-premises\" (or fully containerized)\ndeployment path.</p>\n<p>The <code>EdFi.Ods.AdminApp.Management.Tests</code> project contains an <code>appsettings.json</code>:</p>\n<div class=\"language-javascript codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-javascript codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"AppSettings\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"AppStartup\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"OnPrem\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"ApiStartupType\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"sandbox\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"XsdFolder\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Schema\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"DefaultOdsInstance\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"DefaultOdsInstance\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"DatabaseEngine\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"SqlServer\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"ConnectionStrings\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"Admin\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Data Source=.\\\\;Initial Catalog=EdFi_Admin_Test;Integrated Security=True\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"Security\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Data Source=.\\\\;Initial Catalog=EdFi_Security_Test;Integrated Security=True\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"OdsEmpty\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Data Source=.\\\\;Initial Catalog=EdFi_Ods_Empty_Test;Integrated Security=True\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p>One way to update these values is to <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0&amp;viewFallbackFrom=aspnetcore-2.2#evcp\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">set environment\nvariables</a>,\nwhich will override the appsettings file contents. Like so:</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:AppSettings__DatabaseEngine=</span><span class=\"token string\" style=\"color:#e3116c\">\"PostgreSQL\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:ConnectionStrings__Admin=</span><span class=\"token string\" style=\"color:#e3116c\">\"server=postgres;database=EdFi_Admin;username=postgres;password=</span><span class=\"token string variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><br></div></code></pre></div></div>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"complete-integration-testing-scripts\">Complete Integration Testing Scripts<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#complete-integration-testing-scripts\" class=\"hash-link\" aria-label=\"Direct link to Complete Integration Testing Scripts\" title=\"Direct link to Complete Integration Testing Scripts\" translate=\"no\">​</a></h3>\n<p>We can call this one <code>run-integration.ps1</code>:</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">Set-Location</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token plain\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"mysecretpassword\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$config</span><span class=\"token plain\"> =</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    @</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"engine\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"PostgreSQL\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databaseServer\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"postgres\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databasePort\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"5432\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databaseUser\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"postgres\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databasePassword\"</span><span class=\"token plain\"> = </span><span class=\"token variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"useIntegratedSecurity\"</span><span class=\"token plain\"> = </span><span class=\"token boolean\" style=\"color:#36acaa\">$false</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"adminDatabaseName\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"EdFi_Admin\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"./logs/integration-$(Get-Date -Format \"</span><span class=\"token plain\">yyyyMMddHHmm</span><span class=\"token string\" style=\"color:#e3116c\">\").log\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">eng/run-dbup-migrations</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1 </span><span class=\"token variable\" style=\"color:#36acaa\">$config</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">Tee-Object</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">FilePath </span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:AppSettings__DatabaseEngine=</span><span class=\"token string\" style=\"color:#e3116c\">\"PostgreSQL\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:ConnectionStrings__Admin=</span><span class=\"token string\" style=\"color:#e3116c\">\"server=postgres;database=EdFi_Admin;username=postgres;password=</span><span class=\"token string variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:ConnectionStrings__Security=</span><span class=\"token string\" style=\"color:#e3116c\">\"server=postgres;database=EdFi_Security;username=postgres;password=</span><span class=\"token string variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:ConnectionStrings__OdsEmpty=</span><span class=\"token string\" style=\"color:#e3116c\">\"server=postgres;database=EdFi_Ods_Empty_Test;username=postgres;password=</span><span class=\"token string variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">dotnet test </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">nologo Application/EdFi</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Ods</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">AdminApp</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Management</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Tests </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">Tee-Object</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">Append </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">FilePath </span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><br></div></code></pre></div></div>\n<p>And here is the final script to start this process in Docker:</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker network create adminapp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker run </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">name postgres `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">hostname postgres `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">network adminapp `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">e POSTGRES_PASSWORD=mysecretpassword `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">d `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    edfialliance/ods-api-db-admin:v1</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">0@sha256:258fab94ffbb49bc406b065b074a7154050dbdfaae2626d0570672317c575721</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">try</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    docker run </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">it `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">Application:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/Application:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">run-integration</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/run-integration</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1:ro `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">packages:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">root/</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">nuget/packages:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">logs:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/logs:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">eng:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/eng:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">network adminapp `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        mcr</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">microsoft</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">com/dotnet/core/sdk:3</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">403-focal@sha256:b51325cd8b3eeb14099ca5db2a40bccc126bbb6fcaeec64fd208a73b8f800eec `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        pwsh </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">File </span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/run-integration</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">finally</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    docker stop postgres</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    docker network </span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> adminapp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p>And here's where the experiment goes ends: the integration test project is\nhard-coded to SQL Server. Furthermore, due to licensing concerns, there is no\nSQL Server base image for <code>edfialliance/ods-api-db-admin</code>. It is possible to\novercome this challenge, but not important: the basic point has been made.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"end-to-end-testing\">End-to-End Testing<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#end-to-end-testing\" class=\"hash-link\" aria-label=\"Direct link to End-to-End Testing\" title=\"Direct link to End-to-End Testing\" translate=\"no\">​</a></h2>\n<p>Sadly, this application did not have any end-to-end tests when this version was\ncreated. If it <em>had</em> them, we might be able to start from the integration\ntesting script, which already sets up the database for testing. We would need to\ninject background startup of the application. Then run the tests. The following\nexample is for manual execution after using <code>docker run --it</code> to start the\ncontainer and activate the command prompt.</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">Set-Location</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token plain\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"mysecretpassword\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$config</span><span class=\"token plain\"> =</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    @</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"engine\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"PostgreSQL\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databaseServer\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"postgres\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databasePort\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"5432\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databaseUser\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"postgres\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"databasePassword\"</span><span class=\"token plain\"> = </span><span class=\"token variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"useIntegratedSecurity\"</span><span class=\"token plain\"> = </span><span class=\"token boolean\" style=\"color:#36acaa\">$false</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"adminDatabaseName\"</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"EdFi_Admin\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"./logs/integration-$(Get-Date -Format \"</span><span class=\"token plain\">yyyyMMddHHmm</span><span class=\"token string\" style=\"color:#e3116c\">\").log\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">eng/run-dbup-migrations</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1 </span><span class=\"token variable\" style=\"color:#36acaa\">$config</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">Tee-Object</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">FilePath </span><span class=\"token variable\" style=\"color:#36acaa\">$logFile</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:AppSettings__DatabaseEngine=</span><span class=\"token string\" style=\"color:#e3116c\">\"PostgreSQL\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:ConnectionStrings__Admin=</span><span class=\"token string\" style=\"color:#e3116c\">\"server=postgres;database=EdFi_Admin;username=postgres;password=</span><span class=\"token string variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:ConnectionStrings__Security=</span><span class=\"token string\" style=\"color:#e3116c\">\"server=postgres;database=EdFi_Security;username=postgres;password=</span><span class=\"token string variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$env</span><span class=\"token plain\">:ConnectionStrings__OdsEmpty=</span><span class=\"token string\" style=\"color:#e3116c\">\"server=postgres;database=EdFi_Ods_Empty_Test;username=postgres;password=</span><span class=\"token string variable\" style=\"color:#36acaa\">$pgPass</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># &amp; at the end runs the application in the background</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">Application/EdFi</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Ods</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">AdminApp</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Web/bin/Debug/netcoreapp3</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">1/EdFi</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Ods</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">AdminApp</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Web&amp;</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Execute the tests</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">dotnet test </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Call `ps` to find the process ID of the background service</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">ps</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Now stop that service</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">kill</span><span class=\"token plain\"> &lt;pid&gt;</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"manual-and-exploratory-testing\">Manual and Exploratory Testing<a href=\"https://blog.safnet.com/2025/04/03/docker-as-dev-env#manual-and-exploratory-testing\" class=\"hash-link\" aria-label=\"Direct link to Manual and Exploratory Testing\" title=\"Direct link to Manual and Exploratory Testing\" translate=\"no\">​</a></h2>\n<p>Startup a container with port mapping on port 8080, then run the script above up\nthrough <code>EdFi.Ods.AdminApp.Web&amp;</code>.</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Assuming network was previously created</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker network create adminapp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker run </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">name postgres `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">hostname postgres `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">network adminapp `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">e POSTGRES_PASSWORD=mysecretpassword `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">d `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    edfialliance/ods-api-db-admin:v1</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">0@sha256:258fab94ffbb49bc406b065b074a7154050dbdfaae2626d0570672317c575721</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker run </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">d `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">Application:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/Application:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">run-application</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/run-application</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1:ro `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">packages:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">root/</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">nuget/packages:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">logs:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/logs:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token variable\" style=\"color:#36acaa\">$PSScriptRoot</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">eng:</span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/eng:rw `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">network adminapp `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">p 8080:8080 `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    mcr</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">microsoft</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">com/dotnet/core/sdk:3</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">403-focal@sha256:b51325cd8b3eeb14099ca5db2a40bccc126bbb6fcaeec64fd208a73b8f800eec `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    pwsh </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">File </span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">opt/AdminApp/run-application</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ps1</span><br></div></code></pre></div></div>\n<p>Open <a href=\"http://localhost:8080/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">http://localhost:8080</a> in your browser for manually accessing the web\napplication. Then, don't forget to clean up when you're done:</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker stop postgres</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker network </span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> adminapp</span><br></div></code></pre></div></div>",
            "url": "https://blog.safnet.com/2025/04/03/docker-as-dev-env",
            "title": "Docker Containers in the SDLC: .NET Core SDK",
            "summary": "Containerization of an application benefits operations of the application by",
            "date_modified": "2025-04-03T00:00:00.000Z",
            "tags": [
                "programming",
                "architecture",
                "dotnet"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/02/16/elephant-in-the-room-ai",
            "content_html": "<p>Data engineering gurus <a href=\"https://www.ternarydata.com/about-us\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Joe Reis and Matthew\nHousley</a> once again led a closing town\nhall at Data Day Texas. Rather than opining from the front, they turned\nthe session over to the wisdom of the crowd. Housley seeded the conversation with a\nsingle question – “what is the elephant in the room?” – and the room was ready\nwith an answer: AI. In particular: what is AI going to do to my job?</p>\n<p>Given a room full of strangers, some participants were remarkably open about\ntheir fears. Perhaps knowing that the audience is composed of fellow data geeks\nhelped to establish a sense of vulnerability. These fears were being expressed\nby the people who, in theory, should be the ones developing expertise in using\nAI tooling. But that’s how disruptive the technologies may be: even the data\nexperts are uncertain and afraid.</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"Balloon scarecrow\" src=\"https://blog.safnet.com/assets/images/balloon_scarecrow-3ece27b59f314552129a1e6c19bcc7dc.jpg\" width=\"841\" height=\"443\" class=\"img_ev3q\"></p><p>Perhaps 20 years from now we'll look back and wonder\nwhat the fuss what all about, just as I wonder what the backstory was on this\nballoon-based scarecrow protecting raspberries in my backyard circa 2006. By\nStephen A. Fuqua.</p></div>\n<p>Or at least <em>some</em> of them are. Some in the audience already see themselves as\nthe ones leading the AI wave in their companies, and others feel more confident\ndue to the (perceived?) productivity boost experienced with their own\napplications of the technology. Many embrace the idea that <a href=\"https://hbr.org/2023/08/ai-wont-replace-humans-but-humans-with-ai-will-replace-humans-without-ai\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">AI Won’t Replace\nHumans — But Humans With AI Will Replace Humans Without\nAI</a>.</p>\n<p>Reading the room, the fear may have been equally about the jobs of the future as\nabout the participants’ own jobs. Some noted that many companies have already\nstopped hiring junior engineers because the LLM assistants are already as good.\nA few weeks after the conference, <a href=\"https://www.salesforceben.com/salesforce-will-hire-no-more-software-engineers-in-2025-says-marc-benioff/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Salesforce announced no new hiring of\nsoftware engineers in\n2025</a>\ndue to LLM-based productivity boosts. A question on many minds: who will be the\nengineers of the future if we do not develop new engineers today?</p>\n<p>Sidenote: it will be interesting to see if the hiring freezes are temporary or\nlasting pauses. Is this perhaps as much a PR ploy as it is an engineering\nmanagement strategy?</p>\n<p>Some remarked on the need for humans to provide <em>knowledge engineering</em>:\nstructuring knowledge to provide context and more intelligent capability. Hence\nthe continuing need for <a class=\"\" href=\"https://blog.safnet.com/2025/02/03/data-cataloging-at-ddtx2025\">data\ncataloging</a>. In discussing education and\ntraining, others commented on the need to focus on semantics – algorithms and\nproblem solving – over syntax. LLM’s can help you write the proper code in\nwhatever language, but you as an engineer need to know how to diagnose a problem\nand describe a solution. Another observation is that these tools may help us\ncollectively fix the massive tech debt found in mission critical, but obsolete,\nsoftware systems.</p>\n<p>One individual suggested embracing product management.\nLearn to identify and prioritize features and functionality on your own, weaving\nthem into your AI-assisted tools and products, and then learn how sell and\nsupport those tools for your colleagues’ benefit. But no one dared offer the\nsuggestion of turning themselves into salespeople for AI-assisted software!</p>\n<p>Both fear and excitement were palpable. Perhaps the only certainty we can rely\non is that our jobs, and even more so those of the newcomers to the field, will\ncontinue to evolve at an accelerating pace. Which makes me all the more appreciative of\nthe fantastic learning environment that is <a href=\"https://datadaytexas.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Data Day Texas</a>.</p>",
            "url": "https://blog.safnet.com/2025/02/16/elephant-in-the-room-ai",
            "title": "Addressing the Elephant in the Room – AI – at the Data Day Texas 2025 Town Hall",
            "summary": "Data engineering gurus [Joe Reis and Matthew",
            "date_modified": "2025-02-16T00:00:00.000Z",
            "tags": [
                "technology",
                "data-and-analytics"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/02/03/data-cataloging-at-ddtx2025",
            "content_html": "<p>Notes from three talks on data cataloging at the recent <a href=\"https://datadaytexas.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Data Day Texas\n2025</a></p>\n<ul>\n<li class=\"\">The Meta Grid</li>\n<li class=\"\">Investing in Semantics and Knowledge</li>\n<li class=\"\">We Are All Librarians</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-meta-grid\">The Meta Grid<a href=\"https://blog.safnet.com/2025/02/03/data-cataloging-at-ddtx2025#the-meta-grid\" class=\"hash-link\" aria-label=\"Direct link to The Meta Grid\" title=\"Direct link to The Meta Grid\" translate=\"no\">​</a></h2>\n<p>Ole Olesen-Bagneux opened the conference with a keynote on the <a href=\"https://www.searchingfordata.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">meta\ngrid</a> - a decentralized architecture for data\ncatalogs, inspired by the decentralization of microservices in the API world and\nData Mesh in the world of serving data for analytics and reporting. In short, as\nI understood it: <em>meet the users where they are</em>. The enterprise has many data\nsources; organize a system that supports metadata management at the source,\ninstead of trying to centralize all metadata knowledge.</p>\n<p>Empower individuals to serve as reference librarians who know how to navigate\nthe meta grid (which might be low tech), helping others to find the right\nsources for their queries. Stop wasting money on error-prone attempts to gather\nand maintain all metadata in a single location. Documentation of the systems is\nthe key, through diagrams and ADRs. These are the tools of the reference\nlibrarians, both human and LLM-based. \"Talk to\" a trained LLM to find the\nrequired data.</p>\n<p>Reactions:</p>\n<ul>\n<li class=\"\">Inspiring: another application of the principles of decentralization and\ndesigning for real humans.</li>\n<li class=\"\">Reminder: Technical solutions do not need to be grandiose to be effective.</li>\n<li class=\"\">He refers to this as the third wave in data decentralization. Should\nblockchain also be in that list? Perhaps that is an orthogonal question, since\nits form of decentralization is about ownership, not discovery.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"investing-in-semantics-and-knowledge\">Investing in Semantics and Knowledge<a href=\"https://blog.safnet.com/2025/02/03/data-cataloging-at-ddtx2025#investing-in-semantics-and-knowledge\" class=\"hash-link\" aria-label=\"Direct link to Investing in Semantics and Knowledge\" title=\"Direct link to Investing in Semantics and Knowledge\" translate=\"no\">​</a></h2>\n<p>Be curious and empathetic, <a href=\"https://juansequeda.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Juan Sequeda</a> urged us, as\nyou build knowledge systems. Do not lose sight of why (again, people first, tech\nfollows): reach economies of scale with your data by making them reusable,\ncomposable, and extensible. This requires identifying and recording context of\nthe data you are storing (knowledge), and some function that combines data with\nthat knowledge to produce new data and knowledge (semantics).</p>\n<p>Back to why: he encouraged applying \"five whys\" type of thinking when approached\nwith a solution, to get to the real problem that needs to be solved. Make\nassumptions explicit. Both the assumptions behind the problem / solution, and\nassumptions about the data themselves. More knowledge: what does <code>null</code> mean in\nthis column? That the value is missing or unknown? That it is constantly\nchanging? That it does not exist? Who knows the answer? Record it in the data\ncatalog.</p>\n<p>Sequeda shared about an interesting case study of building a culture around data\nquality, driven by the most basic of incentives: the annual bonus. A company\nidentified data quality problems as costing them lost revenue and extra expense.\nTo turn around the culture, every employee in the company, from the worker who\nmight be eyeballing a measurement instead of being precise to the CEO has to\nimprove the quality of the data, with 25% of their bonus riding on meeting\ncertain benchmarks.</p>\n<p>Again with simplicity: do things the \"manual\" way until you can justify a tool.\nSpreadsheets are great. Don't start your journey with an expensive tool. As you\ntrack your data, look for the opportunities for reuse. And, look at <em>who</em> is\nworking with the data. Who uses them? Who affects them? Record these\nobservations in the Data Catalog.</p>\n<p>Reactions:</p>\n<ul>\n<li class=\"\">He mentioned <a href=\"https://schema.org/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">schema.org</a> in passing. Potential use for\nour work at the [Ed-Fi Alliance]? Might be interesting to compare the\n<a href=\"https://schema.org/Person\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Person</a> and\n<a href=\"https://schema.org/EducationalOrganization\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">EducationalOrganization</a> schemas,\nfor example.</li>\n<li class=\"\">Can a good data catalog track <em>use cases</em> that links them back to data\nsources? What about <em>potential</em> use cases, which would highlight gaps to be\nfilled?</li>\n<li class=\"\">Have never seen data catalog software in action. Can some of these\napplications track the mapping between data specifications? Perhaps as\nlineage?</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"we-are-all-librarians\">We Are All Librarians<a href=\"https://blog.safnet.com/2025/02/03/data-cataloging-at-ddtx2025#we-are-all-librarians\" class=\"hash-link\" aria-label=\"Direct link to We Are All Librarians\" title=\"Direct link to We Are All Librarians\" translate=\"no\">​</a></h2>\n<p>Do you organize (data)? Do you research? Do you educate? Track provenance?\nRetrieve information? Then you are a librarian! Or so says Jessica Talisman.\n<a href=\"https://podcasts.apple.com/us/podcast/building-taxonomies-data-models-to-remove-ambiguity/id1739823286?i=1000671791129\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">This podcast on building\ntaxonomis</a>\nlooks like it covers similar ground.</p>\n<p>Her talk seemed compelling and important... but I admit I got lost at some\npoint. For me, this was too abstract, without sufficient concrete example.\nPerhaps pre-supposing too much? Certainly the keynote speaker enjoyed the talk,\nwith very warm comment and question immediately following.</p>\n<p>One idea that did sink in: when cataloging metadata, it helps to control the\nvocabulary. This becomes the taxonomy. Minimize semantic ambiguity by building a\nthesaurus, with a list of terms, associative relationships, and set of rules on\nhow to use it. This seems very powerful for data mapping exercises.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"conclusion\">Conclusion<a href=\"https://blog.safnet.com/2025/02/03/data-cataloging-at-ddtx2025#conclusion\" class=\"hash-link\" aria-label=\"Direct link to Conclusion\" title=\"Direct link to Conclusion\" translate=\"no\">​</a></h2>\n<p>Conceptually, taxonomy and ontologies should be interesting to me. But I can get\nlost and a bit bored without hands on experience. Nevertheless these sessions\noffered tantalizing clues that one day yet assemble themselves into an aha!\nmoment.</p>",
            "url": "https://blog.safnet.com/2025/02/03/data-cataloging-at-ddtx2025",
            "title": "Data Cataloging at Data Day Texas 2025",
            "summary": "Notes from three talks on data cataloging at the recent [Data Day Texas",
            "date_modified": "2025-02-03T00:00:00.000Z",
            "tags": [
                "technology",
                "data-and-analytics"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/01/24/openapi-to-csv",
            "content_html": "<p>Following up on <a class=\"\" href=\"https://blog.safnet.com/2025/01/14/accepting-ai-assistants\">Grudgingly Accepting AI Coding Assistants</a>, I have a small win from my\nfirst experiment with GitHub Copilot: a Python script for converting portions of\nan Open API specification file to a CSV. There is nothing revolutionary in this\nsmall win; countless programmers have doubtlessly done much more. But a\nreluctant engineer has to start somewhere.</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"Screenshot of prompt entry and Copilot response\" src=\"https://blog.safnet.com/assets/images/openapi-to-csv-copilot-28509bbe8fd942c4b0586b88ffebc9ec.png\" width=\"783\" height=\"454\" class=\"img_ev3q\"></p></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Postscript 3/22/2025: Now available in an expanded form, supporting both Open API version 2 (Swagger)\nand version 3, at <a href=\"https://github.com/Ed-Fi-Exchange-OSS/openapi-to-csv\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">openapi-to-csv</a></p></div></div>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>warning</div><div class=\"admonitionContent_BuS1\"><p>6/25/2025: On further testing, I discovered significant problems with this\napproach, which had looked so promising. I should try again some day with a\nbetter prompt - I don't blame Copilot, I blame my skill at using it.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"context\">Context<a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#context\" class=\"hash-link\" aria-label=\"Direct link to Context\" title=\"Direct link to Context\" translate=\"no\">​</a></h2>\n<p>Interoperability with the <a href=\"https://docs.ed-fi.org/reference/data-exchange\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Ed-Fi Data\nStandard</a> is mediated through a\nREST API, which is documented via an <a href=\"https://www.openapis.org/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Open API</a>\nspecification. Conversion of portions of the specification to CSV could simplify\ncomparison to other specifications and/or ingestion into a data catalog.</p>\n<p>The specification file describes the HTTP-based interactions available in the\nAPI application, including the schemas for the <code>POST</code> and <code>PUT</code> request bodies\nand <code>GET</code> response bodies. These schemas are, in practice, the tangible\nrealization of the Ed-Fi Data Standard. Thus, a CSV file containing the a\nlisting of all of the properties for each path <em>is</em> a listing of the elements of\nthe <em>effective</em> Ed-Fi Data Standard.</p>\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>info</div><div class=\"admonitionContent_BuS1\"><p>There are small divergences between the Data Standard and this API\nSpecification, such as how school years are represented. However, since actual\nexchange of data occurs through the API, then the API specification is the\n\"physical\" source of truth that we need to work with.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"example\">Example<a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#example\" class=\"hash-link\" aria-label=\"Direct link to Example\" title=\"Direct link to Example\" translate=\"no\">​</a></h2>\n<p>Below is a greatly truncated extract from the Ed-Fi API spec:</p>\n<div class=\"language-javascript codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-javascript codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"openapi\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"3.0.1\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"info\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"servers\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"paths\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"/ed-fi/absenceEventCategoryDescriptors\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"get\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"summary\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Retrieves specific resources using the resource's property values (using the \\\"Get\\\" pattern).\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"description\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"This GET operation provides access to resources using the \\\"Get\\\" search pattern.  The values of any properties of the resource that are specified will be used to return all matching results (if it exists).\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"responses\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"200\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"description\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"The requested resource was successfully retrieved.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"content\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">              </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"application/json\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"schema\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                  </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"type\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"array\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                  </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"items\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                    </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"$ref\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"#/components/schemas/edFi_absenceEventCategoryDescriptor\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                  </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">              </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p>The <code>$ref</code> line points a reusable definition of the object used in <code>GET</code>\nresponses and <code>POST</code> and <code>PUT</code> requests (again, truncated for brevity):</p>\n<div class=\"language-javascript codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-javascript codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"components\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"schemas\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"edFi_absenceEventCategoryDescriptor\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"type\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"object\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"properties\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"id\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"type\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"string\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"description\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"codeValue\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"maxLength\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">50</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"type\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"string\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"description\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"A code or abbreviation that is used to refer to the descriptor.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"x-Ed-Fi-isIdentity\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">true</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"namespace\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"maxLength\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">255</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"type\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"string\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"description\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"A globally unique namespace that identifies this descriptor set. Author is strongly encouraged to use the Universal Resource Identifier (http, ftp, file, etc.) for the source of the descriptor definition. Best practice is for this source to be the descriptor file itself, so that it can be machine-readable and be fetched in real-time, if necessary.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"x-Ed-Fi-isIdentity\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">true</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"description\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"maxLength\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1024</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"type\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"string\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"description\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"The description of the descriptor.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-property property\" style=\"color:#36acaa\">\"x-nullable\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">true</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p>Putting this information together, the desired output would be:</p>\n<table><thead><tr><th>Path</th><th>Property Name</th><th>Property Type</th><th>Description</th></tr></thead><tbody><tr><td>/ed-fi/absenceEventCategoryDescriptors</td><td>id</td><td>string</td><td></td></tr><tr><td>/ed-fi/absenceEventCategoryDescriptors</td><td>codeValue</td><td>string</td><td>A code or abbreviation...</td></tr><tr><td>/ed-fi/absenceEventCategoryDescriptors</td><td>namespace</td><td>string</td><td>A globally unique namespace...</td></tr><tr><td>/ed-fi/absenceEventCategoryDescriptors</td><td>description</td><td>string</td><td>The description of the...</td></tr></tbody></table>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"ai-assistants-output\">AI Assistant's Output<a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#ai-assistants-output\" class=\"hash-link\" aria-label=\"Direct link to AI Assistant's Output\" title=\"Direct link to AI Assistant's Output\" translate=\"no\">​</a></h2>\n<p>I've not had time to do much work in Python for the past year, so I'm a bit\nrusty. And, I'm very pressed for time. 💡Light bulb moment: maybe an AI\nassistant can help me out. Let's give GitHub Copilot a shot at this.</p>\n<blockquote>\n<p>Using the attached Open API input file as an example, generate a Python\nprogram that converts this file into a CSV file with one row for every schema\nproperty, and columns for the schema name, property name, property type, and\ndescription. Also add a column for the path associated with that schema\ncomponent.</p>\n</blockquote>\n<p>The prompt was easy to write and feels natural. I don't really need the schema\nname, but I suspect it will be useful. The last part, about appending the Path,\npurposefully assumes an understanding of OpenAPI: namely, how to connect the\ndots from a <code>schema</code> back up to <code>path</code>, via <code>$ref</code>. I do not expect success with\nthe Path.</p>\n<p>The code output by GitHub Copilot is listed in the <a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#original-output\" class=\"\">appendix\nbelow</a>. I run it. A short time later, it finishes with a clean\nexit and a shiny new CSV file in my directory.</p>\n<p>But is it right?</p>\n<table><thead><tr><th>schema_name</th><th>property_name</th><th>property_type</th><th>description</th><th>path</th></tr></thead><tbody><tr><td>edFi_absenceEventCategoryDescriptor</td><td>id</td><td>string</td><td></td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_absenceEventCategoryDescriptor</td><td>codeValue</td><td>string</td><td>A code or...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_absenceEventCategoryDescriptor</td><td>namespace</td><td>string</td><td>\"A globally...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_absenceEventCategoryDescriptor</td><td>description</td><td>string</td><td>The description...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_academicHonorCategoryDescriptor</td><td>id</td><td>string</td><td></td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_academicHonorCategoryDescriptor</td><td>codeValue</td><td>string</td><td>A code or...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_academicHonorCategoryDescriptor</td><td>namespace</td><td>string</td><td>\"A globally...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_academicHonorCategoryDescriptor</td><td>description</td><td>string</td><td>The description...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_academicSubjectDescriptor</td><td>id</td><td>string</td><td></td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_academicSubjectDescriptor</td><td>codeValue</td><td>string</td><td>A code or...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_academicSubjectDescriptor</td><td>namespace</td><td>string</td><td>\"A globally...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr><tr><td>edFi_academicSubjectDescriptor</td><td>description</td><td>string</td><td>The description...</td><td>/ed-fi/absenceEventCategoryDescriptors</td></tr></tbody></table>\n<p>It is! (Sidenote: the quotation marks on the <code>description</code> of <code>namespace</code> are due to the\npresence of commas). How did it get the paths right? Let's look at a bit of code:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> path</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> path_details </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> paths</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">items</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> schema </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> schemas</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">items</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            rows</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">extend</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">process_schema</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> schema</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> path</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<p>It simply iterates over the path elements 🤔. Let's look at the output again.\nOh.... look carefully at the last entry shown above: it has the wrong path. My\ninitial enthusiasm short-circuited my quality assurance brain and I stopped\nlooking carefully after the first several rows. The correctness of those first\nrows was just happenstance, from the alphabetic order of the elements in the\nspecification file.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"revision\">Revision<a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#revision\" class=\"hash-link\" aria-label=\"Direct link to Revision\" title=\"Direct link to Revision\" translate=\"no\">​</a></h2>\n<p>Can I revise the prompt in a way that will instruct Copilot on how to navigate\nOpenAPI?</p>\n<p>Perhaps. But why bother? Time for the human to take over. We can easily convert\nthe <code>schema_name</code> column into the desired content from the <code>path</code> column.\nTelling Copilot how to do this would take me longer than simply writing it in\nPython myself.</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">namespace</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> entity</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">split</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"_\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">path </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"/</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">namespace</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">/</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">entity</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"conclusion\">Conclusion<a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#conclusion\" class=\"hash-link\" aria-label=\"Direct link to Conclusion\" title=\"Direct link to Conclusion\" translate=\"no\">​</a></h2>\n<p>Given my rust, Copilot saved me 15 to 30 minutes of jogging my memory and\niterating over the small mistakes I likely would have made. It took me most of\nthe way, and I was able to take over quickly to finish the job.</p>\n<p>A more skillful engineer could surely instruct the assistant on how to navigate the\nOpen API specification to achieve the desired goal. But this is entirely\nunnecessary; it would have been a poor use of time and electricity.</p>\n<p>It was a simple script that will help avoid hours of tedious, manual labor to\nconvert the JSON to CSV. The stakes were low - this is not a production\napplication, the script size is small, and reinforcing my Python skills is not a\npresent priority - and yet this felt like a small win that even this reluctant\nuser is happy to accept.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"appendix-the-code\">Appendix: the Code<a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#appendix-the-code\" class=\"hash-link\" aria-label=\"Direct link to Appendix: the Code\" title=\"Direct link to Appendix: the Code\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"original-output\">Original Output<a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#original-output\" class=\"hash-link\" aria-label=\"Direct link to Original Output\" title=\"Direct link to Original Output\" translate=\"no\">​</a></h3>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> csv</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">extract_schemas</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    schemas </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'components'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'schemas'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> schemas</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">extract_paths</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    paths </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'paths'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> paths</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">process_schema</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> schema</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> path</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    rows </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    properties </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> schema</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'properties'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> prop_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> prop_details </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> properties</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">items</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        row </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'schema_name'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'property_name'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> prop_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'property_type'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> prop_details</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'type'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'N/A'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'description'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> prop_details</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'description'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'N/A'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'path'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> path</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        rows</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">append</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">row</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> rows</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">main</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">with</span><span class=\"token plain\"> </span><span class=\"token builtin\">open</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'swagger.json'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'r'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">as</span><span class=\"token plain\"> f</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        openapi_data </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">load</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">f</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    schemas </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> extract_schemas</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    paths </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> extract_paths</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    rows </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> path</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> path_details </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> paths</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">items</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> schema </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> schemas</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">items</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            rows</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">extend</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">process_schema</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> schema</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> path</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">with</span><span class=\"token plain\"> </span><span class=\"token builtin\">open</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'output.csv'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'w'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> newline</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">''</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">as</span><span class=\"token plain\"> csvfile</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        fieldnames </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">'schema_name'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'property_name'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'property_type'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'description'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'path'</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        writer </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> csv</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">DictWriter</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">csvfile</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> fieldnames</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">fieldnames</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        writer</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">writeheader</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> row </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> rows</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            writer</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">writerow</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">row</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> __name__ </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'__main__'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    main</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"modified-version\">Modified Version<a href=\"https://blog.safnet.com/2025/01/24/openapi-to-csv#modified-version\" class=\"hash-link\" aria-label=\"Direct link to Modified Version\" title=\"Direct link to Modified Version\" translate=\"no\">​</a></h3>\n<p>With additional modification to remove <code>schema_name</code> and place <code>path</code> as the\nfirst column.</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> csv</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">extract_schemas</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    schemas </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'components'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'schemas'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> schemas</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">convert_to_path</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">namespace</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> entity</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">split</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"_\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"/</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">namespace</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">/</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">entity</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">process_schema</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> schema</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    rows </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    properties </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> schema</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'properties'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> prop_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> prop_details </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> properties</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">items</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Only interested in schemas of form \"&lt;namespace&gt;_&lt;entity&gt;\". Lazily just check for underscore.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">not</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"_\"</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">continue</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        row </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'path'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> convert_to_path</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'property_name'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> prop_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'property_type'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> prop_details</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'type'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'N/A'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">'description'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> prop_details</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'description'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'N/A'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        rows</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">append</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">row</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> rows</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">main</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">with</span><span class=\"token plain\"> </span><span class=\"token builtin\">open</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'swagger.json'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'r'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">as</span><span class=\"token plain\"> f</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        openapi_data </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">load</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">f</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    schemas </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> extract_schemas</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">openapi_data</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    rows </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> schema </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> schemas</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">items</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        rows</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">extend</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">process_schema</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">schema_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> schema</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">with</span><span class=\"token plain\"> </span><span class=\"token builtin\">open</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">'output.csv'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'w'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> newline</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">''</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">as</span><span class=\"token plain\"> csvfile</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        fieldnames </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">'path'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'property_name'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'property_type'</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'description'</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        writer </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> csv</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">DictWriter</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">csvfile</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> fieldnames</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">fieldnames</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        writer</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">writeheader</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> row </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> rows</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            writer</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">writerow</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">row</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> __name__ </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'__main__'</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    main</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>",
            "url": "https://blog.safnet.com/2025/01/24/openapi-to-csv",
            "title": "OpenAPI to CSV with Help from GitHub Copilot",
            "summary": "Following up on Grudgingly Accepting AI Coding Assistants, I have a small win from my",
            "date_modified": "2025-01-24T00:00:00.000Z",
            "tags": [
                "programming",
                "technology",
                "ed-fi"
            ]
        },
        {
            "id": "https://blog.safnet.com/2025/01/14/accepting-ai-assistants",
            "content_html": "<p>As a software engineering director building open source products, I have\nprohibited my teams from using AI coding assistants due to concerns about\nintellectual property and questions about the risks and real world effectiveness\nof AI coding assistants. It is now time to allow and even encourage AI coding\nassistants, with guardrails.</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"Balcones National Wildlife Refuge, by Stephen A. Fuqua\" src=\"https://blog.safnet.com/assets/images/panoramic_balcones_nwr-4bf9a78ad4e32e83ec3ab4b7bb3751e7.webp\" width=\"1024\" height=\"278\" class=\"img_ev3q\"></p><p>Balcones National Wildlife Refuge, December 2024, by Stephen A. Fuqua</p></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"intellectual-property\">Intellectual Property<a href=\"https://blog.safnet.com/2025/01/14/accepting-ai-assistants#intellectual-property\" class=\"hash-link\" aria-label=\"Direct link to Intellectual Property\" title=\"Direct link to Intellectual Property\" translate=\"no\">​</a></h2>\n<p>My fundamental argument against LLM-based coding assistants was this:</p>\n<ol>\n<li class=\"\">These LLMs are trained on open source software.</li>\n<li class=\"\">That training and/or the use of code created from that trained model may be a\nviolation of their license agreements.</li>\n<li class=\"\">My teams build open source software and we do not want our license violated.</li>\n<li class=\"\">Golden rule: do unto others as you would have them do unto you.</li>\n<li class=\"\">Ergo, do not use coding assistants.</li>\n</ol>\n<p>Not surprisingly, many others feel the same, as noted in <a href=\"https://www.theregister.com/2023/05/12/github_microsoft_openai_copilot/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\"><em>The\nRegister</em></a>\n(2023-05-12):</p>\n<blockquote>\n<p>Software developers have bristled that Codex and Copilot were created from\ntheir code, and sometimes reproduce it, without explicit permission or concern\nfor the terms under which they licensed their work. And some of them have sued\nover it.</p>\n</blockquote>\n<p>But <a href=\"https://www.theverge.com/2024/7/9/24195233/github-ai-copyright-coding-lawsuit-microsoft-openai\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">the developers suing over GitHub Copilot got dealt a major blow in\ncourt</a>\n(<em>The Verge</em>, 2024-07-09) — although the open source license violation is\nstill an open question.</p>\n<p>At least two coding assistants, <a href=\"https://www.tabnine.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Tabnine</a> and <a href=\"https://aws.amazon.com/q/developer/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Amazon\nQ Developer</a> (fka Code Whisperer), offer up\nattributions that you can paste into your open source notifications. GitHub has\nalso added public code filtering and now <a href=\"https://docs.github.com/en/copilot/using-github-copilot/finding-public-code-that-matches-github-copilot-suggestions\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">reference\nlinks</a>\nwhen public code filtering is off, which may satisfy the attribution /\nnotification problem. Whether the <em>training</em> is fully legal given the open\nsource licenses remains as an open risk. However, I do not seriously believe that the\ncourt system will strike down LLMs on the basis of being trained on others'\nintellectual property.</p>\n<p>With multiple free and low-cost tools available, and no way to police individual\nusage, it has long been a moot point. People are probably using these tools\nalready, unbeknownst to me, even though I've asked them not to. Time for me to\nstop fighting this and focus on safe and effective usage.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"effectiveness\">Effectiveness<a href=\"https://blog.safnet.com/2025/01/14/accepting-ai-assistants#effectiveness\" class=\"hash-link\" aria-label=\"Direct link to Effectiveness\" title=\"Direct link to Effectiveness\" translate=\"no\">​</a></h2>\n<p>Are these tools effective? There's the marketing, and then there's real world\nexperience. The articles I've been reading suggest both sides of things: that\nthe code produced by AI assistants can be garbage, and that it can be helpful.\nThe code likely will require judicious editing from a trained and talented\nengineer. And, that engineer will need to be talented not only in the faculty of\nunderstanding the code, but also in learning how to <a href=\"https://medium.com/@tsecretdeveloper/the-real-reason-we-still-need-software-developers-in-the-world-of-ai-a2dd42afcaeb\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">provide meaningful\ninstructions</a>\nto the AI assistant.</p>\n<p>As many other writers have noted, a world of AI coding assistants could lead to a\nworld where junior developers never learn to code well and thus have trouble\nimproving the code produced by the assistant. Or have trouble recognizing major\nsecurity flaws in the code (<a href=\"https://www.forrester.com/blogs/predictions-2024-security-and-risk/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Predictions 2024: Security And Risk Pros Will Apply\nGuardrails Beyond Regulatory\nMandates</a>\nfrom Forrester). Even seasoned veterans can easily overlook security problems\nif they review too quickly.</p>\n<p>Last fall, I installed Amazon Q Developer and asked it to help me write\nadditional unit tests for a C# class that had been neglected. It helped me\nuncover several important edge cases around null handling. I failed to tell it that\nI wanted my tests in NUnit and that I prefer use of FluentAssertions; it gave me\nXUnit that I then converted to NUnit (admittedly I cannot remember if a\nlimitation prevented me from tuning my request, or if I was just annoyed and\nwanted to limit my interactions).</p>\n<p>Overall, I estimate it saved me about 20 minutes time on that task. And it\nhelped find a low probability bug in the system-under-test that I may have\nmissed. That was useful.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"guardrails\">Guardrails<a href=\"https://blog.safnet.com/2025/01/14/accepting-ai-assistants#guardrails\" class=\"hash-link\" aria-label=\"Direct link to Guardrails\" title=\"Direct link to Guardrails\" translate=\"no\">​</a></h2>\n<p>We will continue to enforce existing practices:</p>\n<ol>\n<li class=\"\">Use of linters (JavaScript, Python, PowerShell) and\n<a href=\"https://learn.microsoft.com/en-us/visualstudio/code-quality/roslyn-analyzers-overview?view=vs-2022\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Roslyn-based</a>\ncode analyzers for C# (tip:\n<a href=\"https://www.sonarsource.com/products/sonarlint/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Sonarlint</a> and\n<a href=\"https://github.com/SonarSource/sonar-dotnet\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">sonar-dotnet</a>).</li>\n<li class=\"\">Use of static application security tools (e.g. CodeQL) on all contributions\nto our open source code, whether made by staff, contractors, or the public.</li>\n<li class=\"\">Mandatory peer review and merge process into the protected <code>main</code> branch that\ngenerates our binary releases.</li>\n</ol>\n<p>To complement these, we must encourage and support developers to strengthen\ntheir own habits as well as their abilities to sniff out and question\npotentially harmful or simply dodgy code. And we should encourage a community of\npractice where developers on different teams freely share about their\nexperiences.</p>\n<div class=\"theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z\"></path></svg></span>danger</div><div class=\"admonitionContent_BuS1\"><p>Those who are working with closed source applications will want to read the fine\nprint to understand the privacy and security implications of your use of an AI\ncoding assistant. For example, <a href=\"https://github.com/features/copilot\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">GitHub\nsays</a>: \"GitHub does not use either Copilot\nBusiness or Enterprise data to train its models.\". This seems to imply that it\n<em>does use</em> data from Free and Pro.</p></div></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Also see <a href=\"https://blog.gitguardian.com/github-copilot-security-and-privacy/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">GitHub Copilot Security and\nPrivacy Concerns: Understanding the Risks and Best\nPractices</a>\nfor more detailed security tips.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"personal-plans\">Personal Plans<a href=\"https://blog.safnet.com/2025/01/14/accepting-ai-assistants#personal-plans\" class=\"hash-link\" aria-label=\"Direct link to Personal Plans\" title=\"Direct link to Personal Plans\" translate=\"no\">​</a></h2>\n<p>Two weeks into the new year, I have yet to write a line of code. Unless you\nconsider Markdown files and Mermaid diagrams \"documentation as code\". At some\npoint, once the new year kickoff tasks are complete, I hope to jump into the\nfray and complete a few programming tasks. When I do, I plan to continue\nevaluating Amazon Q and may try out GitHub Copilot and Tabnine out of\ncuriosity. (Only tools that support VS Code make it on my evaluation list).</p>\n<p>Depending on the task at hand, I would like to experiment with these uses:</p>\n<ol>\n<li class=\"\">Generating unit tests. I practice and encourage <a class=\"\" href=\"https://blog.safnet.com/best-practices-tdd-oo\">Test Driven\nDevelopment</a>. But not everyone does.\nLow-hanging fruit for me to contribute, without distracting the team: help\nthem beef up the code coverage. (Moral Hazard Warning: only cover older code\nor expand existing tests. Don't let this be an excuse for not testing!).</li>\n<li class=\"\">Asking the assistant to review my code. <a href=\"https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/start-review.html\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Amazon\nQ</a>,\n<a href=\"https://docs.github.com/en/copilot/using-github-copilot/code-review/using-copilot-code-review\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">GitHub\nCopilot</a>,\n<a href=\"https://www.tabnine.com/blog/unveiling-tabnines-code-review-agent/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Tabnine</a>.</li>\n<li class=\"\">Generating boilerplate code. Although this is a popular usage, we have\nlittle boilerplate code left to write. Our applications are at the stage of\nneeding real thought and design in order to grow into the next user story.</li>\n</ol>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"parting-words\">Parting Words<a href=\"https://blog.safnet.com/2025/01/14/accepting-ai-assistants#parting-words\" class=\"hash-link\" aria-label=\"Direct link to Parting Words\" title=\"Direct link to Parting Words\" translate=\"no\">​</a></h2>\n<p>To all of the developers I work with: go forth and write quality\ncode, with or without an AI assistant.</p>",
            "url": "https://blog.safnet.com/2025/01/14/accepting-ai-assistants",
            "title": "Grudgingly Accepting AI Coding Assistants",
            "summary": "As a software engineering director building open source products, I have",
            "date_modified": "2025-01-14T00:00:00.000Z",
            "tags": [
                "programming",
                "technology"
            ]
        },
        {
            "id": "https://blog.safnet.com/2024/12/31/recommended-reading-new-managers",
            "content_html": "<p>Manager-dom was not a sought-after status (though I admit to prior curiosity),\nbut as with many other engineers-turned-managers, it found me anyway (Q3 2023).\nBeing a team lead and then an architect came naturally and brought success for\nmany years. Some of the skills translate well from those roles while other\nnecessary capabilities tend to be underdeveloped. Nothing can truly substitute\nfor the value of gaining experience and reflecting on it; here are a few\nresources that I have found useful in my journey so far, giving me new ideas to\nplay with and questions to aid my reflection.</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"Sharp-shinned Hawk, by Stephen A. Fuqua\" src=\"https://blog.safnet.com/assets/images/sharp-shinned-hawk-166a4e1a7762d571338182aa621c5d52.webp\" width=\"660\" height=\"360\" class=\"img_ev3q\"></p><p>Sharp-shinned Hawk at Resaca de la Palma State Park, by Stephen A. Fuqua (2024).</p></div>\n<p>Let's start with <a href=\"https://zaidesanton.substack.com/p/a-letter-to-the-first-time-manager\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">What I wish somebody told me during my first year as a\nmanager</a>,\nwhich I only encountered yesterday. Most of its advice is not new, and yet\nit is nevertheless timely. What I most appreciated was this acknowledgment:</p>\n<blockquote>\n<p>\"You didn’t expect the emotional drain, right? That some days you’ll be so\nexhausted from being responsible for everything, that you’ll come home and\njust collapse on the couch.\" (Anton Zaides)</p>\n</blockquote>\n<p>For me it wasn't so much a collapse on the couch as a recognition that it became\nharder to get a second (third?) wind after dinner and on the weekend, at least\nfor creative endeavors or mentally challenging avocational work.</p>\n<p>Here are two books that offer mutually-reinforcing advice, though in wildly\ndifferent writing styles:</p>\n<ul>\n<li class=\"\">\n<p><a href=\"https://en.wikipedia.org/wiki/First,_Break_All_the_Rules\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">First, Break All the\nRules</a> was a\nrecommendation from a veteran manager. The book has been influential enough\nthat some of the rules-to-be-broken already seemed a little dated, because\npeople in my orbit have already been breaking them. That does not diminish its\nvalue in bringing deeply researched topics to light. This was a very worthy\nphysical book purchase - a tome that I am sure to revisit periodically for\nyears to come.</p>\n</li>\n<li class=\"\">\n<p><a href=\"https://managinghumans.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Managing Humans</a> is a series of\nfrequently-humorous essays by <a href=\"https://randsinrepose.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Michael Lopp</a>,\nwhich I found in my <a href=\"https://learning.oreilly.com/library/view/managing-humans-more/9781484271162/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">O'Reilly\nsubscription</a>.\nOne of my highlights that I need to continue practicing in 2025:</p>\n<blockquote>\n<p>\"Every month, your team produced something and it's your job to document\nthat production. I do this by spending an hour a month jotting down\nreflections of the team for the past 30 days. What stands out in my mind?\nWhat'd we do? Who rocked?\" (Michael Lopp).</p>\n</blockquote>\n</li>\n</ul>\n<p>Both books have topics addressing the root causes of the <em>emotional drain</em>.\nSometimes you can't avoid the drain, which probably means having a difficult\nconversation. <a href=\"https://en.wikipedia.org/wiki/Crucial_Conversations:_Tools_for_Talking_When_Stakes_Are_High\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Crucial Conversations: Tools for Talking When Stakes Are\nHigh</a>\nwill help you move past the fight-or-flight tendency into a productive\ninteraction.</p>\n<p>Finally, at some point I skimmed through <a href=\"https://management30.com/books/management30/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\"><em>Management 3.0: Leading Agile\nDevelopers, Developing Agile\nLeaders</em></a>, a book I had first read\nten years ago (cf in this blog: <a class=\"\" href=\"https://blog.safnet.com/2014/05/17/easy-simple-lazy-efficient\">easy : simple :: lazy :\nefficient</a> and <a class=\"\" href=\"https://blog.safnet.com/2015/01/20/management_30_knowledge_and_diversity\">Management\n3.0: Knowledge and\nDiversity</a>). A\nuseful resource, but, unfortunately, what currently stands out in this reader's\nmind is the author's off-putting sarcasm.</p>",
            "url": "https://blog.safnet.com/2024/12/31/recommended-reading-new-managers",
            "title": "Tools for Engineering New Managers",
            "summary": "Manager-dom was not a sought-after status (though I admit to prior curiosity),",
            "date_modified": "2024-12-31T00:00:00.000Z",
            "tags": [
                "books",
                "meta"
            ]
        },
        {
            "id": "https://blog.safnet.com/2024/06/24/living-with-agile",
            "content_html": "<p>In reaction to all of the \"agile is dead\" articles, I am cleaning up <a class=\"\" href=\"https://blog.safnet.com/tags/agile\">old posts\nabout Agile</a>, re-reading them, contemplating lessons learned but\nforgotten, and asking myself if some practices have outlived their usefulness.\nThat is the spirit of agility: the interplay of action and reflection.</p>\n<p>\"Agile\" is not a <a href=\"https://en.wikipedia.org/wiki/No_Silver_Bullet\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">silver bullet</a>\nfor improving software productivity, reliability, and simplicity. But \"Agile\"\ncontinues to give us tools that <em>can</em> foster improved software engineering.</p>\n<p>The Agile Manifesto was a distillation of certain trends that the authors had\nnoticed in their successful projects. On balance, they ring true to my\ntwenty-five year career in software.</p>\n<p>It did not offer guarantees and it did not offer to solve world hunger. Through\nprescriptive frameworks such as Extreme Programming and Scrum, and common\npractices such as development of story cards and short cycles (aka sprints), the\nAgile \"revolution\" broke us free from the confines of gigantic requirements and\ndesign documents that were always at least slightly wrong, and frequently very\ndifficult to change. It helped us embrace the uncertainty of software\ndevelopment, empowering us to find our way out of that wrongness more quickly\nand productively.</p>",
            "url": "https://blog.safnet.com/2024/06/24/living-with-agile",
            "title": "Living with Agile",
            "summary": "In reaction to all of the \"agile is dead\" articles, I am cleaning up [old posts",
            "date_modified": "2024-06-24T00:00:00.000Z",
            "tags": [
                "technology",
                "programming",
                "agile"
            ]
        },
        {
            "id": "https://blog.safnet.com/2024/05/01/bahai-principles-for-climate-action",
            "content_html": "<p>Amongst its many admirable principles, where does one find environmental\nconcerns in the Bahá'í Faith? So I wondered, early on my journey on this\nreligious path. Recently, in honor of <a href=\"https://interfaithpowerandlight.org/faithclimateactionweek/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Faith Climate Action\nWeek</a>, several\nfriends and I reflected on this question.</p>\n<p>Our conversation opened with a brief meditation on this passage of praise for\nGod, who is recognized through all the natural wonders:</p>\n<blockquote>\n<p>“… whatever I behold I readily discover that it maketh Thee known\nunto me, and it remindeth me of Thy signs, and of Thy tokens, and of Thy\ntestimonies. By Thy glory! Every time I lift up mine eyes unto Thy heaven, I\ncall to mind Thy highness and Thy loftiness, and Thine incomparable glory and\ngreatness; and every time I turn my gaze to Thine earth, I am made to\nrecognize the evidences of Thy power and the tokens of Thy bounty. And when I\nbehold the sea, I find that it speaketh to me of Thy majesty, and of the\npotency of Thy might, and of Thy sovereignty and Thy grandeur. And at whatever\ntime I contemplate the mountains, I am led to discover the ensigns of Thy\nvictory and the standards of Thine omnipotence.”</p>\n<p>Bahá'u'lláh, <em>Prayers and Meditations by Bahá’u’lláh</em>, <a href=\"https://www.bahai.org/r/129964059\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">CLXXVI</a></p>\n</blockquote>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"Naw Ruz flowers at Enchanted Rock State Natural Area, by Stephen A. Fuqua\" src=\"https://blog.safnet.com/assets/images/Enchanged-Rock-Flowers-2024-03-20-731cca28226eefe32cb3f359984c9334.jpg\" width=\"1327\" height=\"323\" class=\"img_ev3q\"></p><p>Wildflowers on Naw Ruz at Enchanted Rock State Natural Area, by Stephen A. Fuqua (2024).</p></div>\n<p>From there, we turned to the importance of thinking about the oneness of\nhumanity, and the analogy: if one limb of the body is afflicted, the entire\nhuman body suffers. So too is it with individuals in a community. Thus, knowing\nthat the climate crisis afflicts humanity, we must act to avert that crisis as\nbest we can.</p>\n<p>Bahá'í literature and teachings first emphasize the principals of the \"three\nonenesses\" (or \"unities\"): that <a href=\"https://www.bahai.us/beliefs/building-community/oneness-of-humanity/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">of\nhumanity</a>\nis joined by recognition of the oneness of\n<a href=\"https://www.bahai.us/beliefs/building-community/oneness-of-humanity/beliefs/building-community/oneness-of-god/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">God</a>\nand of\n<a href=\"https://www.bahai.us/beliefs/building-community/progressive-revelation/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">religion</a>.\nNext, they frequently (and rightly) echo\n<a href=\"https://bahaipedia.org/List_of_Bah%C3%A1%E2%80%99%C3%AD_social_principles\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">principles</a>\nenunciated by 'Abdu'l-Bahá in His writings and talks, dating especially to the\nfirst decades of the twentieth century. Lists of those principles frequently\nreference abolition of prejudice, equality of the sexes, independent\ninvestigation of the truth, harmony of science and religion, world peace,\ninternational governance, universal auxiliary language, and compulsory\neducation. Ther are no forewarnings about health impact of pollution, the future\nclimate calamity, or loss of biodiversity.</p>\n<p>On the one hand: these were not the most pressing challenges of the time. And on\nthe other: these very principles, and the institutions inaugurated by\nBahá'u'lláh and 'Abdu'l-Bahá, are essential for solving the new questions and\nchallenges that have arisen since that time.</p>\n<p>Not surprisingly, we quickly identified \"harmony of science and religion\" as a\nkey concept in thinking about climate change and related issues . This principle\nsuggests that religion bereft of science – rationalism, investigation\n– tends toward superstition and social stagnation. And science and\ntechnological innovation without spirituality and religion tends toward a\nmaterialism that demeans individual dignity, facilitates an uneven distribution\nof wealth, and encourages excessive consumption.</p>\n<p>Clearly there is a very strong link between excess consumption, pollution, and\nclimate degradation. Though not in that list above, Bahá'u'lláh mentioned the\nimportance of \"moderation\" many times. A favorite and germane example:</p>\n<blockquote>\n<p>“The civilization, so often vaunted by the learned exponents of arts and\nsciences, will, if allowed to overleap the bounds of moderation, bring great\nevil upon men. Thus warneth you He Who is the All-Knowing. If carried to\nexcess, civilization will prove as prolific a source of evil as it had been of\ngoodness when kept within the restraints of moderation.”</p>\n<p>Bahá'u'lláh, <em>Gleanings from the Writings of Bahá'u'lláh</em>, <a href=\"https://www.bahai.org/r/042388414\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">CLXIV</a></p>\n</blockquote>\n<p>We noted that fundamental concepts of personal spirituality naturally moderate\nour desires for physical gratification and consumption. The book <em>Reflections on\nthe Life of the Spirit</em> is the first unit in a <a href=\"https://www.ruhi.org/en/embarking-on-a-path-of-service/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">sequence of\ncourses</a> for youth and\nadults. The book is studied by Bahá'ís and their friends worldwide. Under unit\nheadings of \"Understanding the Bahá'í Writings\", \"Prayer\", and \"Life and Death\",\nthis book introduces spiritual practices of reading sacred scriptures daily;\nholding ourselves accountable for our thoughts and deeds; personal and communal\n(not congregational) prayer; and the notion of developing the life of the soul.\nSurely these are ameliorating influences to counter \"civilization\" \"carried to\nexcess.\"</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"A Central Texas Hill Country sunset, by Stephen A. Fuqua\" src=\"https://blog.safnet.com/assets/images/hill-country-sunset-e604187f7bb08d448130480e3674213a.jpg\" width=\"1331\" height=\"385\" class=\"img_ev3q\"></p><p>Central Texas Hill Country sunset, by Stephen A. Fuqua (2024).</p></div>\n<p>Another fundamental concept in the Faith, not listed in the principles above, is\nthe idea of\n<a href=\"https://www.bahai.org/action/institutional-capacity/spaces-consultation\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">consultation</a>:</p>\n<blockquote>\n<p>“The Great Being saith: The heaven of divine wisdom is illumined with\nthe two luminaries of consultation and compassion. Take ye counsel together in\nall matters, inasmuch as consultation is the lamp of guidance which leadeth\nthe way, and is the bestower of understanding.”</p>\n<p>Bahá'u'lláh, <em>Tablets of Bahá'u'lláh</em>, <a href=\"https://www.bahai.org/r/545364905\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Lawḥ-i-Maqṣúd</a></p>\n</blockquote>\n<p>This spiritual consultation is the idea of a group of people engaged in a frank\nand cordial search for the truth. It is a cornerstone of family, community, and\ninstitutional decision-making in the Bahá'í Faith, and a tool that Bahá'ís offer\ngladly to any who wish to make use of it.</p>\n<p>As we wound down our conversation, we noted how the knowledge, attribute, and\nactions imparted by all of the books in the sequence of courses teach lessons\nvaluable to those who recognize the need for improved environmental stewardship.\nFor example, the topic of consultation is treated many times, most especially in\nthe tenth book. The second book introduces the idea of \"meaningful\nconversations\" – aiding the participant to develop the skill of having\nthis very sort of conversation. Likewise each book could be linked in some way.</p>\n<p>Finally, we touched briefly on the importance of action: recognizing the\nimperative for the course of humanity to be shifted onto a sustainable path, we\nmust each determine how we can contribute to individual action while\nsimultaneously supporting collective endeavors.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"other-resources\">Other Resources<a href=\"https://blog.safnet.com/2024/05/01/bahai-principles-for-climate-action#other-resources\" class=\"hash-link\" aria-label=\"Direct link to Other Resources\" title=\"Direct link to Other Resources\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><a href=\"https://www.bahai.org/documents/compiled-research-department-universal-house-of-justice/conservation-earths-resources\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Conservation of the Earth's\nResources</a>,\ncompiled from the Bahá'í Writings in 1989.</li>\n<li class=\"\"><a href=\"https://www.bic.org/news/role-environment-future-global-governance\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">The Role of the Environment in the Future of Global\nGovernance</a>,\narticle from the Bahá’í International Community, 6 March 2024.</li>\n<li class=\"\"><a href=\"https://www.greenbahai.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">GreenBahai.com</a>, by your humble author.</li>\n<li class=\"\">From this blog:<!-- -->\n<ul>\n<li class=\"\"><a class=\"\" href=\"https://blog.safnet.com/2014/09/27/discourses_of_society_climate_change\">Discourses of Society: Climate Change</a></li>\n<li class=\"\"><a class=\"\" href=\"https://blog.safnet.com/2015/01/04/eco-spiritual_integration_three_texts_1\">Eco-spiritual Integration: Three Texts</a></li>\n<li class=\"\"><a class=\"\" href=\"https://blog.safnet.com/2013/04/10/bah_devotional_program_on_humanitys_relationship_with_nature\">Bahá'í­ Devotional Program on Humanity's Relationship with Nature</a></li>\n<li class=\"\"><a class=\"\" href=\"https://blog.safnet.com/2014/02/23/can_we_talk_about_climate_change_pt_1\">Can We Talk About Climate Change?</a></li>\n</ul>\n</li>\n</ul>",
            "url": "https://blog.safnet.com/2024/05/01/bahai-principles-for-climate-action",
            "title": "A Conversation on Bahá'í Principles for Climate Action",
            "summary": "Amongst its many admirable principles, where does one find environmental",
            "date_modified": "2024-05-01T00:00:00.000Z",
            "tags": [
                "religion",
                "nature",
                "bahai-faith",
                "discourse",
                "climate"
            ]
        },
        {
            "id": "https://blog.safnet.com/2024/04/18/cloud-native-edi-fi-api",
            "content_html": "<blockquote>\n<p>\"For the past two years, the Ed-Fi Alliance software development team has been\nlistening to community members through its Technical Advisory Group, Special\nInterest Groups, and at our annual events. We have been hearing that the pace\nof change in the Ed-Fi ODS/API Platform needs to accelerate, shifting to a\ncloud-native architecture that can better support large-scale deployments\nwhile offering greater cost and performance flexibility. To do so, we need a\nreboot.\"</p>\n</blockquote>\n<p>→ <em>Full article at <a href=\"https://www.ed-fi.org/blog/cloud-native-ed-fi-technology/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">New Cloud-Native Functionality Coming to the Ed-Fi Alliance\nTechnology Suite</a></em></p>\n<p>Though barely mentioned in the article, the work to create a production ready\nsystem has been dubbed <a href=\"https://github.com/Ed-Fi-Alliance-OSS/Project-Tanager\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Project\nTanager</a>, the third\nbird-related project name in my tenure with the Ed-Fi Alliance\n(<a class=\"\" href=\"https://blog.safnet.com/2019/06/04/postgresql-for-the-ods\">Roadrunner</a>,\n<a class=\"\" href=\"https://blog.safnet.com/2022/06/03/exploring-next-generation-meadowlark\">Meadowlark</a>).</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"Scarlet Tanager, by Adam Jackson, no rights reserved\" src=\"https://blog.safnet.com/assets/images/scarlet-tanager_by_adam-jackson_no-rights-reserved_square-256-36ec23fbe39b6f703c9cb1deeca3b344.png\" width=\"256\" height=\"256\" class=\"img_ev3q\"></p></div>",
            "url": "https://blog.safnet.com/2024/04/18/cloud-native-edi-fi-api",
            "title": "Project Tanager, the next generation of Ed-Fi API software",
            "summary": "\"For the past two years, the Ed-Fi Alliance software development team has been",
            "date_modified": "2024-04-18T00:00:00.000Z",
            "tags": [
                "programming",
                "ed-fi",
                "architecture"
            ]
        },
        {
            "id": "https://blog.safnet.com/2024/02/11/delivering-value-as-engineer",
            "content_html": "<p>In <a href=\"https://medium.com/@juraj.malenica/unpopular-opinion-its-harder-than-ever-to-be-a-good-software-engineer-8560585b0ea8\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Unpopular Opinion: It’s harder than ever to be a good software\nengineer</a>,\nauthor Juraj Malencia opines that \"good engineering\" equates to \"bring[ing]\nmaximum value in achieving a goal.\" This is a comforting thought for those of us\nperiodically wondering if we're still good software or data engineers, though we\nmight not be up-to-date on the latest hype.</p>\n<p>Pointing out that \"engineering does not equal programming\", implying that we can\nbring great \"engineering\" (solutioning) value without writing a line of code, he\npresents a modified version of a <a href=\"https://newsletter.pragmaticengineer.com/p/engineering-leadership-skillset-overlaps\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">venn\ndiagram</a>\non how people in various roles spend their time between programming, alignment,\npeople, and \"other\". I was bemused to note I currently sit squarely in the\ncategory labeled \"beware\" 🤨. A position that may continue throughout this year,\nbut will need to change over time.</p>\n<p>One way that we can deliver value as senior engineers is by knowing when, and\nhow, to break the rules. Although I am now in management, I am staying close to\nthe work of one of our development teams. When I saw them struggling to meet the\nsprint commitment, I decided to lean in. There was a tangled situation with\nlibrary dependencies. The thorniest problem lay with library A, which depended\non a dozen or so classes in library B. Remembering a lesson I learned from a\nvery smart \"junior\" developer a few years ago, I decided that Write Everything\nTwice (WET) trumped Don't Repeat Yourself (DRY) in this case.</p>\n<p>In other words, I just copied the required code from library B directly into\nlibrary A. In this case, all of the copied code was highly stable, and generally\nof a \"utility\" nature rather than being business logic. Problem solved.</p>\n<p>However: I probably could have contributed similar value by simply suggesting\nthat the team duplicate that code, rather than taking it on myself. It felt good\nto do the work, and helped unblock the team, but it might not have been the\nmaximum value I could have brought <em>to the business</em>. What was I not getting\ndone - the opportunity cost? This is a critical lesson for this first-time\nmanager to learn: think twice (or more) before jumping into the code. Get better\nat empowering than (hands-on) solving.</p>\n<div class=\"image\"><p><img decoding=\"async\" loading=\"lazy\" alt=\"Alexander the Great, cutting the Gordian Knot\" src=\"https://blog.safnet.com/assets/images/gordian-knot-b99c0f9095b8e2b8fd2216154b862114.png\" width=\"1600\" height=\"846\" class=\"img_ev3q\"></p><p>Maturino da Firenze. <a href=\"https://www.artic.edu/artworks/7441/alexander-cutting-the-gordian-knot\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Alexander Cutting the Gordian\nKnot</a>,\n1510-1527. The Art Institute of Chicago.</p></div>",
            "url": "https://blog.safnet.com/2024/02/11/delivering-value-as-engineer",
            "title": "Delivering Value as a Software Engineering Manager",
            "summary": "In [Unpopular Opinion: It’s harder than ever to be a good software",
            "date_modified": "2024-02-11T00:00:00.000Z",
            "tags": [
                "programming"
            ]
        },
        {
            "id": "https://blog.safnet.com/2023/12/08/edfi-client-from-swagger",
            "content_html": "<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"motivation\">Motivation<a href=\"https://blog.safnet.com/2023/12/08/edfi-client-from-swagger#motivation\" class=\"hash-link\" aria-label=\"Direct link to Motivation\" title=\"Direct link to Motivation\" translate=\"no\">​</a></h2>\n<p>The <a href=\"https://techdocs.ed-fi.org/display/ODSAPIS3V71\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Ed-Fi ODS/API</a> is a REST\nAPI that support interoperability of student data systems. The API definition,\nvia the <a href=\"https://techdocs.ed-fi.org/display/ETKB/Ed-Fi+Standards\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Ed-Fi Data\nStandard</a>, is\nextensible: many large-scale or specialized implementations add their own local\nuse cases that are not supported out of the box by the Ed-Fi Data Standard\n(Extensions). Furthermore, the Data Standard receives regular updates; sometimes\nthese are merely additive, and from time to time there are breaking changes.\nThese factors make it impossible to create a one-size fits all client\nlibrary.</p>\n<p>But, not all is lost: the ODS/API exposes its API definition using\n<a href=\"https://www.openapis.org/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">OpenAPI</a>, and we can use <a href=\"https://swagger.io/tools/swagger-codegen/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Swagger\nCodegen</a> to build a client library\nbased on the target installation's data model / API spec. The basic process of\ncreating a C# code library (SDK) is described in Ed-Fi documentation at <a href=\"https://techdocs.ed-fi.org/display/ODSAPIS3V71/Using+Code+Generation+to+Create+an+SDK\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Using\nCode Generation to Create an\nSDK</a>\n(Note: this link is for ODS/API 7.1, but the instructions are essentially the\nsame for all versions).</p>\n<p>But what about Python? Yes, Swagger Codegen supports Python output. But it is\nnot quite enough - you also need to manage authentication on your own. And,\nrunning Swagger Codgen requires the Java Development Kit (JDK). The notes below\nwill walk through generating a client library with help from Docker (no local\ninstall of the JDK required) and demonstrate basic usage of a simple\n<code>TokenManager</code> class for handling authentication.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>See\n<a href=\"https://github.com/stephenfuqua/Ed-Fi-API-Client-Python\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Ed-Fi-API-Client-Python</a>\nfor a source code repository containing the <code>TokenManager</code> class listed below,\nwhich might receive updates after this post has been published.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"generating-an-ed-fi-client-package\">Generating an Ed-Fi Client Package<a href=\"https://blog.safnet.com/2023/12/08/edfi-client-from-swagger#generating-an-ed-fi-client-package\" class=\"hash-link\" aria-label=\"Direct link to Generating an Ed-Fi Client Package\" title=\"Direct link to Generating an Ed-Fi Client Package\" translate=\"no\">​</a></h2>\n<p>The Swagger Codegen tool is available as a <a href=\"https://github.com/swagger-api/swagger-codegen#public-pre-built-docker-images\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">pre-built Docker\nimage</a>,\nat repository <code>swaggerapi/swagger-codegen-cli</code>. We will use it to build a\nclient package for working with Ed-Fi Data Standard v5.0, which is available through\nEd-Fi ODS/API v7.1. The <a href=\"https://api.ed-fi.org/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">ODS/API Landing Page</a> has links\nto the Swagger UI-based \"documentation\" (UI on top of OpenAPI specification) for\nall currently supported versions of the ODS/API. From there, we can find a link\nto the <a href=\"https://api.ed-fi.org/v7.1/api/metadata/data/v3/resources/swagger.json\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">specification\ndocument</a>.</p>\n<p>The example shell commands use PowerShell, and they are easily adaptable to Bash\nor another shell. The generated code will be in a new <code>edfi-client</code> directory.\nNote that this repository's <code>.gitignore</code> file excludes this directory from\nsource control, since the original intent of this repository is to provide\ninstructions, not a full-blown client. If you fork this repository and want to\ncreate your own package, then you may wish to remove that line from <code>.gitignore</code>\nso that you can keep your custom client code in your forked repository.</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token variable\" style=\"color:#36acaa\">$url</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"https://api.ed-fi.org/v7.1/api/metadata/data/v3/resources/swagger.json\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$outputDir</span><span class=\"token plain\"> = </span><span class=\"token string\" style=\"color:#e3116c\">\"./edfi-client\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">New-Item</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">Path </span><span class=\"token variable\" style=\"color:#36acaa\">$outputDir</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token function\" style=\"color:#d73a49\">Type</span><span class=\"token plain\"> Directory </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">Force </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">out-null</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token variable\" style=\"color:#36acaa\">$outputDir</span><span class=\"token plain\"> = </span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token function\" style=\"color:#d73a49\">Resolve-Path</span><span class=\"token plain\"> </span><span class=\"token variable\" style=\"color:#36acaa\">$outputDir</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">docker run </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token function\" style=\"color:#d73a49\">rm</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">v </span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token string function\" style=\"color:#d73a49\">$</span><span class=\"token string function punctuation\" style=\"color:#393A34\">(</span><span class=\"token string function variable\" style=\"color:#36acaa\">$outputDir</span><span class=\"token string function punctuation\" style=\"color:#393A34\">)</span><span class=\"token string\" style=\"color:#e3116c\">:/local\"</span><span class=\"token plain\"> swaggerapi/swagger-codegen-</span><span class=\"token function\" style=\"color:#d73a49\">cli</span><span class=\"token plain\"> generate `</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">i </span><span class=\"token variable\" style=\"color:#36acaa\">$url</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">l python </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">o </span><span class=\"token operator\" style=\"color:#393A34\">/</span><span class=\"token plain\">local</span><br></div></code></pre></div></div>\n<p>On my machine, this took about a minute to run. Here's what we get as output:</p>\n<div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">&gt; </span><span class=\"token function\" style=\"color:#d73a49\">ls</span><span class=\"token plain\"> edfi-client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    Directory: C:\\source\\Ed-Fi-API-Client-Python\\edfi-client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Mode                 LastWriteTime         Length Name</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">                 </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">         </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">d-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">        11/27/2023   9:31 PM                </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">swagger-codegen</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">d-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">        11/27/2023   9:31 PM                docs</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">d-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">        11/27/2023   9:32 PM                out</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">d-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">        11/27/2023   9:31 PM                swagger_client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">d-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token plain\">        11/27/2023   9:31 PM                test</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM            786 </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">gitignore</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM           1030 </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">swagger-codegen-ignore</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM            359 </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">travis</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">yml</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM           1663 git_push</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">sh</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM         351139 README</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">md</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM             96 requirements</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">txt</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM           1811 setup</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">py</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM             69 </span><span class=\"token function\" style=\"color:#d73a49\">test-requirements</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">txt</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">a-</span><span class=\"token operator\" style=\"color:#393A34\">--</span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token plain\">        11/27/2023   9:31 PM            149 tox</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ini</span><br></div></code></pre></div></div>\n<p>We have code, we have tests, and even documentation. Here is a usage example from\none of the auto-generated docs:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> __future__ </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> print_function</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> time</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> swagger_client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">rest </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> ApiException</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> pprint </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> pprint</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Configure OAuth2 access token for authorization: oauth2_client_credentials</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">configuration </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">Configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">access_token </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'YOUR_ACCESS_TOKEN'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># create an instance of the API class</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">api_instance </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">AcademicWeeksApi</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ApiClient</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin\">id</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'id_example'</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># str | A resource identifier that uniquely identifies the resource.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">if_match </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'if_match_example'</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># str | The ETag header value used to prevent the DELETE from removing a resource modified by another consumer. (optional)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">try</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Deletes an existing resource using the resource identifier.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    api_instance</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">delete_academic_week_by_id</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token builtin\">id</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> if_match</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">if_match</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">except</span><span class=\"token plain\"> ApiException </span><span class=\"token keyword\" style=\"color:#00009f\">as</span><span class=\"token plain\"> e</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"Exception when calling AcademicWeeksApi-&gt;delete_academic_week_by_id: %s\\n\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">%</span><span class=\"token plain\"> e</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"converting-to-poetry\">Converting to Poetry<a href=\"https://blog.safnet.com/2023/12/08/edfi-client-from-swagger#converting-to-poetry\" class=\"hash-link\" aria-label=\"Direct link to Converting to Poetry\" title=\"Direct link to Converting to Poetry\" translate=\"no\">​</a></h2>\n<p>I like to use <a href=\"https://python-poetry.org/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Poetry</a> for managing Python packages\ninstead of Pip, Conda, Tox, etc. Converting the <code>requirements.txt</code> file for use\nin Poetry is quite easy with this PowerShell command (<a href=\"https://stackoverflow.com/a/73691994/30384\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">hat\ntip</a>):</p>\n<div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Push-Location edfi-client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">poetry init </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--name</span><span class=\"token plain\"> edfi-client </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-l</span><span class=\"token plain\"> Apache-2.0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">@</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">cat requirements.txt</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> %</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token operator\" style=\"color:#393A34\">&amp;</span><span class=\"token plain\">poetry </span><span class=\"token function\" style=\"color:#d73a49\">add</span><span class=\"token plain\"> </span><span class=\"token variable\" style=\"color:#36acaa\">$_</span><span class=\"token plain\">.replace</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">' '</span><span class=\"token plain\">,</span><span class=\"token string\" style=\"color:#e3116c\">''</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Pop-Location</span><br></div></code></pre></div></div>\n<p>(The default <code>requirements.txt</code> file has some unexpected spaces; the <code>replace</code>\ncommand above strips those out).</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"missing-token-generation\">Missing Token Generation<a href=\"https://blog.safnet.com/2023/12/08/edfi-client-from-swagger#missing-token-generation\" class=\"hash-link\" aria-label=\"Direct link to Missing Token Generation\" title=\"Direct link to Missing Token Generation\" translate=\"no\">​</a></h2>\n<p>Note the line above with <code>access_token = 'YOUR_ACCESS_TOKEN'</code>. Swagger Codegen\nrequires you to bring your own token generation routine. We can build one using\nportions of the client library itself. The ODS/API supports the OAuth 2.0 client\ncredentials flow, which generates an bearer-style access token. A basic HTTP\nrequest for authentication looks like this:</p>\n<div class=\"language-http codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-http codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">POST /v7.1/api/oauth/token HTTP/1.1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Host: api.ed-fi.org</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Content-Type: application/x-www-form-urlencoded</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Accept: application/json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">grant_type=client_credentials&amp;client_id=YOUR CLIENT ID&amp;client_secret=YOUR CLIENT SECRET</span><br></div></code></pre></div></div>\n<p>There are some variations in how these parameters can be passed, but this may be\nthe most common / universal format, and this is what we will implement here.</p>\n<p>Generated tokens are only good for so long; they expire. When a token expires,\nit would be nice if we could recognize that and automatically call for a new\none, instead of encountering an error. The generated code does not support token\nrefresh, and does not have an obvious hook for how to do so. For a very clean\ndeveloper experience, the authentication and refresh mechanisms would be built\ninto the <code>ApiClient</code> class created by Swagger Codegen. But be warned: if you\nrerun the generator, it will overwrite your customizations.</p>\n<p>Someone with deeper Python expertise can probably come up with multiple ways to\napproach the refresh problem. This sample code handles token refresh very\ncrudely, requiring the <em>user</em> of the code to detect the problem and try to\nre-authenticate. Perhaps a <a href=\"https://realpython.com/python-with-statement/#creating-function-based-context-managers\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Context\nManager</a>\nimplementation would help here.</p>\n<p>Copy the following source code and paste it into a file called\n<code>token_manager.py</code> inside the <code>edfi-client/swagger_client</code> directory.</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> datetime </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> datetime</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> timedelta</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">rest </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> ApiException</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">configuration </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> Configuration</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">api_client </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> ApiClient</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">class</span><span class=\"token plain\"> </span><span class=\"token class-name\">TokenManager</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token builtin\">object</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">\"\"\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    Creates a new instance of the TokenManager.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"display:inline-block;color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    Parameters</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    ---------</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    token_url: str</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">        The token URL for the Ed-Fi API.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    configuration: Configuration</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">        A list dictionary of configuration options for the RESTClientObject.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">        Must study the RESTClientObject constructor carefully to understand the</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">        available options.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    \"\"\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">__init__</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> token_url</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> Configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">None</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">assert</span><span class=\"token plain\"> token_url </span><span class=\"token keyword\" style=\"color:#00009f\">is</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">not</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">None</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">assert</span><span class=\"token plain\"> token_url</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">strip</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">!=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">token_url</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> token_url</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> Configuration </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> configuration</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">client</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> ApiClient </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> ApiClient</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">expires_at </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> datetime</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">now</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">_authenticate</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">None</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        post_params </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"grant_type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"client_credentials\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"client_id\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">username</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"client_secret\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">password</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        headers </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"application/x-www-form-urlencoded\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        token_response </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">request</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"POST\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">token_url</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> headers</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">headers</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> post_params</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">post_params</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        data </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">loads</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">token_response</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">data</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">expires_at </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> datetime</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">now</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">+</span><span class=\"token plain\"> timedelta</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">seconds</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">data</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"expires_in\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">access_token </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> data</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"access_token\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">\"\"\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    Sends a token request and creates an ApiClient containing the returned access token.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"display:inline-block;color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    Returns</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    ------</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    ApiClient</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">        an ApiClient instance that has already been authenticated.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    \"\"\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">create_authenticated_client</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> ApiClient</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">_authenticate</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">\"\"\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    Re-authenticates if the token has expired.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">    \"\"\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">refresh</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">None</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> datetime</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">now</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">expires_at</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">_authenticate</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">else</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">raise</span><span class=\"token plain\"> ApiException</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"Token is not expired; authentication failure may be a configuration problem.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"demonstration\">Demonstration<a href=\"https://blog.safnet.com/2023/12/08/edfi-client-from-swagger#demonstration\" class=\"hash-link\" aria-label=\"Direct link to Demonstration\" title=\"Direct link to Demonstration\" translate=\"no\">​</a></h2>\n<p>The following snippet demonstrates use the token manager with a simple token\nrefresh mechanism. Note that this tries to delete an object that does not exist,\ntherefore you should expect it to raise an exception with a 404 NOT FOUND\nmessage.</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">configuration </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> Configuration</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">token_manager </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> TokenManager</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">api </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> AcademicWeeksApi</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> swagger_client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">rest </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> ApiException</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">BASE_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"https://api.ed-fi.org/v7.1/api\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">config </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> Configuration</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">config</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">username </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"RvcohKz9zHI4\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">config</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">password </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"E1iEFusaNf81xzCxwHfbolkC\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">config</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">host </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">BASE_URL</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">/data/v3/\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">config</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">debug </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">tm </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> TokenManager</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">BASE_URL</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">/oauth/token\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> config</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">api_client </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> tm</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">create_authenticated_client</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">api_instance </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> AcademicWeeksApi</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">api_client</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">try</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    api_instance</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">delete_academic_week_by_id</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"bogus\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">except</span><span class=\"token plain\"> ApiException </span><span class=\"token keyword\" style=\"color:#00009f\">as</span><span class=\"token plain\"> ae</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> ae</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">status </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">401</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        tm</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">refresh</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        api_instance</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">delete_academic_week_by_id</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"bogus\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">else</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">raise</span><br></div></code></pre></div></div>",
            "url": "https://blog.safnet.com/2023/12/08/edfi-client-from-swagger",
            "title": "Ed-Fi Client Generation in Python with Swagger CLI",
            "summary": "Motivation",
            "date_modified": "2023-12-08T00:00:00.000Z",
            "tags": [
                "ed-fi",
                "programming"
            ]
        },
        {
            "id": "https://blog.safnet.com/2023/08/10/responsible-data-use",
            "content_html": "<p>A strange thing about my job is that, although we're all about supporting K-12\neducation data interoperability, we don't actually work with any K-12 data. We\nbuild software, and others use it to collect data from disparate data sources\ninto a single, unified, and standardized data set. But that does't stop me from\nthinking about how data <em>should</em> be used.</p>\n<p>On a flight out to the #STATSDC2023 conference hosted by the National Center for\nEducational Statistics (my first time at this event), I finally wrote down my\npersonal principles for ethical / responsible use of data and AI. Many have\nwritten about responsible use of data; there is nothing ground breaking here.\nYet it feels meaningful, even if only for myself, to acknowledge \"out loud\" the\nvalues and principles that I wish to hold myself accountable for whenever I <em>do</em>\nuse data, encourage others to make use of data, allow my own data to be used,\netc.</p>\n<p><strong>☝Note</strong> First draft, 2023-08-10</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"virtues-and-values\">Virtues and Values<a href=\"https://blog.safnet.com/2023/08/10/responsible-data-use#virtues-and-values\" class=\"hash-link\" aria-label=\"Direct link to Virtues and Values\" title=\"Direct link to Virtues and Values\" translate=\"no\">​</a></h2>\n<p>As a person of faith, I tend to think of virtues before I think of values.\nVirtues are the spiritual foundations, and values are culturally-relevant\nextensions of those virtues. While there are countless variations on virtues and\nvalues, those below can serve as guiding lights for the principles that follow.</p>\n<blockquote>\n<p>\"Truthfulness is the foundation of all human virtues.\" `Abdu'l-Bahá</p>\n</blockquote>\n<table><thead><tr><th>Virtue</th><th>Value Correlate(s)</th></tr></thead><tbody><tr><td>truthfulness</td><td>transparency, honesty, truth-seeking</td></tr><tr><td>justice</td><td>equity, independence, consensus</td></tr><tr><td>nobility</td><td>human dignity, recognition of aspirations, privacy</td></tr></tbody></table>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"guiding-principles\">Guiding Principles<a href=\"https://blog.safnet.com/2023/08/10/responsible-data-use#guiding-principles\" class=\"hash-link\" aria-label=\"Direct link to Guiding Principles\" title=\"Direct link to Guiding Principles\" translate=\"no\">​</a></h2>\n<p>Admittedly this is a hodge-podge list of principles, with no attempt to be\nsystematic at this time.</p>\n<p><strong>Data Source Transparency</strong>: Retain as much information about data sources as\npossible. Vet them for integrity, completeness, appropriateness, and manner of\ncollection. Only use ethically sourced information. Insure personal data usage\ncorresponds to subject's intent. Give credit where credit is due.</p>\n<p>Example: avoid (shady) data aggregator / brokers.</p>\n<p><strong>Algorithmic Transparency</strong>: Prefer open algorithms. Look for, or conduct,\naudits to assess accuracy, applicability to the desired use case(s), and\npotential for biases. Look for explainability and ensure there is a reasonable\nhuman appeals process. Does a <em>great</em> deep learning model really outweigh having\na <em>good</em> and <em>explainable</em> decision tree or a regression model?</p>\n<p>Example: Do AI and cheat detection systems have low false positives? Are there\nclear appeals mechanisms? Have they been cross-validated against different\nsubgroups to ensure they do not introduce biases? Can you explain why a person's\napplication is rejected or a promotion has been offered to one and not another\nwhen AI is involved in the process?</p>\n<p>Side note: Does use of generative AI really matter in the given context?</p>\n<p><strong>Equity</strong>: Do not be content with doing no harm, rather seek to uplift\nmarginalized perspectives and peoples. Consider institutional biases that may\ninfluence raw data (over and under representation). Consider environmental\nfactors that may impact some populations, independent of or in concert with\nother demographic factors. Be mindful of accidental proxies.</p>\n<p>Examples: Account for ethnic / racial disparities in policing, gender disparities in\nsurveying, and geographic impacts such as longer bus rides to schools (\"all the\nXYZ kids have poor attendance\" might be due to late buses, or working parents,\nnothing to do with ethnicity).</p>\n<p>Don't feel virtuous by avoiding use of race while at the same time analyzing\nbased on income, if income and race are highly correlated in a particular area.</p>\n<p><strong>Statistical Honesty</strong>: Use appropriate sample sizes and/or statistical tests\nto confirm assumptions. Cross validate results. Use large iterations when\nbootstrapping and repeat to insure stability of results.</p>\n<p><strong>Privacy</strong>: Practice appropriate anonymization and apply \"need to know\" (least\nprivilege) restrictions. Watch out for small sample sizes that can\nunintentionally reveal identity. Use the least personal data available and\nrelevant for the analysis.</p>\n<p>Examples: Imagine an analysis that predicts a certain outcome for a target\ndemographic, and the dataset only contains a few people from that demographic.\nApply a filter by zip code. How hard is it to guess the person / household?</p>\n<p>Can you use census block instead of street address? Zip code instead of census\nblock? Age range instead of birth year?</p>\n<p><strong>Consensus and Review</strong>: Seek review of data, algorithms, and outcomes from\nother experts and/or affected parties. Work to achieve consensus on techniques\nand communication. Start early to avoid the trap of stubbornly clinging to a\nmisdirected idea.</p>\n<p><strong>Destiny</strong>: as a matter of justice, of nobility, and of truthfulness: be on\nguard for predictions becoming destiny.</p>\n<p>Example: Whether policing on the street or teachers disciplining students, a\nprediction that one group will have more criminal / behavioral incidents easily\nleads to paying more attention to that group, ignoring other groups, and thus\ndetecting (or \"inventing\") more ill behavior in that group - thus reinforcing\nthe original prediction.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"sources-of-inspiration\">Sources of Inspiration<a href=\"https://blog.safnet.com/2023/08/10/responsible-data-use#sources-of-inspiration\" class=\"hash-link\" aria-label=\"Direct link to Sources of Inspiration\" title=\"Direct link to Sources of Inspiration\" translate=\"no\">​</a></h2>\n<p>Speaking of giving credit where credit is due, here are some sources that have\nhelped shape my thinking on this topic:</p>\n<ul>\n<li class=\"\"><a href=\"https://www.codedbias.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Coded Bias</a> (film) and the <a href=\"https://www.ajl.org/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Algorithmic Justice\nLeague</a></li>\n<li class=\"\"><a href=\"https://www.ruhabenjamin.com/race-after-technology\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Race After Technology</a>\n(book), by Ruha Benjamin.</li>\n<li class=\"\"><a href=\"https://newjimcrow.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">The New Jim Crow</a> (book), by Michelle Alexander.</li>\n<li class=\"\"><a href=\"https://learning.oreilly.com/library/view/97-things-about/9781492072652/q\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">97 Things About Ethics Everyone in Data Science Should\nKnow</a>\n(book), edited by Bill Franks.</li>\n</ul>\n<p>And articles, asides, and conversations that are too numerous to remember or\ncite.</p>\n<p>Also see references in:</p>\n<ul>\n<li class=\"\"><a class=\"\" href=\"https://blog.safnet.com/2020/12/29/constructing-just-algorithms\">Constructing Just Algorithms (Dec. 29, 2020)</a></li>\n<li class=\"\"><a class=\"\" href=\"https://blog.safnet.com/2020/12/06/in-pursuit-algorithmic-equity\">In Pursuit of Data and Algorithmic Equity (Dec. 06, 2020)</a></li>\n</ul>",
            "url": "https://blog.safnet.com/2023/08/10/responsible-data-use",
            "title": "Thoughts on Responsible Data Use",
            "summary": "A strange thing about my job is that, although we're all about supporting K-12",
            "date_modified": "2023-08-10T00:00:00.000Z",
            "tags": [
                "discourse",
                "justice",
                "inclusion",
                "technology",
                "data-and-analytics"
            ]
        },
        {
            "id": "https://blog.safnet.com/2023/05/08/meadowlark-cascading-updates",
            "content_html": "<p>Design document describing an event-driven approach for offline cascading\nupdates in the Ed-Fi Meadowlark project's document storage system: <a href=\"https://github.com/Ed-Fi-Exchange-OSS/Meadowlark/blob/main/docs/design/offline-cascading-updates/README.md\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Design for\nOffline Cascading\nUpdates</a></p>",
            "url": "https://blog.safnet.com/2023/05/08/meadowlark-cascading-updates",
            "title": "Project Meadowlark: Design for Offline Cascading Updates",
            "summary": "Design document describing an event-driven approach for offline cascading",
            "date_modified": "2023-08-07T00:00:00.000Z",
            "tags": [
                "ed-fi",
                "programming",
                "architecture"
            ]
        }
    ]
}