<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ernesto Herrera Salinas</title>
    <description>The latest articles on Forem by Ernesto Herrera Salinas (@ernestohs).</description>
    <link>https://forem.com/ernestohs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F784815%2Fd5daef24-3e99-433f-819f-5b37dfac6558.png</url>
      <title>Forem: Ernesto Herrera Salinas</title>
      <link>https://forem.com/ernestohs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ernestohs"/>
    <language>en</language>
    <item>
      <title>Empower Your Workflow with Aliases: Save Time by Being Lazy</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Sun, 27 Jul 2025 01:43:20 +0000</pubDate>
      <link>https://forem.com/ernestohs/empower-your-workflow-with-aliases-save-time-by-being-lazy-4m6o</link>
      <guid>https://forem.com/ernestohs/empower-your-workflow-with-aliases-save-time-by-being-lazy-4m6o</guid>
      <description>&lt;p&gt;In software development, every keystroke counts. That’s where &lt;strong&gt;aliases&lt;/strong&gt; come in—tiny shortcuts that let you run long or repetitive commands with minimal typing. The Git and shell aliases shared in your Gist are a powerful example of how developers can streamline their daily work.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://gist.github.com/ernestohs/e3fc2d8c936f173128cca159391ca324?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Gist&lt;/a&gt; entitled &lt;strong&gt;“BASH/ZSH ALIASES FOR THE LAZY DEVELOPER”&lt;/strong&gt; provides a curated set of aliases tailored to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Git workflows&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Docker Compose&lt;/li&gt;
&lt;li&gt;NPM commands&lt;/li&gt;
&lt;li&gt;General system productivity tools
(&lt;a href="https://news.ycombinator.com/item?id=41284775&amp;amp;utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt;, &lt;a href="https://dev.to/oyetoket/how-to-create-productive-bash-command-aliases-practical-guide-2d23?utm_source=chatgpt.com"&gt;DEV Community&lt;/a&gt;, &lt;a href="https://gist.github.com/ernestohs/e3fc2d8c936f173128cca159391ca324?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Gist&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Why Use Aliases?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speed &amp;amp; Efficiency&lt;/strong&gt;&lt;br&gt;
Commands like &lt;code&gt;git status -s&lt;/code&gt; become simply &lt;code&gt;s&lt;/code&gt;, or &lt;code&gt;git push origin $(git rev‑parse --abbrev‑ref HEAD)&lt;/code&gt; becomes &lt;code&gt;push&lt;/code&gt;. This cuts down typing and mental context switching.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;br&gt;
Reuse familiar shortcuts across machines by storing them in your shell startup file (&lt;code&gt;.bashrc&lt;/code&gt;, &lt;code&gt;.zshrc&lt;/code&gt;, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error Reduction&lt;/strong&gt;&lt;br&gt;
Avoid typos in complex commands by using shorter, predefined aliases.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📦 Highlighted Alias Categories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔧 Git Workflow Aliases
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alias&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;push&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Push current branch to origin: &lt;code&gt;git push origin $(git rev‑parse --abbrev-ref HEAD)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pull&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull current branch and submodules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git status -s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git add . &amp;amp;&amp;amp; git status -s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git log --oneline --all --graph --decorate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git fetch &amp;amp;&amp;amp; git checkout &amp;lt;branch&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;undo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git checkout -- &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git reset --hard HEAD~1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dry‑run &lt;code&gt;git clean&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;branch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git checkout -b&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;friday&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;End-of-week branch setup + push with date-based naming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;p&gt;These handle daily Git tasks faster, and even some creative eco‑friendly shortcuts like &lt;code&gt;friday&lt;/code&gt; combine multiple steps into one single alias. (&lt;a href="https://gist.github.com/ernestohs/e3fc2d8c936f173128cca159391ca324?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Gist&lt;/a&gt;, &lt;a href="https://dev.to/oyetoket/how-to-create-productive-bash-command-aliases-practical-guide-2d23?utm_source=chatgpt.com"&gt;DEV Community&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  🐳 Docker / Compose Aliases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dc&lt;/code&gt;, &lt;code&gt;dcu&lt;/code&gt;, &lt;code&gt;dcb&lt;/code&gt;, &lt;code&gt;dcd&lt;/code&gt;, etc.
Simplify running and managing Docker Compose commands, often with &lt;code&gt;sudo&lt;/code&gt;. A big win for developers frequently working with containers. (&lt;a href="https://gist.github.com/ernestohs/e3fc2d8c936f173128cca159391ca324?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Gist&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📦 NPM Aliases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nr&lt;/code&gt;, &lt;code&gt;nrs&lt;/code&gt;, &lt;code&gt;nrb&lt;/code&gt;, &lt;code&gt;npi&lt;/code&gt;, &lt;code&gt;nru&lt;/code&gt;, &lt;code&gt;npo&lt;/code&gt;, &lt;code&gt;npa&lt;/code&gt;, and more
Quickly run npm scripts like &lt;code&gt;npm run&lt;/code&gt; or common tasks like build, lint, test, audit—all with just two or three characters. (&lt;a href="https://gist.github.com/ernestohs/e3fc2d8c936f173128cca159391ca324?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Gist&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  💻 System Aliases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ls='ls -lah --color=auto'&lt;/code&gt;
A colorized, human-readable version of &lt;code&gt;ls&lt;/code&gt; that’s easier on the eyes. (&lt;a href="https://gist.github.com/ernestohs/e3fc2d8c936f173128cca159391ca324?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Gist&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 Productivity Best Practices
&lt;/h2&gt;

&lt;p&gt;Based on articles and expert guidance on command aliasing, here are some best practices to multiply your aliases effectively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alias your most-used commands&lt;/strong&gt;, not everything. Keep aliases memorable &amp;amp; limited. (&lt;a href="https://dev.to/oyetoket/how-to-create-productive-bash-command-aliases-practical-guide-2d23?utm_source=chatgpt.com"&gt;DEV Community&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid single‑letter overrides&lt;/strong&gt; of essential shell built‑ins like &lt;code&gt;r&lt;/code&gt; or &lt;code&gt;x&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document your aliases&lt;/strong&gt; with comments—both for your future self and collaborators. (&lt;a href="https://dev.to/oyetoket/how-to-create-productive-bash-command-aliases-practical-guide-2d23?utm_source=chatgpt.com"&gt;DEV Community&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Git aliases via &lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/strong&gt; when possible for consistency across shells and machines. They also integrate with Git’s auto-completion and autocorrect features. (&lt;a href="https://gist.github.com/mwhite/6887990?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Gist&lt;/a&gt;, &lt;a href="https://opensource.com/article/20/11/git-aliases?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Opensource.com&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ✅ Sample Alias Setup (excerpt from &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt;)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Git shortcuts&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"git status -s"&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git add . &amp;amp;&amp;amp; git status -s'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git log --oneline --all --graph --decorate'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git checkout -b'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;undo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git checkout --'&lt;/span&gt;

&lt;span class="c"&gt;# Push current branch&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git push origin $(git rev-parse --abbrev-ref HEAD)'&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;pull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'git pull --recurse-submodules origin $(git rev-parse --abbrev-ref HEAD)'&lt;/span&gt;

&lt;span class="c"&gt;# YAML end-of-week workflow&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;friday&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'pull &amp;amp;&amp;amp; git checkout -b "friday-$(date +%Y-%m-%d)" &amp;amp;&amp;amp; git commit -a -m "[WIP] Friday $(date +%Y-%m-%d)" &amp;amp;&amp;amp; push'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧰 Tips to Customize Further
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use shell history to identify frequently typed commands that deserve an alias.&lt;/li&gt;
&lt;li&gt;Keep alias names intuitive—e.g. &lt;code&gt;d&lt;/code&gt; for &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;nr&lt;/code&gt; for &lt;code&gt;npm run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Combine commands: &lt;code&gt;a&lt;/code&gt; adds + status, &lt;code&gt;l&lt;/code&gt; logs + graph formatting.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔚 Wrap-Up
&lt;/h2&gt;

&lt;p&gt;Aliases may look small, but they deliver huge productivity gains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer keystrokes&lt;/strong&gt; means faster workflows and less wrist fatigue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified mental load&lt;/strong&gt;—no need to remember verbose flags or commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable, error‑free execution&lt;/strong&gt; of routine tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use your Gist as a template—customize it to your projects and style. Whether you're managing Git, Docker, NPM, or system commands, well-crafted aliases let you work smarter, not harder.&lt;/p&gt;

&lt;p&gt;Let me know if you'd like help tailoring these for your team or expanding them with more advanced patterns like chained commands or conditional logic!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Data-Driven Testing in Jest (Parameterized Tests)</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Wed, 02 Apr 2025 11:37:48 +0000</pubDate>
      <link>https://forem.com/ernestohs/data-driven-testing-in-jest-parameterized-tests-9f5</link>
      <guid>https://forem.com/ernestohs/data-driven-testing-in-jest-parameterized-tests-9f5</guid>
      <description>&lt;h2&gt;
  
  
  What is Data-Driven Testing?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Data-driven testing&lt;/strong&gt; (also known as &lt;em&gt;parameterized&lt;/em&gt; or &lt;em&gt;table-driven&lt;/em&gt; testing) is a technique where the same test logic is run against multiple sets of input data. Instead of writing separate test functions for each input scenario, you externalize the test cases (inputs and expected outputs) and feed them into a single, generic test. In other words, the test behavior is &lt;em&gt;driven&lt;/em&gt; by a collection of data values. This approach allows one test script to execute with different inputs by separating the data from the test code (&lt;a href="https://www.functionize.com/automated-testing/data-driven-testing#:~:text=Data,generating%20test%20results%20more%20efficient" rel="noopener noreferrer"&gt;What is Data-Driven Testing? Enhancing Accuracy Through Data&lt;/a&gt;). The result is more thorough coverage with less repetitive code: you avoid duplicating test code for each case and make it easy to add or modify test scenarios as needed (&lt;a href="https://blog.codeleak.pl/2021/12/parameterized-tests-with-jest.html#:~:text=Parameterized%20tests%20are%20used%20to,easier%20to%20read%20and%20maintain" rel="noopener noreferrer"&gt;Parameterized tests in JavaScript with Jest&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Design a test once, then run it for a variety of data values. The data sets can come from an in-memory array, an external file (like JSON or CSV), or even a database query (&lt;a href="https://blog.codeleak.pl/2021/12/parameterized-tests-with-jest.html#:~:text=Parameterized%20tests%20are%20used%20to,easier%20to%20read%20and%20maintain" rel="noopener noreferrer"&gt;Parameterized tests in JavaScript with Jest&lt;/a&gt;). For each data set, the test will execute and verify that the code under test produces the expected outcome for that input. This method is particularly useful for pure functions or algorithms where you want to validate numerous input-output combinations (including edge cases) without writing dozens of nearly identical test functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Data-Driven Testing
&lt;/h2&gt;

&lt;p&gt;Adopting a data-driven approach in your test suite provides several important benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoids Duplication and Reduces Boilerplate:&lt;/strong&gt; You write the test logic once and reuse it for all data variations. This prevents copy-pasting similar test code multiple times (&lt;a href="https://blog.codeleak.pl/2021/12/parameterized-tests-with-jest.html#:~:text=Parameterized%20tests%20are%20used%20to,easier%20to%20read%20and%20maintain" rel="noopener noreferrer"&gt;Parameterized tests in JavaScript with Jest&lt;/a&gt;) (&lt;a href="https://dev.to/bgord/simplify-repetitive-jest-test-cases-with-test-each-310m#:~:text=Benefits%3A"&gt;Simplify repetitive Jest test cases with test.each - DEV Community&lt;/a&gt;). Fewer repeated test functions mean a cleaner, more maintainable test file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easier Maintainability:&lt;/strong&gt; Adding or updating test cases is as simple as modifying the data source. You can insert new input/expected pairs into the data set without touching the test logic at all (&lt;a href="https://dev.to/bgord/simplify-repetitive-jest-test-cases-with-test-each-310m#:~:text=Benefits%3A"&gt;Simplify repetitive Jest test cases with test.each - DEV Community&lt;/a&gt;). This isolation of data makes it less likely to introduce errors when extending your tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improved Test Coverage:&lt;/strong&gt; By feeding many different inputs into the same test, you can cover more scenarios with minimal effort. Simply changing or expanding the data set increases coverage without additional test code (&lt;a href="https://www.functionize.com/automated-testing/data-driven-testing#:~:text=One%20of%20its%20main%20advantages,thorough%20testing%20with%20less%20effort" rel="noopener noreferrer"&gt;What is Data-Driven Testing? Enhancing Accuracy Through Data&lt;/a&gt;). This encourages testing normal cases, edge cases, and invalid inputs alike.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability for Large Data Sets:&lt;/strong&gt; Data-driven tests handle large collections of test cases gracefully. Whether you have 5 cases or 500, the structure of the test remains the same. This scales well for situations like mathematical functions or algorithms that need verification against lots of values.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Readability of Test Intent:&lt;/strong&gt; When structured well, data-driven tests make it clear &lt;em&gt;what&lt;/em&gt; is being tested with each input. By using descriptive names or placeholders in test titles, each data-driven test's purpose is evident (e.g. “&lt;code&gt;153 should be an Armstrong number&lt;/code&gt;” for a particular input). The list of test cases itself acts as documentation of expected behavior for various inputs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistency and Lower Risk of Human Error:&lt;/strong&gt; Because the same code path is used for all test cases, you ensure consistent execution. There’s less risk that one of many copy-pasted tests has a typo or mistake – one logic covers all cases. This also makes it easier to update the assertion or test steps in one place if requirements change.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, data-driven testing separates test data from test scripts, which enhances reusability and maintainability of tests (&lt;a href="https://bugbug.io/blog/software-testing/data-driven-testing/#:~:text=Guide%20to%20Data,reusability%2C%20maintainability%2C%20and%20test" rel="noopener noreferrer"&gt;Guide to Data-Driven Testing - BugBug.io&lt;/a&gt;). It leads to more thorough testing with less effort by letting simple data variations exercise the code in depth (&lt;a href="https://www.functionize.com/automated-testing/data-driven-testing#:~:text=One%20of%20its%20main%20advantages,thorough%20testing%20with%20less%20effort" rel="noopener noreferrer"&gt;What is Data-Driven Testing? Enhancing Accuracy Through Data&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Data-Driven Tests in Jest
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Jest&lt;/strong&gt; – a popular JavaScript testing framework – provides built-in support for data-driven (parameterized) testing. Rather than manually writing loops, you can use Jest’s utilities to supply a table of inputs to a single test definition. This is typically done with the &lt;code&gt;test.each&lt;/code&gt; or &lt;code&gt;it.each&lt;/code&gt; methods (in Jest, &lt;code&gt;it&lt;/code&gt; is just an alias for &lt;code&gt;test&lt;/code&gt;). The official Jest documentation recommends using &lt;code&gt;test.each&lt;/code&gt; when you find yourself duplicating the same test with different data (&lt;a href="https://jestjs.io/docs/en/api#testeachtable-name-fn-timeout#:~:text=Use%20,once%20and%20pass%20data%20in" rel="noopener noreferrer"&gt;Globals · Jest&lt;/a&gt;). By using this feature, you “write the test once and pass data in” for each case (&lt;a href="https://jestjs.io/docs/en/api#testeachtable-name-fn-timeout#:~:text=Use%20,once%20and%20pass%20data%20in" rel="noopener noreferrer"&gt;Globals · Jest&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Jest offers two primary ways to define data-driven tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Using an Array of Cases:&lt;/strong&gt; You can call &lt;code&gt;test.each(table)(name, fn)&lt;/code&gt; where &lt;strong&gt;table&lt;/strong&gt; is an array of arrays (or an array of objects) representing the test cases (&lt;a href="https://jestjs.io/docs/en/api#testeachtable-name-fn-timeout#:~:text=" rel="noopener noreferrer"&gt;Globals · Jest&lt;/a&gt;) (&lt;a href="https://jestjs.io/docs/en/api#testeachtable-name-fn-timeout#:~:text=test.each%28%5B%20,2%2C%20b%3A%201%2C%20expected%3A%203" rel="noopener noreferrer"&gt;Globals · Jest&lt;/a&gt;). Each inner array contains the arguments for one test invocation, and each element in the array of objects can be destructured in the test function. For example:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;       &lt;span class="c1"&gt;// a, b, expected&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;    &lt;span class="c1"&gt;// a, b, expected&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;       &lt;span class="c1"&gt;// a, b, expected&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;given %p and %p, returns %p&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;In this example, three sets of inputs (&lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;) with their expected output are provided. Jest will generate three sub-tests from this one definition, substituting each set of values. The &lt;code&gt;%p&lt;/code&gt; placeholders in the test title are tokens that Jest replaces with the actual parameter values for easier identification of each case (&lt;a href="https://dev.to/bgord/simplify-repetitive-jest-test-cases-with-test-each-310m#:~:text=describe%28,"&gt;Simplify repetitive Jest test cases with test.each - DEV Community&lt;/a&gt;). The test output would list “&lt;strong&gt;given 2 and 2, returns 4&lt;/strong&gt;” etc., making it clear which case passed or failed.&lt;/p&gt;

&lt;p&gt;Jest also supports using an array of &lt;strong&gt;objects&lt;/strong&gt; instead of arrays. In that case, each object’s properties can be used in the test function via destructuring. For example, you might have &lt;code&gt;[{a: 2, b: 2, expected: 4}, {...}, ...]&lt;/code&gt; and use a test title placeholder like &lt;code&gt;%o&lt;/code&gt; to print the object, or individually interpolate values in the title. This can sometimes improve clarity if you label the data fields. Under the hood, each element (array or object) in the cases array will result in a separate test execution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Using a Tagged Template (Table Syntax):&lt;/strong&gt; Jest allows a more readable table format using template literals. This is invoked as &lt;code&gt;test.each\`table\`(name, fn)&lt;/code&gt;. The first row of the template defines column names, and subsequent rows define values. For example:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="s2"&gt;`
  a    | b    | expected
  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add(${'$a'}, ${'$b'}) = ${'$expected'}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Here we use a table layout for readability. Jest will convert each row into a test case, and we can reference the named variables (&lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;expected&lt;/code&gt;) in both the test name and the test function. This format can be very clear, though it’s essentially syntactic sugar on top of &lt;code&gt;test.each&lt;/code&gt; functionality (&lt;a href="https://jestjs.io/docs/en/api#testeachtable-name-fn-timeout#:~:text=" rel="noopener noreferrer"&gt;Globals · Jest&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches accomplish the same goal: parameterizing a test. Each row of data creates a new test with the full lifecycle (setup, execution, teardown) just like any normal test case (&lt;a href="https://blog.codeleak.pl/2021/12/parameterized-tests-with-jest.html#:~:text=Please%20note%20that%20in%20a,4%20sets%20of%20data%20values" rel="noopener noreferrer"&gt;Parameterized tests in JavaScript with Jest&lt;/a&gt;). In the test report, each case is listed separately, which helps pinpoint which input failed if something goes wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative: Manual iteration.&lt;/strong&gt; In addition to Jest's built-in &lt;code&gt;test.each&lt;/code&gt;, one can also simply loop through data within a test suite. For example, you might load an array of test cases and call &lt;code&gt;test()&lt;/code&gt; or &lt;code&gt;expect()&lt;/code&gt; in a &lt;code&gt;.forEach&lt;/code&gt; loop. This achieves a similar effect – dynamically generating tests – and is sometimes used if custom processing of data is needed. However, using Jest’s native parameterization is generally cleaner and provides better reporting. With &lt;code&gt;test.each&lt;/code&gt;, you avoid potential pitfalls of asynchronous loops and ensure each case is treated as a separate test by Jest automatically. As a best practice, prefer &lt;code&gt;test.each&lt;/code&gt; or &lt;code&gt;it.each&lt;/code&gt; for simplicity, unless you have a specific reason to manually generate tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using External Data (JSON) for Test Cases in Jest
&lt;/h2&gt;

&lt;p&gt;One powerful pattern in data-driven testing is to keep the test cases in external files (like JSON) for even cleaner separation of data from code. Jest, running on Node.js, can directly import JSON files using &lt;code&gt;require()&lt;/code&gt; or ES &lt;code&gt;import&lt;/code&gt;. This means you can store an array of inputs and expected outputs in a &lt;code&gt;.json&lt;/code&gt; file, load that file in your test, and feed it into &lt;code&gt;test.each&lt;/code&gt; or a loop.&lt;/p&gt;

&lt;p&gt;Using an external JSON brings a few advantages: if you have a very large number of test cases or want non-developers to be able to review/edit test data, a separate file is convenient. It also declutters the test file, making the logic more readable. The Jest team and testing experts encourage this approach when dealing with extensive datasets (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=Advanced%20Use%20Cases%20%E2%80%93%20Usage,Test%20data%20from%20external%20sources" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of loading JSON test data:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose we have a JSON file &lt;code&gt;testCases.json&lt;/code&gt; containing an array of test inputs and outputs for a function (for instance, a set of number pairs to add). It might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"input1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"input2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"input1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"input2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"input1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"input2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Jest test suite, you can load and use this data as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./testCases.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Addition function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adds $input1 and $input2, expecting $expected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;input1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this snippet, &lt;code&gt;it.each(testData)&lt;/code&gt; will iterate over each object in the JSON array and run the test. We use &lt;code&gt;$input1&lt;/code&gt;, &lt;code&gt;$input2&lt;/code&gt;, and &lt;code&gt;$expected&lt;/code&gt; in the test name string to interpolate those values for each case’s name. The test body destructures the object to get &lt;code&gt;input1&lt;/code&gt;, &lt;code&gt;input2&lt;/code&gt;, and &lt;code&gt;expected&lt;/code&gt;, performs the addition, and asserts the result. This pattern of loading external data is directly supported by Jest and keeps tests simple (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=const%20testdata%20%3D%20require%28%27" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;). The output will list a separate test for each set of inputs, making it clear which data set passed or failed.&lt;/p&gt;

&lt;p&gt;Jest even allows other formats (like CSV or other modules) to be used as data sources, as long as you can import or read them in Node. This aligns with the idea that the data could come from any source (CSV file, database, etc.), though JSON is most straightforward in JavaScript. When using large data sets, storing them externally is recommended because it keeps the test file shorter and easier to read, and avoids cluttering your code with lengthy inline arrays (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=Advanced%20Use%20Cases%20%E2%80%93%20Usage,Test%20data%20from%20external%20sources" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintainability, Scalability, and Readability Improvements
&lt;/h2&gt;

&lt;p&gt;Data-driven tests significantly improve &lt;strong&gt;maintainability&lt;/strong&gt; of the test code. Since all scenarios use a single implementation, there is one place to update if the function under test changes behavior. For example, if the formula or logic in a function changes, you might only need to adjust the expected values in the data file or adjust the assertion in one spot, rather than editing many individual test functions. Adding new test scenarios is as easy as adding a new entry to the cases array or JSON – no new &lt;code&gt;test()&lt;/code&gt; boilerplate needed (&lt;a href="https://dev.to/bgord/simplify-repetitive-jest-test-cases-with-test-each-310m#:~:text=Benefits%3A"&gt;Simplify repetitive Jest test cases with test.each - DEV Community&lt;/a&gt;). This modularity means the test suite can grow in coverage without a corresponding explosion in code size.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;scalability&lt;/strong&gt;, consider that some algorithms may need to be verified against dozens or hundreds of inputs (think of a prime number checker, or our Armstrong number example below). Writing 100 separate tests is error-prone and hard to manage; a data-driven approach can handle this by design. Each new data row is effectively a new test, and Jest will handle running them all. There isn’t a practical limit (within reason) to how many cases you can add in a data-driven test – you could even generate them programmatically if needed. The test output will simply show each case result, and you can use grouping (&lt;code&gt;describe&lt;/code&gt;) to organize if there are logical subsets of cases.&lt;/p&gt;

&lt;p&gt;In terms of &lt;strong&gt;readability&lt;/strong&gt;, a well-structured data-driven test can actually make tests easier to understand at a glance. The reason is that the core logic is written once, clearly, and the list of test cases (data) reads like a specification of expected outcomes. By using descriptive test names with placeholders, anyone reading the test results or code can infer the purpose of each case. Moreover, keeping the data separate (in an array or file) means the test file isn’t bogged down with repetitive code – it focuses on the &lt;em&gt;behavior&lt;/em&gt; being verified. This separation of concerns (logic vs data) is a hallmark of clean testing practices (&lt;a href="https://blog.codeleak.pl/2021/12/parameterized-tests-with-jest.html#:~:text=Parameterized%20tests%20are%20used%20to,easier%20to%20read%20and%20maintain" rel="noopener noreferrer"&gt;Parameterized tests in JavaScript with Jest&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It's worth noting that each data-driven test case is still isolated and reported separately by Jest. If one case fails, it doesn't stop the others from running, and you get a precise report of which input failed. This granular reporting, combined with dynamic test names, improves debuggability as well – you immediately know which input caused an issue.&lt;/p&gt;

&lt;p&gt;To maximize readability and maintainability, follow these tips (many of which apply to any test, but especially parameterized ones):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep the data structures simple and representative of the inputs. If needed, include comments or use object keys that make the meaning of each value obvious (&lt;a href="https://dev.to/bgord/simplify-repetitive-jest-test-cases-with-test-each-310m#:~:text=I%20find%20it%20worthwhile%20to,readability%20and%20reduce%20mental%20effort"&gt;Simplify repetitive Jest test cases with test.each - DEV Community&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use placeholders or interpolation in test names to clearly identify each test case in output. For example, use tokens like &lt;code&gt;%i&lt;/code&gt;, &lt;code&gt;%s&lt;/code&gt;, &lt;code&gt;%o&lt;/code&gt; in the test title (as supported by Jest) to print numbers, strings, or objects (&lt;a href="https://dev.to/flyingdot/data-driven-unit-tests-with-jest-26bh#:~:text=We%20can%20use%20several%20predefined,to%20locate%20a%20failing%20test"&gt;Data-driven Unit Tests with Jest - DEV Community&lt;/a&gt;) (&lt;a href="https://dev.to/bgord/simplify-repetitive-jest-test-cases-with-test-each-310m#:~:text=describe%28,"&gt;Simplify repetitive Jest test cases with test.each - DEV Community&lt;/a&gt;). This way, a failing test message immediately shows which data set failed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Group related data-driven tests using &lt;code&gt;describe()&lt;/code&gt; blocks if it makes sense to separate contexts or functionalities. Jest also offers &lt;code&gt;describe.each&lt;/code&gt; to run an entire group of tests under multiple conditions (though that is more advanced usage).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure the data sets cover not just typical cases but edge cases (e.g., for Armstrong: 0, 1, largest n-digit Armstrong, etc.). The ease of adding cases means it's feasible to include edge scenarios that might be overlooked in manual one-off tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a particular data-driven test grows too large or complex, consider splitting it into smaller ones for different categories of inputs (for example, valid vs invalid inputs in separate tests) to maintain clarity.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following these patterns, the test suite remains &lt;strong&gt;readable&lt;/strong&gt; and &lt;strong&gt;easy to extend&lt;/strong&gt;, even as new requirements emerge. Developers can scan a data file or array to see everything that's being validated, and the single test definition remains uncluttered and focused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Data-Driven Tests in Jest
&lt;/h2&gt;

&lt;p&gt;When implementing data-driven (parameterized) tests in Jest, keep in mind some best practices to get the most out of this technique:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use Data-Driven Tests Intentionally:&lt;/strong&gt; Only use &lt;code&gt;test.each&lt;/code&gt; (or similar) when you truly have the &lt;em&gt;same&lt;/em&gt; logic being tested with varying inputs. If each case requires different setup or fundamentally different assertions, separate &lt;code&gt;test&lt;/code&gt; blocks might be clearer. In short, parameterize tests when it removes duplication, but don't force unrelated scenarios into one loop (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=Here%20are%20a%20few%20best,each%28%29%20in%20Jest" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Leverage Descriptive Test Names:&lt;/strong&gt; Make sure each generated test has a unique and descriptive name. Utilize format strings or template literals to include input values in the name (&lt;a href="https://dev.to/flyingdot/data-driven-unit-tests-with-jest-26bh#:~:text=We%20can%20use%20several%20predefined,to%20locate%20a%20failing%20test"&gt;Data-driven Unit Tests with Jest - DEV Community&lt;/a&gt;). This practice makes it much easier to identify failing cases. For example, &lt;code&gt;"isArmstrong(%i) should return %s"&lt;/code&gt; is clearer than a generic name for all cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use Placeholders and Template Literals for Clarity:&lt;/strong&gt; Jest’s &lt;code&gt;%p&lt;/code&gt; (pretty print), &lt;code&gt;%i&lt;/code&gt; (integer), &lt;code&gt;%s&lt;/code&gt; (string), and &lt;code&gt;%o&lt;/code&gt; (object) placeholders in test titles can automatically format values (&lt;a href="https://dev.to/flyingdot/data-driven-unit-tests-with-jest-26bh#:~:text=We%20can%20use%20several%20predefined,to%20locate%20a%20failing%20test"&gt;Data-driven Unit Tests with Jest - DEV Community&lt;/a&gt;). Alternatively, the tagged template form allows you to reference variables directly in the title. Use these to your advantage so that your test output is self-explanatory and each case can be distinguished (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=,to%20group%20the%20test%20cases" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Organize Test Data Logically:&lt;/strong&gt; If the data set is large, consider splitting it into multiple smaller sets or using multiple &lt;code&gt;describe.each&lt;/code&gt; blocks for different categories. This can prevent one huge table from becoming unwieldy. Also, keep the data sorted or structured (for example, all expected-true cases first, then false cases) if that helps readability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Externalize Large Data Sets:&lt;/strong&gt; As noted, move large case tables to external files (JSON/CSV) for clarity (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=,to%20group%20the%20test%20cases" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;) (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=Advanced%20Use%20Cases%20%E2%80%93%20Usage,Test%20data%20from%20external%20sources" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;). This also allows non-developers or testers to review the test cases easily. Ensure that your test runner is configured to include these files (with Jest, simply requiring the JSON is sufficient).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stay Consistent:&lt;/strong&gt; Use a consistent approach across your test suite. For instance, if you use &lt;code&gt;test.each&lt;/code&gt; in one place, use similar patterns elsewhere when appropriate so that other contributors quickly understand the style. Consistency in how data-driven tests are written will make your test codebase more uniform and predictable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watch Out for Async Data Sources:&lt;/strong&gt; If your data is coming from an asynchronous source (like a database or API call), make sure to retrieve it &lt;em&gt;before&lt;/em&gt; calling &lt;code&gt;test.each&lt;/code&gt;. For example, use a &lt;code&gt;beforeAll&lt;/code&gt; to fetch data, then inside it define or run the parameterized tests. The data needs to be available at the time the tests are defined. (If using a static JSON file, this isn’t an issue – it’s loaded immediately.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validate Test Data:&lt;/strong&gt; It can be useful to ensure your test data itself is correct (especially if it's large or hand-crafted). Simple sanity checks (like ensuring no duplicate cases, or that expected outputs match a known formula for known inputs) can save debugging time later. This isn't specific to Jest, but a general good practice when you rely on external test vectors.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following these best practices helps ensure that your parameterized tests remain efficient and understandable rather than turning into a confusing abstraction (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=Here%20are%20a%20few%20best,each%28%29%20in%20Jest" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;). The goal is to simplify testing, and with a thoughtful approach, data-driven tests can greatly streamline your test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Armstrong Number Tests with Data-Driven Approach
&lt;/h2&gt;

&lt;p&gt;To cement the concepts, let's consider an &lt;strong&gt;Armstrong number&lt;/strong&gt; checker function and see how data-driven tests apply. An Armstrong number (also known as a narcissistic number) is an &lt;code&gt;n&lt;/code&gt;-digit number that is equal to the sum of each of its digits raised to the power of &lt;code&gt;n&lt;/code&gt;. For example, 153 is a 3-digit Armstrong number because 13+53+33=1531^3 + 5^3 + 3^3 = 153. We want to test a function &lt;code&gt;isArmstrong(num)&lt;/code&gt; that returns &lt;code&gt;true&lt;/code&gt; if &lt;code&gt;num&lt;/code&gt; is an Armstrong number, and &lt;code&gt;false&lt;/code&gt; otherwise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional approach (for comparison):&lt;/strong&gt; Without data-driven testing, you might write separate tests like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;153 is an Armstrong number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isArmstrong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;153&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;9474 is an Armstrong number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isArmstrong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9474&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123 is not an Armstrong number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isArmstrong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="err"&gt;⋮&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have many such numbers to test, writing individual tests becomes tedious and repetitive. Instead, we can list all our interesting test cases (both Armstrong numbers and non-Armstrong numbers with expected false) and iterate through them in one sweep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data-driven approach with Jest:&lt;/strong&gt; We create a JSON file (or simply an array in the test file) of cases. For example, &lt;code&gt;armstrong-cases.json&lt;/code&gt; might contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;153&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9474&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9475&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we include some known Armstrong numbers (0, 1, 153, 9474 are Armstrong numbers) and some that are not (9475, 123, etc.). Now our Jest test can load this data and use &lt;code&gt;it.each&lt;/code&gt; to create a test for each entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./armstrong-cases.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isArmstrong()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns $expected for $number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isArmstrong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this test suite runs, Jest will generate a separate sub-test for each object in &lt;code&gt;cases&lt;/code&gt;. The test name will interpolate the values, so you’ll see output like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✓ returns true for 0&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✓ returns true for 1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✓ returns true for 153&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✓ returns true for 9474&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✓ returns false for 9475&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✓ returns false for 123&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those corresponds to one row in our JSON. We have effectively written one template test and supplied it with multiple inputs. This makes it very easy to verify a bunch of Armstrong numbers and non-Armstrong numbers in one go. If tomorrow we want to test another number (say 370, which is also an Armstrong number), we just add &lt;code&gt;{ "number": 370, "expected": true }&lt;/code&gt; to the JSON file – no other code changes required.&lt;/p&gt;

&lt;p&gt;This Armstrong number test case demonstrates how data-driven testing improves &lt;strong&gt;readability&lt;/strong&gt; (the test cases are clearly enumerated), &lt;strong&gt;maintainability&lt;/strong&gt; (new cases can be added without modifying test logic), and &lt;strong&gt;coverage&lt;/strong&gt; (we can include many examples, including edge cases like 0 or 1). The structure follows the same pattern described earlier for any data-driven test in Jest. In fact, this approach is identical to the addition example shown previously, just applied to a different problem domain (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=const%20testdata%20%3D%20require%28%27" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;). Whether it's Armstrong numbers, mathematical functions, or any scenario with input-output pairs, the methodology remains the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Data-driven testing in Jest is a powerful pattern that simplifies how we write repetitive tests. By separating the &lt;em&gt;data&lt;/em&gt; from the &lt;em&gt;test code&lt;/em&gt;, we gain cleaner tests that are easy to extend and hard to accidentally break when requirements change. Jest’s built-in support via &lt;code&gt;test.each&lt;/code&gt;/&lt;code&gt;it.each&lt;/code&gt; makes implementing parameterized tests straightforward – turning an array of cases into a suite of individual tests with one concise definition. This approach yields a more maintainable, scalable, and readable test suite, as demonstrated with the Armstrong number example and others.&lt;/p&gt;

&lt;p&gt;By leveraging official Jest features and following best practices (like descriptive test names and externalizing large datasets), you can improve your test quality and coverage with minimal overhead. In summary, data-driven tests allow you to &lt;strong&gt;“write once, test many”&lt;/strong&gt;, ensuring your code is verified against a wide range of inputs while keeping the test code DRY (Don’t Repeat Yourself) (&lt;a href="https://blog.codeleak.pl/2021/12/parameterized-tests-with-jest.html#:~:text=Parameterized%20tests%20are%20used%20to,easier%20to%20read%20and%20maintain" rel="noopener noreferrer"&gt;Parameterized tests in JavaScript with Jest&lt;/a&gt;). It’s a technique well worth using whenever you have numerous similar test scenarios to cover in your JavaScript projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Jest Official Documentation on Parameterized Tests (&lt;code&gt;test.each&lt;/code&gt;) (&lt;a href="https://jestjs.io/docs/en/api#testeachtable-name-fn-timeout#:~:text=Use%20,once%20and%20pass%20data%20in" rel="noopener noreferrer"&gt;Globals · Jest&lt;/a&gt;) (&lt;a href="https://jestjs.io/docs/en/api#testeachtable-name-fn-timeout#:~:text=test.each%28,2%2C%201%2C%203" rel="noopener noreferrer"&gt;Globals · Jest&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Community tutorials on data-driven testing with Jest (&lt;a href="https://dev.to/bgord/simplify-repetitive-jest-test-cases-with-test-each-310m#:~:text=describe%28,"&gt;Simplify repetitive Jest test cases with test.each - DEV Community&lt;/a&gt;) (&lt;a href="https://dev.to/bgord/simplify-repetitive-jest-test-cases-with-test-each-310m#:~:text=Benefits%3A"&gt;Simplify repetitive Jest test cases with test.each - DEV Community&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Best practice guides for using &lt;code&gt;it.each&lt;/code&gt; with external data (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=Here%20are%20a%20few%20best,each%28%29%20in%20Jest" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;) (&lt;a href="https://www.browserstack.com/guide/it-each-jest#:~:text=const%20testdata%20%3D%20require%28%27" rel="noopener noreferrer"&gt;it.each function in Jest | BrowserStack&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explanation of parameterized tests and their benefits in unit testing (&lt;a href="https://blog.codeleak.pl/2021/12/parameterized-tests-with-jest.html#:~:text=Parameterized%20tests%20are%20used%20to,easier%20to%20read%20and%20maintain" rel="noopener noreferrer"&gt;Parameterized tests in JavaScript with Jest&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tdd</category>
      <category>unittest</category>
    </item>
    <item>
      <title>The Lazy Engineer’s Guide to Automating Timesheets: Part 1</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Tue, 28 Jan 2025 23:34:47 +0000</pubDate>
      <link>https://forem.com/ernestohs/the-lazy-engineers-guide-to-automating-timesheets-part-1-i3b</link>
      <guid>https://forem.com/ernestohs/the-lazy-engineers-guide-to-automating-timesheets-part-1-i3b</guid>
      <description>&lt;p&gt;Ah, timesheets. The Lex Luthor of every software engineer’s existence. If you’re like me, you’d rather debug a race condition at 3 a.m. than spend even five minutes documenting what you did all day. But alas, as freelancers or full-time employees, we’re often stuck with this tedious chore.&lt;/p&gt;

&lt;p&gt;This year, I hit my breaking point. After a whirlwind of projects—some canceled, some pivoted, and others shelved indefinitely—I found myself staring down the barrel of a year-end timesheet deadline. The thought of manually reconstructing my entire year’s work made me want to scream. So, I did what any self-respecting engineer would do: I decided to automate the problem away.&lt;/p&gt;

&lt;p&gt;This is the story of how I turned a day of dread into a coding adventure. Buckle up—it’s going to be a lazy, efficient ride.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Timesheets Are the Worst
&lt;/h2&gt;

&lt;p&gt;Let’s set the scene:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Task&lt;/strong&gt;: Document every hour spent on every task for the entire year.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Obstacle&lt;/strong&gt;: My memory is about as reliable as a flaky integration test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Deadline&lt;/strong&gt;: One day. Yes,  &lt;em&gt;one day&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I knew I couldn’t do this manually. So, I hatched a plan to pull data from all the tools I use daily—JIRA, Git, Slack, and Outlook—and stitch it together into a coherent timesheet.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tools of the Trade
&lt;/h2&gt;

&lt;p&gt;Here’s what I used:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JIRA&lt;/strong&gt;: For tracking tasks and tickets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt;: For commit history (because every good engineer ties commits to tickets, right?).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Slack&lt;/strong&gt;: For team communication (because meetings and messages count too).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outlook&lt;/strong&gt;: For calendar events (because apparently, meetings are work too).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 1: Pulling JIRA Tickets
&lt;/h2&gt;

&lt;p&gt;My first stop was JIRA. I needed to pull all the tickets assigned to me within a specific date range. Thankfully, JIRA has a robust API, and with a little Python magic, I was able to automate this process.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Script
&lt;/h3&gt;

&lt;p&gt;Here’s the Python script I wrote to fetch JIRA tickets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jira&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JIRA&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;

&lt;span class="c1"&gt;# Configure logging
&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%(asctime)s - %(levelname)s - %(message)s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JiraTicketPuller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Initialize JIRA client with authentication credentials.

        Args:
            server: JIRA server URL
            email: User&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s email for authentication
            api_token: JIRA API token
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jira&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JIRA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basic_auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Successfully connected to JIRA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to connect to JIRA: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_tickets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Retrieve all tickets assigned to a user within a date range.

        Args:
            username: JIRA username to search for
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format

        Returns:
            List of dictionaries containing ticket information
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Construct JQL query
&lt;/span&gt;            &lt;span class="n"&gt;jql_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assignee = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; AND &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created &amp;gt;= &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; AND &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created &amp;lt;= &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ORDER BY created DESC&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Fields to retrieve
&lt;/span&gt;            &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;issuetype&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;updated&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;components&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="c1"&gt;# Get issues using JQL
&lt;/span&gt;            &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jira&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_issues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;jql_query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;maxResults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Adjust based on your needs
&lt;/span&gt;                &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;tickets_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Priority&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Issue Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issuetype&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Created&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Get date only
&lt;/span&gt;                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Updated&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Get date only
&lt;/span&gt;                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Project&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Components&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;tickets_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retrieved &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tickets for user &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tickets_data&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error retrieving tickets: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;export_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Export tickets data to CSV file.

        Args:
            tickets: List of ticket dictionaries
            output_file: Path to output CSV file
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No tickets to export&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;

            &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Successfully exported tickets to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error exporting to CSV: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Validate date string format (YYYY-MM-DD).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Set up argument parser
&lt;/span&gt;    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pull JIRA tickets for a user within a date range&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;JIRA username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--start-date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Start date (YYYY-MM-DD)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--end-date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;End date (YYYY-MM-DD)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--output&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jira_tickets.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Output CSV file path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Validate dates
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;validate_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid date format. Please use YYYY-MM-DD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Configuration
&lt;/span&gt;    &lt;span class="n"&gt;JIRA_SERVER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JIRA_SERVER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;JIRA_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JIRA_EMAIL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;JIRA_API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JIRA_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize ticket puller
&lt;/span&gt;    &lt;span class="n"&gt;puller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JiraTicketPuller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JIRA_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JIRA_EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JIRA_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get and export tickets
&lt;/span&gt;    &lt;span class="n"&gt;tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;puller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user_tickets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;puller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Ensure JIRA API token is set
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JIRA_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JIRA_API_TOKEN environment variable not set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: The script uses your JIRA email and API token to authenticate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JQL Query&lt;/strong&gt;: It constructs a JQL query to fetch tickets assigned to you within a date range.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Export&lt;/strong&gt;: The results are exported to a CSV file for easy analysis.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 2: Fetching Git Commits
&lt;/h2&gt;

&lt;p&gt;Next, I turned to Git. Since our team follows a practice of including JIRA ticket IDs in commit messages, I wrote a script to extract commit data and map it to tickets.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_git_commits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;since_date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Get commit data with full commit message
&lt;/span&gt;    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;log&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--pretty=format:%h|%ad|%s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--date=iso&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;since_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--since&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;since_date&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;commits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Process commits to extract JIRA IDs
&lt;/span&gt;    &lt;span class="n"&gt;processed_commits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;jira_pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;JIRA:\s*([A-Z]+-\d+)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Skip empty lines
&lt;/span&gt;            &lt;span class="n"&gt;hash_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;jira_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jira_pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;jira_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jira_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;jira_match&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;

            &lt;span class="n"&gt;processed_commits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;commit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hash_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jira_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jira_id&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;# Write to CSV
&lt;/span&gt;    &lt;span class="n"&gt;csv_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;git_commits_jira.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;fieldnames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;commit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jira_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv_filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processed_commits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;processed_commits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Git Log&lt;/strong&gt;: The script uses  &lt;code&gt;git log&lt;/code&gt;  to fetch commit history.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JIRA ID Extraction&lt;/strong&gt;: It uses regex to extract JIRA ticket IDs from commit messages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CSV Export&lt;/strong&gt;: The results are saved to a CSV file for later use.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 3: Tackling Slack Messages
&lt;/h2&gt;

&lt;p&gt;Slack was trickier. Messages are context-heavy, and mapping them to specific tasks isn’t straightforward. I briefly considered using AI to parse the data but decided against it due to cost and complexity. Instead, I created a generic ticket to capture communication time and wrote a script to fetch Slack messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;slack_sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;slack_sdk.errors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SlackApiError&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_all_conversations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Get all conversations (channels, private channels, and DMs) the bot has access to.

    Args:
        client: Slack WebClient instance

    Returns:
        list: List of conversation objects
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;conversations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Get all conversations (channels, private channels, and DMs)
&lt;/span&gt;        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;conversations_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public_channel,private_channel,im,mpim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="c1"&gt;# Handle pagination
&lt;/span&gt;        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response_metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;next_cursor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response_metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;next_cursor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;conversations_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public_channel,private_channel,im,mpim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;SlackApiError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error fetching conversations: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conversations&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Get user information from their ID.

    Args:
        client: Slack WebClient instance
        user_id: User&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s Slack ID

    Returns:
        dict: User information including real name and email
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;users_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;real_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;real_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;display_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;display_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;SlackApiError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;real_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;display_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_messages_from_conversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Retrieve messages from a specific conversation within a date range.

    Args:
        client: Slack WebClient instance
        channel_id (str): The ID of the Slack channel
        channel_name (str): The name of the Slack channel
        start_date (str): Start date in format &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YYYY-MM-DD&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;
        end_date (str): End date in format &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YYYY-MM-DD&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;

    Returns:
        list: List of messages within the date range
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;start_timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;end_timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strptime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Initialize the cursor for pagination
&lt;/span&gt;        &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Get messages using conversations.history
&lt;/span&gt;            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;conversations_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;oldest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;start_timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;end_timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Process messages
&lt;/span&gt;            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;user_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_user_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;real_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;display_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="n"&gt;message_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;channel_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;real_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d %H:%M:%S&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;thread_ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;thread_ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Check if there are more messages to fetch
&lt;/span&gt;            &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response_metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;next_cursor&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;SlackApiError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error fetching messages from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Set your date range
&lt;/span&gt;    &lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-01-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize the Slack client
&lt;/span&gt;    &lt;span class="n"&gt;slack_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;slack_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please set the SLACK_BOT_TOKEN environment variable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WebClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;slack_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get all conversations
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetching all conversations...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conversations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_all_conversations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Fetch messages from all conversations
&lt;/span&gt;    &lt;span class="n"&gt;all_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;total_conversations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_conversations&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; conversations. Fetching messages...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conv&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;channel_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;channel_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# DMs don't have names
&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_conversations&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_messages_from_conversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;channel_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;end_date&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;all_messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert to DataFrame for easier handling
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Save to CSV
&lt;/span&gt;    &lt;span class="n"&gt;output_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slack_messages_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_to_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Total messages retrieved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Messages saved to: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Print summary
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Messages per channel:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;channel_summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Conversation List&lt;/strong&gt;: The script fetches all channels and DMs accessible to the bot.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Message Retrieval&lt;/strong&gt;: It retrieves messages within a specified date range.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CSV Export&lt;/strong&gt;: The messages are saved to a CSV file for further analysis.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 4: Capturing Outlook Meetings
&lt;/h2&gt;

&lt;p&gt;Finally, I needed to account for meetings. Using the  &lt;code&gt;exchangelib&lt;/code&gt;  Python library, I wrote a script to pull calendar events and export them to a CSV.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;exchangelib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_outlook_meetings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Replace with your email and password
&lt;/span&gt;    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JIRA_EMAIL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OUTLOOK_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Connect to Exchange
&lt;/span&gt;    &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;autodiscover&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get calendar items for the last 30 days (adjust as needed)
&lt;/span&gt;    &lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get calendar items
&lt;/span&gt;    &lt;span class="n"&gt;calendar_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Prepare data structure
&lt;/span&gt;    &lt;span class="n"&gt;meetings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# Process each appointment
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;calendar_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Calculate duration in minutes
&lt;/span&gt;        &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;meetings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%H:%M&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;# Create DataFrame and save to CSV
&lt;/span&gt;    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meetings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;outlook_meetings.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;

&lt;span class="c1"&gt;# Run the function
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;meetings_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_outlook_meetings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exported &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meetings_df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; meetings to outlook_meetings.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: The script uses your Outlook email and password to authenticate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Calendar Query&lt;/strong&gt;: It fetches calendar events within a specified date range.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CSV Export&lt;/strong&gt;: The events are saved to a CSV file.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;At this point, I had four CSV files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JIRA Tickets&lt;/strong&gt;: All the tasks I worked on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Git Commits&lt;/strong&gt;: All the code I wrote.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Slack Messages&lt;/strong&gt;: All the communication I participated in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outlook Meetings&lt;/strong&gt;: All the meetings I attended.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In  &lt;strong&gt;Part 2&lt;/strong&gt;, I’ll show you how I stitched these datasets together to create a comprehensive timesheet. Spoiler alert: It involves more Python, some data wrangling, and a little bit of magic.&lt;/p&gt;

&lt;p&gt;Stay tuned, and remember: Laziness is the mother of invention.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What’s your least favorite chore as a software engineer? Have you automated it yet? Share your stories in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>git</category>
      <category>csv</category>
    </item>
    <item>
      <title>Private npm Repositories</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Thu, 02 Jan 2025 23:01:41 +0000</pubDate>
      <link>https://forem.com/ernestohs/private-npm-repositories-5e48</link>
      <guid>https://forem.com/ernestohs/private-npm-repositories-5e48</guid>
      <description>&lt;h1&gt;
  
  
  Private npm Repositories: Taking Control of Your Code
&lt;/h1&gt;

&lt;p&gt;As software craftsmen, we understand that code isn’t just a tool—it’s a legacy. And like any craftsman, we guard our tools fiercely. What happens, though, when your team’s shared libraries or proprietary modules need to be distributed securely, efficiently, and without exposing your intellectual treasure to the world? Enter the private npm repository—a vault for your code artifacts. Let’s explore how to build, configure, and wield this essential tool.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Private npm Repositories Matter
&lt;/h2&gt;

&lt;p&gt;Imagine a workshop where every tool is labeled, organized, and accessible only to those who’ve earned the right to use them. That’s what a private npm registry offers:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security Without Compromise&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Keep sensitive code behind your firewall. No more leaking API keys or proprietary logic into public domains.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speed as a Virtue&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Reduce dependency on external networks. Your builds run faster when packages are hosted locally, cached, and always available.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Governance You Can Enforce&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Not every developer needs access to every package. Control who publishes, who installs, and who audits.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Versioning Without Chaos&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Manage internal dependencies cleanly. No more “works on my machine” surprises when public packages shift beneath your feet.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Choosing Your Registry: A Craftsman’s Guide
&lt;/h2&gt;

&lt;p&gt;Private registries come in many forms. Your choice depends on scale, infrastructure, and philosophy. Let’s weigh the options:  &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Self-Hosted Solutions
&lt;/h3&gt;

&lt;p&gt;For teams who own their infrastructure:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://verdaccio.org/" rel="noopener noreferrer"&gt;Verdaccio&lt;/a&gt;&lt;/strong&gt;: Lightweight, open-source, and perfect for small teams. It proxies public npm, caches dependencies, and requires minimal setup.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.sonatype.com/products/nexus-repository" rel="noopener noreferrer"&gt;Sonatype Nexus&lt;/a&gt;&lt;/strong&gt;: The Swiss Army knife of repositories. Supports npm, Maven, Docker—ideal for polyglot environments.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://jfrog.com/artifactory/" rel="noopener noreferrer"&gt;JFrog Artifactory&lt;/a&gt;&lt;/strong&gt;: Enterprise-grade, with advanced security and scalability.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Git-Integrated Registries
&lt;/h3&gt;

&lt;p&gt;For those already immersed in Git workflows:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Packages&lt;/strong&gt;: Tightly integrated with GitHub. Publish once, and your code and packages live side-by-side.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitLab Packages&lt;/strong&gt;: Built into GitLab’s DevOps pipeline. CI/CD and package management in one breath.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bitbucket&lt;/strong&gt;: Less native, but achievable through add-ons like npm Registry or custom scripts.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. npm Enterprise
&lt;/h3&gt;

&lt;p&gt;For large organizations needing turnkey solutions:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSO integration, compliance audits, and dedicated support. It’s npm, but on your terms.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Crafting Your Registry: Hands-On
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Verdaccio: The Artisan’s Choice
&lt;/h3&gt;

&lt;p&gt;Verdaccio is to npm registries what a well-honed chisel is to a woodworker: simple, effective, and precise.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Install&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; verdaccio  &lt;span class="c"&gt;# Sharpening the tool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Configure&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Fire up Verdaccio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;verdaccio  &lt;span class="c"&gt;# The registry awakens at http://localhost:4873&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tweak  &lt;code&gt;~/.config/verdaccio/config.yaml&lt;/code&gt;  to suit your needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Like a key to your workshop &lt;/span&gt;

&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;htpasswd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./htpasswd&lt;/span&gt;  
    &lt;span class="na"&gt;max_users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100&lt;/span&gt;  

&lt;span class="c1"&gt;# Proxy public packages to avoid external delays &lt;/span&gt;
&lt;span class="na"&gt;uplinks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;npmjs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://registry.npmjs.org/&lt;/span&gt;

&lt;span class="c1"&gt;# Define access rules—guard your tools &lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@myteam/*'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$authenticated&lt;/span&gt;  
    &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$authenticated&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Publish&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm adduser &lt;span class="nt"&gt;--registry&lt;/span&gt; http://localhost:4873  &lt;span class="c"&gt;# Claim your space &lt;/span&gt;
npm publish &lt;span class="nt"&gt;--registry&lt;/span&gt; http://localhost:4873  &lt;span class="c"&gt;# Forge the artifact&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Consume&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Either use  &lt;code&gt;--registry&lt;/code&gt;  for one-off installs or lock it in  &lt;code&gt;.npmrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4873  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. GitHub Packages: The Collaborator’s Haven
&lt;/h3&gt;

&lt;p&gt;When your code lives on GitHub, why scatter packages elsewhere?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Authenticate&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Generate a Personal Access Token (PAT) with  &lt;code&gt;read:packages&lt;/code&gt;  and  &lt;code&gt;write:packages&lt;/code&gt;  scopes. Then, in  &lt;code&gt;.npmrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;//npm.pkg.github.com/:&lt;/span&gt;&lt;span class="py"&gt;_authToken&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ghp_YourTokenHere  &lt;/span&gt;
&lt;span class="err"&gt;@your-org:&lt;/span&gt;&lt;span class="py"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://npm.pkg.github.com  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Publish with Precision&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Update  &lt;code&gt;package.json&lt;/code&gt;  to reflect your org’s scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@your-org/secret-sauce"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="nl"&gt;"publishConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"registry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://npm.pkg.github.com"&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm publish  &lt;span class="c"&gt;# Your secret sauce is now sealed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Install Seamlessly&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Any teammate with access can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @your-org/secret-sauce  &lt;span class="c"&gt;# Serve it hot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. GitLab Packages: The CI/CD Power Play
&lt;/h3&gt;

&lt;p&gt;GitLab’s registry is a natural fit for teams automating their pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Configure Access&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Add to  &lt;code&gt;.npmrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;@your-group:&lt;/span&gt;&lt;span class="py"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://gitlab.com/api/v4/packages/npm/  &lt;/span&gt;
&lt;span class="err"&gt;//gitlab.com/api/v4/packages/npm/:&lt;/span&gt;&lt;span class="py"&gt;_authToken&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;YourPAT  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Publish from the Pipeline&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In your  &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "//gitlab.com/api/v4/packages/npm/:_authToken=${CI_JOB_TOKEN}" &amp;gt; .npmrc&lt;/span&gt;  
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm publish&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Enterprise Forges: Nexus and Artifactory
&lt;/h2&gt;

&lt;p&gt;For large-scale operations, these tools are the industrial presses of package management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nexus Quickstart&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install Nexus, create an npm hosted repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Point your  &lt;code&gt;.npmrc&lt;/code&gt;  to  &lt;code&gt;http://nexus.yourcorp.com/repository/npm-internal/&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm login --registry=http://nexus.yourcorp.com&lt;/code&gt;  and publish.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Artifactory in a Nutshell&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Spin up Artifactory, create a local npm repo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure  &lt;code&gt;.npmrc&lt;/code&gt;  with  &lt;code&gt;registry=http://artifactory.yourcorp.com/artifactory/api/npm/npm-local/&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publish, and let Artifactory handle replication, caching, and security.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Principles for the Prudent Craftsman
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scope Your Packages&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Prefix internal packages with  &lt;code&gt;@your-org/&lt;/code&gt;. It’s namespacing, not bureaucracy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secure Your .npmrc&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Keep tokens out of Git. Use environment variables or encrypted secrets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Proxy Public Packages&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Let your registry cache public dependencies. Faster installs, fewer outages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automate Relentlessly&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Bake publishing into CI/CD. No manual  &lt;code&gt;npm publish&lt;/code&gt;—humans err, pipelines endure.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion: Own Your Artifacts
&lt;/h2&gt;

&lt;p&gt;A private npm registry isn’t just a technical choice—it’s a statement of ownership. Whether you’re a solo developer safeguarding side projects or an enterprise architecting a global pipeline, the principles remain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Control&lt;/strong&gt;  your dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secure&lt;/strong&gt;  your code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Streamline&lt;/strong&gt;  your workflow.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose the tool that fits your shop. Set it up with care. Then go forth, craft brilliantly, and let your code thrive—safely, efficiently, and entirely on your terms.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After all, isn’t that what clean craftsmanship is all about?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>npm</category>
      <category>javascript</category>
      <category>programming</category>
      <category>packagemanage</category>
    </item>
    <item>
      <title>A Technical Introduction to LocalStack</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Thu, 02 Jan 2025 06:32:52 +0000</pubDate>
      <link>https://forem.com/ernestohs/a-technical-introduction-to-localstack-23e2</link>
      <guid>https://forem.com/ernestohs/a-technical-introduction-to-localstack-23e2</guid>
      <description>&lt;p&gt;LocalStack is an excellent tool that allows you to emulate AWS services on your local machine. By spinning up a local environment that replicates various AWS services, developers can speed up their development and testing cycles without incurring cloud costs or requiring an active AWS account. In this post, we’ll discuss what LocalStack is, how to install it, and how you can use it with various AWS services in your local development workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is LocalStack?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://localstack.cloud/" rel="noopener noreferrer"&gt;LocalStack&lt;/a&gt; is a fully functional local AWS cloud stack. It provides mock implementations of many popular AWS services, including S3, DynamoDB, Lambda, and more. Using LocalStack, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simulate&lt;/strong&gt; AWS services locally for development and testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid&lt;/strong&gt; AWS billing charges for creating and destroying resources repeatedly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed up&lt;/strong&gt; the development cycle by removing the dependency on external AWS infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test&lt;/strong&gt; AWS infrastructure and application logic in a controlled environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LocalStack is built using Python, Docker, and various mocks and emulators for the different AWS services. Once you have LocalStack running, you can use your existing AWS CLI or SDK commands, provided you point them to your local endpoints.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt;: LocalStack uses Docker containers to spin up AWS mock services. Make sure you have Docker installed and running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python 3+&lt;/strong&gt; (if you prefer to install the LocalStack CLI via &lt;code&gt;pip&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Installing LocalStack
&lt;/h3&gt;

&lt;p&gt;The recommended way to run LocalStack is by using Docker Compose. You can either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;a href="https://hub.docker.com/r/localstack/localstack" rel="noopener noreferrer"&gt;LocalStack Docker Image&lt;/a&gt;, or&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;docker-compose.yml&lt;/code&gt; to customize your environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is an example &lt;code&gt;docker-compose.yml&lt;/code&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;localstack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localstack/localstack:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localstack&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4566:4566"&lt;/span&gt;   &lt;span class="c1"&gt;# LocalStack edge port&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4571:4571"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SERVICES=s3,dynamodb,lambda&lt;/span&gt;   &lt;span class="c1"&gt;# Which services to start&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DEBUG=1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATA_DIR=/tmp/localstack/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;LAMBDA_EXECUTOR=docker&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;HOST_TMP_FOLDER=/tmp/localstack&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./localstack-data:/tmp/localstack"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: LocalStack exposes &lt;strong&gt;port 4566&lt;/strong&gt; (the “edge” port) to accept requests for all services. This can simplify your endpoint configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After saving this file, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LocalStack will now start in your terminal, showing logs about each service being initialized.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using LocalStack
&lt;/h2&gt;

&lt;p&gt;Once LocalStack is running, you can interact with it using:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI&lt;/strong&gt; (by configuring endpoints)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS SDKs&lt;/strong&gt; (e.g., &lt;code&gt;boto3&lt;/code&gt; in Python)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LocalStack CLI&lt;/strong&gt; (optional, if installed locally)&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Configuring the AWS CLI
&lt;/h3&gt;

&lt;p&gt;To interact with LocalStack using the AWS CLI, you need to specify the LocalStack endpoint and credentials. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure &lt;span class="nb"&gt;set &lt;/span&gt;aws_access_key_id &lt;span class="nb"&gt;test
&lt;/span&gt;aws configure &lt;span class="nb"&gt;set &lt;/span&gt;aws_secret_access_key &lt;span class="nb"&gt;test
&lt;/span&gt;aws configure &lt;span class="nb"&gt;set &lt;/span&gt;default.region us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can specify the &lt;code&gt;--endpoint-url&lt;/code&gt; for each service call. For instance, to create an S3 bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api create-bucket &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--bucket&lt;/span&gt; local-bucket &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:4566
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To list S3 buckets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api list-buckets &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:4566
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your locally created buckets in the output.&lt;/p&gt;




&lt;h3&gt;
  
  
  Using LocalStack with &lt;code&gt;boto3&lt;/code&gt; (Python)
&lt;/h3&gt;

&lt;p&gt;Below is a short Python snippet showing how to connect to LocalStack’s S3 service using &lt;a href="https://github.com/boto/boto3" rel="noopener noreferrer"&gt;&lt;code&gt;boto3&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="c1"&gt;# Create an S3 client pointing to LocalStack
&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;endpoint_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4566&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create a bucket
&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;local-bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# List buckets
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_buckets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buckets:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Buckets&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Upload a file
&lt;/span&gt;&lt;span class="n"&gt;file_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from LocalStack!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hello.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;file_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Download the file
&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hello.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;downloaded_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Downloaded file content:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;downloaded_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this script will interact with your local S3 service hosted by LocalStack.&lt;/p&gt;




&lt;h3&gt;
  
  
  Example: DynamoDB
&lt;/h3&gt;

&lt;p&gt;Let’s create a DynamoDB table and add a few items to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb create-table &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--table-name&lt;/span&gt; MyLocalTable &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--attribute-definitions&lt;/span&gt; &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Id,AttributeType&lt;span class="o"&gt;=&lt;/span&gt;S &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--key-schema&lt;/span&gt; &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Id,KeyType&lt;span class="o"&gt;=&lt;/span&gt;HASH &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--billing-mode&lt;/span&gt; PAY_PER_REQUEST &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:4566
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, list all tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb list-tables &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:4566
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And insert data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb put-item &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--table-name&lt;/span&gt; MyLocalTable &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--item&lt;/span&gt; &lt;span class="s1"&gt;'{"Id": {"S": "1001"}, "Name": {"S": "Test Item"}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:4566
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, query to see your data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb scan &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--table-name&lt;/span&gt; MyLocalTable &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:4566
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Example: Lambda
&lt;/h3&gt;

&lt;p&gt;To test Lambda functions locally, you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;SAM&lt;/code&gt; CLI to build and deploy your Lambda code into LocalStack.&lt;/li&gt;
&lt;li&gt;Or directly create your Lambda function using a zip package and uploading via AWS CLI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Creating a zip&lt;/strong&gt; for a simple Python function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# main.py&lt;/span&gt;
def handler&lt;span class="o"&gt;(&lt;/span&gt;event, context&lt;span class="o"&gt;)&lt;/span&gt;:
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"statusCode"&lt;/span&gt;: 200,
        &lt;span class="s2"&gt;"body"&lt;/span&gt;: &lt;span class="s2"&gt;"Hello from LocalStack!"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Zip the file&lt;/span&gt;
zip &lt;span class="k"&gt;function&lt;/span&gt;.zip main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create the Lambda function&lt;/strong&gt; in LocalStack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda create-function &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--function-name&lt;/span&gt; LocalLambda &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--handler&lt;/span&gt; main.handler &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--runtime&lt;/span&gt; python3.9 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--zip-file&lt;/span&gt; fileb://function.zip &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt; arn:aws:iam::123456:role/irrelevant &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:4566
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--function-name&lt;/span&gt; LocalLambda &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:4566 &lt;span class="se"&gt;\&lt;/span&gt;
    output.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the results in &lt;code&gt;output.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;output.json
&lt;span class="c"&gt;# {&lt;/span&gt;
&lt;span class="c"&gt;#    "statusCode": 200,&lt;/span&gt;
&lt;span class="c"&gt;#    "body": "Hello from LocalStack!"&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Tips and Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Service-Specific Ports vs. Edge Port&lt;/strong&gt;: By default, all AWS services in LocalStack are available through the edge port (&lt;code&gt;4566&lt;/code&gt;). If you need to, you can enable service-specific ports (e.g., 4572 for S3). However, it’s recommended to stick to the edge port for simplicity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persisting Data&lt;/strong&gt;: By default, LocalStack does not persist data across restarts. To persist data, set the &lt;code&gt;DATA_DIR&lt;/code&gt; environment variable or mount a volume in Docker (as in the example &lt;code&gt;docker-compose.yml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Testing&lt;/strong&gt;: LocalStack can be used for end-to-end integration tests without deploying to AWS. You can run your CI/CD workflows using LocalStack in Docker containers and test your infrastructure changes reliably.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrading&lt;/strong&gt;: LocalStack is actively developed, so new features and bug fixes are frequently released. Keep your Docker image updated to take advantage of the latest changes.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;LocalStack is a powerful tool that brings AWS infrastructure right to your local machine. Whether you’re developing new services, testing your IaC (Infrastructure as Code) templates, or running integration tests, LocalStack provides a quick, cost-effective, and convenient solution.&lt;/p&gt;

&lt;p&gt;By following the examples above, you’ll be able to spin up essential AWS services like S3, DynamoDB, and Lambda in your local environment. This approach not only accelerates your development cycle but also ensures that you can verify your application logic thoroughly before deploying to real AWS infrastructure.&lt;/p&gt;

&lt;p&gt;Give it a try and see how it transforms your local AWS development experience!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.localstack.cloud/" rel="noopener noreferrer"&gt;LocalStack Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/localstack/localstack" rel="noopener noreferrer"&gt;LocalStack GitHub Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html" rel="noopener noreferrer"&gt;AWS CLI Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html" rel="noopener noreferrer"&gt;Boto3 Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>Navigating the World of Event-Driven Process Orchestration for Technical Leaders</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Sat, 28 Dec 2024 10:04:15 +0000</pubDate>
      <link>https://forem.com/ernestohs/navigating-the-world-of-event-driven-process-orchestration-for-technical-leaders-pj9</link>
      <guid>https://forem.com/ernestohs/navigating-the-world-of-event-driven-process-orchestration-for-technical-leaders-pj9</guid>
      <description>&lt;h1&gt;
  
  
  Event-Driven Process Orchestration
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Imagine you're at a large, bustling party with many activities—games, snacks, music, and more. Event-driven process orchestration is like having an exceptionally skilled party planner who ensures everything happens at the right moment and in the most efficient way possible. This planner monitors the flow of activities (or “events”) and dynamically reacts to changes, ensuring seamless coordination across the board.&lt;/p&gt;

&lt;p&gt;In technical terms, event-driven process orchestration is designed to respond to significant changes in state—business events—in real-time or near real-time. This approach is especially beneficial when processes are complex, rapidly evolving, and highly responsive to new information or changing conditions.&lt;/p&gt;

&lt;p&gt;Event-driven orchestration involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event Detection&lt;/strong&gt;: Monitoring for events from sensors, user interfaces, or external applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process Orchestration&lt;/strong&gt;: Coordinating workflows and business logic responding to these events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Response&lt;/strong&gt;: Adapting to new data and conditions in real-time or near real-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration of Services&lt;/strong&gt;: Seamlessly tying together various services and applications to automate processes.
Decentralization and Scalability: These allow individual components to be loosely coupled and scale independently as the event load varies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Analytics and Monitoring&lt;/strong&gt;: Continuously tracking orchestration performance and adjusting operations as needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Event-driven orchestration enables businesses to be more agile and competitive in everything from finance and e-commerce to supply chain management and IoT. Below, we delve deeper into how event-driven process orchestration works, its benefits, and how you can begin implementing it in your organization.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Event-Driven Process Orchestration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A Basic Python Example
&lt;/h3&gt;

&lt;p&gt;Let’s start with a simple code snippet illustrating the basics of event-driven orchestration. The scenario involves processing an order as soon as an event (e.g., a new order request) is detected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# Event Listener
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_new_order_received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;process_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Process/Service
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;check_inventory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;inventory_available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;confirm_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;ship_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;notify_out_of_stock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example event data
&lt;/span&gt;&lt;span class="n"&gt;event_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orderId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 123, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Widget&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 10}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;# Simulate event occurrence
&lt;/span&gt;&lt;span class="nf"&gt;on_new_order_received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;on_new_order_received&lt;/strong&gt;: Listens for new orders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;process_order&lt;/strong&gt;: Orchestrates the next steps—inventory checks, order confirmation, shipping, or notification of stock unavailability.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While this example is straightforward, it demonstrates the essence of event-driven thinking: we detect an event (“new order”) and initiate processes accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Distributed Example with Apache Kafka
&lt;/h3&gt;

&lt;p&gt;In real-world scenarios, event-driven orchestration spans multiple services, data sources, and message brokers. One of the most popular distributed event streaming platforms is &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Apache Kafka&lt;/a&gt;. Kafka provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Topics&lt;/strong&gt;: Named related data streams where producers publish, and consumers subscribe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Producers&lt;/strong&gt;: Applications or services that write data to Kafka topics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumers&lt;/strong&gt;: Applications or services that read data from Kafka topics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka Streams&lt;/strong&gt;: A powerful library for processing and analyzing event streams in real-time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider an online retail system that tracks user activities (such as viewing products, adding items to the cart, and making purchases) for recommendations, inventory management, and order processing. Below is a simplified pseudocode example in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;kafka&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KafkaProducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KafkaConsumer&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# Kafka Producer for publishing user activity events
&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KafkaProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bootstrap_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;kafka-server:9092&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_user_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;activity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_activities&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Kafka Consumer for processing user activity events
&lt;/span&gt;&lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KafkaConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_activities&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bootstrap_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;kafka-server:9092&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_user_activity&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;activity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;purchase&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;process_purchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Simple orchestration for purchase events
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_purchase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Here you could implement logic for inventory check, order processing, etc.
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing purchase for user &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage
&lt;/span&gt;&lt;span class="nf"&gt;publish_user_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;view&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;publish_user_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;add_to_cart&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;publish_user_activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;purchase&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;process_user_activity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Typically runs as a service or background process
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;publish_user_activity&lt;/strong&gt;: Sends user activity events to a Kafka topic named &lt;code&gt;user_activities&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;process_user_activity&lt;/strong&gt;: Listens to the topic and triggers relevant business processes (like &lt;code&gt;process_purchase&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka&lt;/strong&gt;: Helps decouple the event producers from event consumers, providing scalability, fault tolerance, and high throughput.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Comparison with Traditional Orchestration Methods
&lt;/h2&gt;

&lt;p&gt;In traditional (often synchronous) orchestration models, a central workflow engine directly invokes services step-by-step—think of it a tightly controlled assembly line. Event-driven orchestration, by contrast, decouples producers and consumers, relying on events to trigger asynchronous responses. This model is more flexible and scalable because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loose Coupling&lt;/strong&gt;: Each component can evolve independently if the event contracts (message formats) remain stable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: More consumers or producers can be added independently to handle peaks in demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilience&lt;/strong&gt;: If one component fails, it doesn’t necessarily bring down the entire system. Events can be cached in brokers like Kafka until the consumer returns online.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Principles and Benefits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Principles
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asynchronicity&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Components operate independently and communicate through events rather than direct synchronous calls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Decentralization&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Responsibility is distributed among multiple services or microservices, reducing single points of failure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event-First Design&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
System interactions are driven by creating, detecting, and handling events, emphasizing data flow rather than direct commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema Evolution and Versioning&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Events typically follow a standardized schema. The evolution of these schemas must be managed carefully to ensure backward compatibility.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time Responsiveness&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Systems can immediately react to user actions, sensor data, or market events, giving businesses an edge in competitive environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improved Efficiency and Automation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
By automating processes in reaction to events, companies reduce manual work, minimize delays, and boost overall efficiency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Scalability&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Event-driven architectures can gracefully handle fluctuating workloads, making them well-suited for applications with variable traffic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better Integration of Disparate Systems&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Data flows smoothly between multiple services or microservices. This integration fosters better cross-team collaboration and more coherent operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Facilitation of Digital Transformation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Event-driven orchestration underpins broader digital initiatives by promoting flexibility, adaptability, and modern integration patterns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increased Customer Satisfaction&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Rapid responses and seamless processes translate into better user experiences, driving customer loyalty and revenue growth.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Challenges and Considerations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complexity of Distributed Systems&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Managing multiple microservices, ensuring reliable message delivery, and handling eventual consistency can be complex.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Consistency and Transactions&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Transactional guarantees can be difficult to obtain in an asynchronous environment. Ensuring data integrity may require patterns like Saga or two-phase commit (where applicable).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitoring and Observability&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Tracing event flows across distributed systems demands robust logging, monitoring, and alerting tools. Systems like Jaeger or OpenTelemetry can help provide visibility.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error Handling and Retry Mechanisms&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Transient issues may cause events to fail to process. Handling retries (e.g., dead-letter queues) is crucial without accidentally reprocessing events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security and Governance&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Proper authentication, authorization, and encryption must be in place for events, especially in multi-tenant or highly regulated environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema Evolution&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Changes to event structures or topics must be carefully orchestrated to avoid breaking downstream consumers.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Real-World Examples
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;E-Commerce Personalization&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Major e-commerce platforms use event-driven orchestration to update recommendations in real-time. Whenever a user views or purchases a product, that event triggers recommendation services to recalculate relevant product suggestions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Financial Trading Systems&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Event-driven architectures power trading platforms to handle market data feeds, rapidly update order books, and execute trades. Real-time event processing helps analysts respond to market fluctuations instantly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IoT and Smart Devices&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In supply chain and logistics, sensors emit events related to inventory, temperature conditions, and location. These events trigger immediate orchestration steps, such as rerouting shipments or sending maintenance alerts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ride-Sharing Applications&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Services such as dispatch, payments, driver location tracking, and customer notifications rely on event-driven orchestration to stay synchronized. Each trip request, acceptance, or cancellation triggers workflows in separate microservices.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Lessons Learned&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start Small&lt;/strong&gt;: Adopt a pilot project to familiarize teams with event-based patterns.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage Managed Services&lt;/strong&gt;: Cloud providers offer managed Kafka services (e.g., Confluent Cloud, AWS MSK, Azure Event Hubs) to reduce infrastructure overhead.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invest in Observability&lt;/strong&gt;: Conduct logging and monitoring early to diagnose event-processing pipelines effectively.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Looking Ahead: Future Trends in Orchestration
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Serverless Architectures&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Platforms like AWS Lambda, Azure Functions, and Google Cloud Functions enable highly elastic, pay-as-you-go event processing pipelines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event Mesh and Edge Computing&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
As IoT grows, events may be processed close to the data source (edge) for real-time analytics. Event mesh patterns will help route events efficiently across regions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI-Driven Orchestration&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Machine Learning (ML) models can help optimize event flow or predict future events, further automating and refining the orchestration process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Global-Scale Implementations&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
With globalization, event-driven architectures will continue to expand across geographies and data centers, necessitating advanced strategies for data replication, multi-region failover, and compliance.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Event-driven process orchestration represents a significant paradigm shift, enabling systems to become more responsive, scalable, and resilient. By detecting and reacting to events in real-time, companies gain operational efficiencies, improve customer satisfaction, and unlock new insights from data streams.&lt;/p&gt;

&lt;p&gt;For technical leaders and developers, now is the time to explore event-driven orchestration. Pilot it for small projects, integrate robust event brokers like Kafka, and ensure you have the right monitoring and observability tools. As organizations adopt microservices, IoT, and AI, event-driven architectures will only grow in importance—both as a strategic differentiator and a foundation for future innovation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call to Action&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evaluate&lt;/strong&gt; your current architecture for potential event-driven opportunities.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experiment&lt;/strong&gt; with a proof-of-concept using a lightweight event broker or managed Kafka service.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expand&lt;/strong&gt; your knowledge by exploring advanced event processing tools like Kafka Streams, KSQL, or serverless platforms.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By embracing event-driven orchestration, you position your organization to tackle modern engineering challenges with agility, scalability, and resilience.&lt;/p&gt;




</description>
      <category>kafka</category>
      <category>eventdriven</category>
      <category>pubsub</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>AGI, Are We There Yet?</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Tue, 24 Dec 2024 19:02:22 +0000</pubDate>
      <link>https://forem.com/ernestohs/agi-are-we-there-yet-n5p</link>
      <guid>https://forem.com/ernestohs/agi-are-we-there-yet-n5p</guid>
      <description>&lt;p&gt;&lt;em&gt;Exploring the Latest Milestones and Ongoing Quest for Human-Level Machine Intelligence&lt;/em&gt;  &lt;/p&gt;




&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Picture this: you’re chatting with a digital assistant who’s as smart as a star engineer, as creative as your favorite novelist, and as empathetic as your closest friend—all rolled into one. That’s Artificial General Intelligence's (AGI) dream: a machine that can understand, learn, and apply knowledge across practically any domain, just like a human. Sounds a bit like a sci-fi blockbuster, right?&lt;/p&gt;

&lt;p&gt;But wait—didn’t we hear that OpenAI’s newest model, &lt;strong&gt;o3&lt;/strong&gt;, aced the Abstraction and Reasoning Corpus (ARC) with a score of 87.5%, beating the average human performance of 84%? Should we pop the confetti and declare AGI has arrived? Not so fast. As it turns out, it’s more of a pitstop on the road to AGI than the final destination. Let’s dive into our milestones, the scientific studies shaping the field, and why experts still say, “We’re not there yet.”&lt;/p&gt;




&lt;h3&gt;
  
  
  What Is AGI, Really?
&lt;/h3&gt;

&lt;p&gt;At its core, &lt;strong&gt;AGI&lt;/strong&gt; means a machine with the broad adaptability of a human mind. It can switch between tasks without a hitch—solving math problems one minute, giving cooking advice the next, and maybe explaining quantum mechanics if you want to go there. This contrasts with &lt;strong&gt;narrow AI&lt;/strong&gt;, which is laser-focused on a single task, like recognizing cats in images or recommending your next Netflix binge.&lt;/p&gt;

&lt;p&gt;AGI isn’t just about raw computational power. It’s about understanding context, nuance, and learning strategies that span multiple domains. In other words, if your AI personal trainer can seamlessly teach you chess and then whip up a solid investment portfolio, that’s the hallmark of AGI. While impressive, today’s best AI systems still fall short of this multi-purpose intelligence.&lt;/p&gt;




&lt;h3&gt;
  
  
  The State of the Art: Spotlight on OpenAI’s o3
&lt;/h3&gt;

&lt;p&gt;In the quest for AGI, &lt;strong&gt;OpenAI’s o3 model&lt;/strong&gt; is making headlines for scoring &lt;strong&gt;87.5%&lt;/strong&gt; on the &lt;strong&gt;ARC test&lt;/strong&gt;—a benchmark that measures an AI’s ability to handle novel and complex puzzles. This is no small feat, given that the &lt;strong&gt;average human score is around 84%&lt;/strong&gt;. That might be enough to declare a new champion if we were talking about regular IQ tests.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But here’s the catch&lt;/strong&gt;: ARC primarily tests abstract reasoning skills, not the wide-ranging adaptability required for true AGI. While o3 shows remarkable performance within that specific domain, it remains, in essence, a specialized system. It’s like a chess grandmaster who might still struggle to fry an egg without burning down the kitchen. Experts caution that o3 doesn’t quite qualify as “general” intelligence despite this milestone.  &lt;/p&gt;




&lt;h3&gt;
  
  
  Breaking Down the “Thinking Process”: Chain-of-Thought Research
&lt;/h3&gt;

&lt;p&gt;As interesting as these performance milestones is new research on &lt;strong&gt;how&lt;/strong&gt; large language models (LLMs) reason, particularly focusing on &lt;strong&gt;Chain-of-Thought (CoT)&lt;/strong&gt; techniques. In the paper “&lt;a href="https://arxiv.org/abs/2309.12064" rel="noopener noreferrer"&gt;LLMs Do Not Think Step-by-step In Implicit Reasoning&lt;/a&gt;” by Yijiong Yu, researchers discovered that even massive models (like the 72-billion-parameter Qwen2.5-72B-Instruct) often rely on intuition or direct experience, rather than systematically computing intermediate steps, when reasoning implicitly.&lt;/p&gt;

&lt;p&gt;Why does this matter for AGI? Because for truly general intelligence, it’s not enough to stumble onto the correct answer; &lt;strong&gt;how&lt;/strong&gt; the answer is derived is crucial. If the AI takes shortcuts or lacks solid reasoning steps, it’s more prone to making bizarre mistakes. This is one reason experts are experimenting with &lt;strong&gt;explicit CoT&lt;/strong&gt;, which forces models to articulate their intermediate reasoning, leading to more reliable and transparent outputs—albeit at a higher computational cost. Think of it like showing your work in math class: it’s tedious but prevents many errors and guesswork.&lt;/p&gt;




&lt;h3&gt;
  
  
  Scaling Laws and the Bigger Picture
&lt;/h3&gt;

&lt;p&gt;Another pivotal piece of the AGI puzzle is understanding how to scale up models for better performance. The paper “&lt;a href="https://arxiv.org/abs/2001.08361" rel="noopener noreferrer"&gt;Scaling Laws for Autoregressive Generative Modeling&lt;/a&gt;” by Tom Henighan et al. spotlights some key discoveries:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Scaling Laws:&lt;/strong&gt; As the model size and compute power increase, performance follows a predictable power-law relationship—bigger models generally perform better.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimal Model Size:&lt;/strong&gt; There’s a sweet spot between model size and available compute resources, and it’s strikingly similar across domains like images, video, text, and even math problems.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Information-Theoretic Lens:&lt;/strong&gt; By viewing cross-entropy loss as a combination of data entropy and KL divergence, the study lays a framework for quantifying data complexity and model inefficiency.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain-Specific Insights:&lt;/strong&gt; From image modeling and multimodal tasks to mathematical problem solving and image classification, the benefits of scaling hold up—even if tasks differ wildly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In short, “&lt;strong&gt;scaling laws&lt;/strong&gt;” assure us that if we keep throwing resources (and money) at bigger models, we’ll continue to see breakthroughs. However, scaling alone doesn’t guarantee &lt;strong&gt;general&lt;/strong&gt; intelligence; it’s just one piece in a very complex puzzle.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;So, are we there yet? &lt;strong&gt;No&lt;/strong&gt;—but we’re inching closer. The journey toward AGI is a marathon, and while we’ve seen some thrilling sprints (like OpenAI’s o3 beating human averages on ARC), we still have a ways to go. Researchers are grappling with how AI models reason, how to make them more transparent, and how to ensure that bigger is better when scaling.&lt;/p&gt;

&lt;p&gt;AGI might remain on the horizon for now, but these advancements—strong test performances, nuanced research into chain-of-thought, and robust scaling laws—show that the finish line is at least coming into view. In the meantime, we can celebrate each milestone and keep asking, &lt;em&gt;“AGI, are we there yet?”&lt;/em&gt; And maybe, just maybe, we’ll get an AI that can whip up a perfect omelet while explaining quantum mechanics before you know it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why Use AWS App Runner?</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Wed, 11 Sep 2024 19:37:50 +0000</pubDate>
      <link>https://forem.com/ernestohs/why-use-aws-app-runner-218e</link>
      <guid>https://forem.com/ernestohs/why-use-aws-app-runner-218e</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why Use AWS App Runner?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In today’s fast-paced tech environment, developers and DevOps teams are constantly looking for ways to streamline application deployment and reduce the complexity of managing infrastructure. AWS offers a wide array of services to help achieve this, but with so many options, it can be challenging to decide which one is best for your needs. AWS App Runner is a relatively new service designed to simplify the deployment of containerized and web applications. But what makes it stand out, and why should you consider it over other options like Elastic Beanstalk, AWS Lambda, or ECS?&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Introduction: The Struggle with Infrastructure Complexity
&lt;/h3&gt;

&lt;p&gt;If you're feeling overwhelmed by the plethora of AWS services available for application deployment, you're not alone. Developers and DevOps professionals often face the frustration of managing complex infrastructures, ensuring scalability, and constantly monitoring their services. AWS App Runner aims to eliminate these pain points by offering a managed service that simplifies the deployment process while handling the infrastructure for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. What is AWS App Runner?
&lt;/h3&gt;

&lt;p&gt;AWS App Runner is a fully managed service that allows you to build, deploy, and run containerized applications and web services at scale without worrying about the underlying infrastructure. It supports deploying applications directly from a container registry like ECR or a GitHub repository, making it incredibly easy to set up and manage.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Features:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ease of Use&lt;/strong&gt;: With App Runner, you don’t need to manage infrastructure. AWS automatically provisions, scales, and monitors your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling&lt;/strong&gt;: Your application automatically scales based on incoming traffic, and it can scale down to zero when there’s no traffic, saving costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment Automation&lt;/strong&gt;: App Runner integrates easily with CI/CD pipelines, enabling automatic deployments when new code or container images are pushed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. How AWS App Runner Simplifies App Deployment
&lt;/h3&gt;

&lt;p&gt;App Runner removes much of the complexity associated with traditional infrastructure management. For example, when deploying an application with AWS ECS, you need to configure task definitions, clusters, load balancers, autoscaling policies, and VPC configurations. With App Runner, you can skip all of these steps. The service automatically provisions these resources and manages them for you. &lt;/p&gt;

&lt;p&gt;Additionally, App Runner offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Built-in Load Balancing&lt;/strong&gt;: No need to manually configure ALB/NLB for your web applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Monitoring and Auto Recovery&lt;/strong&gt;: App Runner ensures your app stays healthy and automatically recovers it if something goes wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small teams or startups without a dedicated DevOps team, this can be a game changer.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. AWS App Runner vs. Other AWS Services
&lt;/h3&gt;

&lt;p&gt;When deciding between AWS App Runner and other AWS services like Elastic Beanstalk, AWS Lambda, or ECS, it’s important to understand the trade-offs.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;AWS App Runner vs. ECS/Fargate&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App Runner&lt;/strong&gt;: Great for simple, containerized apps with minimal operational overhead. AWS handles the scaling and infrastructure, making it easy for teams with limited AWS expertise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECS/Fargate&lt;/strong&gt;: Offers more control and flexibility, especially for complex, multi-container applications. However, it requires more expertise and effort to set up and manage infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;AWS App Runner vs. AWS Lambda&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App Runner&lt;/strong&gt;: Ideal for containerized applications and microservices with stateful components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt;: Perfect for event-driven, stateless applications. It’s also serverless but doesn’t handle long-running processes or stateful applications as well as App Runner.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;AWS App Runner vs. Elastic Beanstalk&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App Runner&lt;/strong&gt;: More specialized for containerized apps, with simplified scaling and deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elastic Beanstalk&lt;/strong&gt;: Better for applications that need a more customized environment, supporting multiple programming languages and frameworks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. The Benefits of AWS App Runner
&lt;/h3&gt;

&lt;p&gt;Here are the key benefits of AWS App Runner:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: App Runner abstracts the complexity of infrastructure management, enabling developers to focus on coding and building features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Scaling&lt;/strong&gt;: The service scales your application based on demand, and scales to zero when there’s no traffic, reducing unnecessary costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-Efficiency&lt;/strong&gt;: You only pay for the compute and memory your app uses, with no additional costs for load balancers or infrastructure management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: App Runner provides built-in security features, including automatic SSL certificates and integrations with IAM roles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Potential Drawbacks and Limitations
&lt;/h3&gt;

&lt;p&gt;While AWS App Runner is a powerful service, it does come with some limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited Control&lt;/strong&gt;: Compared to ECS or EKS, App Runner gives you less control over the underlying infrastructure. This might not be ideal for applications that require custom networking configurations or advanced scaling policies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost for High-Traffic Applications&lt;/strong&gt;: App Runner’s simplicity might come with a higher price tag for large-scale applications that require optimized resource usage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Is AWS App Runner Right for You?
&lt;/h3&gt;

&lt;p&gt;If you're a small team, startup, or business looking for a simple, scalable, and low-maintenance solution for deploying containerized applications, AWS App Runner is an excellent choice. It allows you to focus on your application without worrying about the underlying infrastructure.&lt;/p&gt;

&lt;p&gt;However, if you have more complex needs, such as custom scaling, service meshes, or advanced VPC configurations, ECS or EKS might be a better fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Example: Deploying a Simple Containerized Application with AWS App Runner
&lt;/h3&gt;

&lt;p&gt;Let’s walk through a step-by-step example of how to deploy a simple containerized web application using &lt;strong&gt;AWS App Runner&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Prerequisites:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You should have an AWS account.&lt;/li&gt;
&lt;li&gt;AWS CLI should be installed and configured with appropriate permissions.&lt;/li&gt;
&lt;li&gt;Docker installed on your local machine.&lt;/li&gt;
&lt;li&gt;A code repository (like GitHub) or a container image stored in AWS Elastic Container Registry (ECR).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Simple Web Application
&lt;/h3&gt;

&lt;p&gt;For this example, we will create a simple &lt;strong&gt;Node.js&lt;/strong&gt; web application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello from AWS App Runner!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`App is running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; to containerize this application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use the official Node.js image from Docker Hub&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:14&lt;/span&gt;

&lt;span class="c"&gt;# Create and set the working directory&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="c"&gt;# Copy the package.json and install dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Copy the source code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Expose the application's port&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="c"&gt;# Start the application&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "app.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Build and Push the Docker Image to ECR
&lt;/h3&gt;

&lt;p&gt;You need to push the Docker image of your application to &lt;strong&gt;AWS Elastic Container Registry (ECR)&lt;/strong&gt;. Let’s create a repository and push the image:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a repository in ECR:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; my-app-runner-repo &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Login to ECR:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2 | docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &amp;lt;AWS_ACCOUNT_ID&amp;gt;.dkr.ecr.us-west-2.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build and tag your Docker image:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-app-runner-app &lt;span class="nb"&gt;.&lt;/span&gt;
docker tag my-app-runner-app:latest &amp;lt;AWS_ACCOUNT_ID&amp;gt;.dkr.ecr.us-west-2.amazonaws.com/my-app-runner-repo:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Push the image to ECR:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push &amp;lt;AWS_ACCOUNT_ID&amp;gt;.dkr.ecr.us-west-2.amazonaws.com/my-app-runner-repo:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Deploy the Application with AWS App Runner
&lt;/h3&gt;

&lt;p&gt;Now, let’s deploy the container using &lt;strong&gt;AWS App Runner&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create an App Runner service&lt;/strong&gt; using the AWS CLI:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws apprunner create-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-name&lt;/span&gt; my-app-runner-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-configuration&lt;/span&gt; &lt;span class="nv"&gt;ImageRepository&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nv"&gt;ImageIdentifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;AWS_ACCOUNT_ID&amp;gt;.dkr.ecr.us-west-2.amazonaws.com/my-app-runner-repo:latest,ImageConfiguration&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nv"&gt;Port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000&lt;span class="o"&gt;}&lt;/span&gt;,ImageRepositoryType&lt;span class="o"&gt;=&lt;/span&gt;ECR&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specifies the service name (&lt;code&gt;my-app-runner-service&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Points to the Docker image stored in your ECR repository.&lt;/li&gt;
&lt;li&gt;Defines the port on which your application will run (&lt;code&gt;Port=3000&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Verify your deployment:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can check the status of your App Runner service by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws apprunner describe-service &lt;span class="nt"&gt;--service-arn&lt;/span&gt; &amp;lt;SERVICE_ARN&amp;gt; &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the status shows as &lt;code&gt;ACTIVE&lt;/code&gt;, AWS App Runner will automatically handle the scaling, load balancing, and monitoring of your containerized app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Access Your Application
&lt;/h3&gt;

&lt;p&gt;Once deployed, AWS App Runner assigns a unique URL to your application. You can find this in the output of the &lt;code&gt;describe-service&lt;/code&gt; command or from the AWS Management Console. Simply open the URL in your browser to access your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://&amp;lt;your-app-runner-url&amp;gt;.awsapprunner.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello from AWS App Runner!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Continuous Deployment (Optional)
&lt;/h3&gt;

&lt;p&gt;To enable continuous deployment, you can integrate App Runner with your CI/CD pipeline or directly from GitHub or GitLab. This ensures that any code changes are automatically built, containerized, and deployed without manual intervention.&lt;/p&gt;

&lt;p&gt;To set this up via GitHub, modify the &lt;code&gt;create-service&lt;/code&gt; command to use a GitHub repository instead of an ECR image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws apprunner create-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-name&lt;/span&gt; my-app-runner-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-configuration&lt;/span&gt; &lt;span class="nv"&gt;CodeRepository&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nv"&gt;RepositoryUrl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://github.com/username/my-app-repo,SourceCodeVersion&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;BRANCH,Value&lt;span class="o"&gt;=&lt;/span&gt;main&lt;span class="o"&gt;}&lt;/span&gt;,CodeConfiguration&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nv"&gt;ConfigurationSource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;API,CodeConfigurationValues&lt;span class="o"&gt;={&lt;/span&gt;&lt;span class="nv"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NODEJS_14,Port&lt;span class="o"&gt;=&lt;/span&gt;3000&lt;span class="o"&gt;}}}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will automatically deploy the code from the &lt;code&gt;main&lt;/code&gt; branch of your GitHub repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Conclusion
&lt;/h3&gt;

&lt;p&gt;AWS App Runner offers a simplified, scalable solution for deploying containerized applications without the overhead of managing infrastructure. It's ideal for teams that want to focus on development and minimize operational complexity. However, for those needing more control and flexibility, other AWS services like ECS or EKS might be more suitable.&lt;/p&gt;

&lt;p&gt;If you’re looking for a managed service that streamlines deployment while offering scalability, security, and ease of use, AWS App Runner is worth exploring.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>startup</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Book Review: "A Philosophy of Software Design" by John Ousterhout – A Masterclass in Crafting Maintainable Code</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Wed, 11 Sep 2024 19:33:13 +0000</pubDate>
      <link>https://forem.com/ernestohs/book-review-a-philosophy-of-software-design-by-john-ousterhout-4dnh</link>
      <guid>https://forem.com/ernestohs/book-review-a-philosophy-of-software-design-by-john-ousterhout-4dnh</guid>
      <description>&lt;h1&gt;
  
  
  Book Review: "A Philosophy of Software Design" by John Ousterhout – A Masterclass in Crafting Maintainable Code
&lt;/h1&gt;




&lt;p&gt;John Ousterhout’s &lt;em&gt;A Philosophy of Software Design&lt;/em&gt; isn’t just another programming book. It’s a rallying cry for developers to stop chasing quick fixes and start building software that stands the test of time. In this review, we’ll unpack Ousterhout’s groundbreaking ideas—from &lt;strong&gt;“deep modules”&lt;/strong&gt; to slaying the dragon of &lt;strong&gt;complexity&lt;/strong&gt;—and explore why this book might change how you write code forever.  &lt;/p&gt;




&lt;h3&gt;
  
  
  The Big Idea: Software Design Isn’t Optional
&lt;/h3&gt;

&lt;p&gt;Let’s cut to the chase: Ousterhout argues that most software projects fail not because of bad code but because of bad &lt;em&gt;design&lt;/em&gt;. The book flips the script on conventional coding wisdom, making a compelling case that great software isn’t just about making it work—it’s about making it &lt;strong&gt;last&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Whether you’re a junior developer or a battle-scarred architect, Ousterhout serves up equal parts theory and hard-won practicality. The result? A playbook for building systems that are &lt;strong&gt;maintainable&lt;/strong&gt;, &lt;strong&gt;adaptable&lt;/strong&gt;, and—dare we say—&lt;em&gt;enjoyable&lt;/em&gt; to work with.  &lt;/p&gt;




&lt;h3&gt;
  
  
  Deep Modules: Where Simplicity Meets Power
&lt;/h3&gt;

&lt;p&gt;Imagine a magic black box. You feed it simple inputs, and it delivers incredible results—no need to understand the 10,000 gears whirring inside. That’s Ousterhout’s vision of a &lt;strong&gt;deep module&lt;/strong&gt;, the crown jewel of his design philosophy.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Deep modules&lt;/strong&gt; = Simple interfaces masking complex functionality
&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Shallow modules&lt;/strong&gt; = Fancy interfaces doing almost nothing
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;“Most frameworks get this backwards,”&lt;/em&gt; Ousterhout observes. He challenges developers to create modules that act like icebergs—&lt;strong&gt;90% hidden complexity&lt;/strong&gt; beneath a sleek, minimal interface. The payoff? Codebases where changes don’t trigger domino effects of bugs.  &lt;/p&gt;




&lt;h3&gt;
  
  
  The Myth of “Working Code”
&lt;/h3&gt;

&lt;p&gt;Here’s the uncomfortable truth: &lt;em&gt;Your code works. Now what?&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;Ousterhout drops a truth bomb: &lt;strong&gt;“Working code is just the starting line.”&lt;/strong&gt; The real race is against &lt;strong&gt;complexity&lt;/strong&gt;—the silent killer lurking in every codebase. Through war stories from his Stanford courses and industry experience, he shows how:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tactical programming&lt;/strong&gt; (code-first/think-later) creates &lt;em&gt;“landmines for future you”&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategic programming&lt;/strong&gt; (design-first) saves 10x the effort down the line
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity compounds&lt;/strong&gt;—bad design decisions today become existential crises tomorrow
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Declaring War on Complexity
&lt;/h3&gt;

&lt;p&gt;Ousterhout identifies complexity as public enemy #1. His battle plan?  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kill shallow modules&lt;/strong&gt; (they’re complexity in disguise)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embrace encapsulation&lt;/strong&gt; like your code’s life depends on it (because it does)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose abstractions&lt;/strong&gt; that map to real-world concepts
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The book’s pièce de résistance? A ruthless framework for spotting and eliminating complexity hotspots. &lt;em&gt;Pro tip:&lt;/em&gt; If your documentation is longer than your code, you’re probably doing it wrong.  &lt;/p&gt;




&lt;h3&gt;
  
  
  From Theory to Trenches
&lt;/h3&gt;

&lt;p&gt;Don’t mistake this for academic fluff. Ousterhout arms readers with:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔍 &lt;strong&gt;Red flag checklists&lt;/strong&gt; for spotting design rot
&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;Refactoring playbooks&lt;/strong&gt; for legacy code rescues
&lt;/li&gt;
&lt;li&gt;🧩 &lt;strong&gt;API design patterns&lt;/strong&gt; that prevent &lt;em&gt;“dependency hell”&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-world examples hit hard, like his takedown of Java’s &lt;code&gt;InputStream&lt;/code&gt; class (&lt;em&gt;Spoiler:&lt;/em&gt; It’s the poster child for shallow modules gone wrong).  &lt;/p&gt;




&lt;h3&gt;
  
  
  Code in Action: Ousterhout’s Principles at Work
&lt;/h3&gt;

&lt;p&gt;Let’s make this concrete. Here’s how Ousterhout’s design philosophy translates to real code (language-agnostic examples):  &lt;/p&gt;




&lt;h4&gt;
  
  
  1. &lt;strong&gt;Deep Module Showdown&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Principle: "Modules should be deep—lots of functionality behind simple interfaces."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Deep Module (Good):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Configuration Loader  
&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_settings.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s hidden:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File I/O&lt;/li&gt;
&lt;li&gt;YAML/JSON/XML parsing&lt;/li&gt;
&lt;li&gt;Environment variable overrides&lt;/li&gt;
&lt;li&gt;Validation rules&lt;/li&gt;
&lt;li&gt;Caching mechanism&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ Shallow Module (Bad):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_settings.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="n"&gt;raw_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
&lt;span class="n"&gt;yaml_parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;YamlParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="n"&gt;config_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml_parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
&lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConfigValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config_dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;  
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config_dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Ousterhout’s verdict:&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Every exposed interface is a liability. The best modules act like LEGO bricks—simple connectors, powerful possibilities."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  2. &lt;strong&gt;Complexity Slayer&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Principle: "Complexity is incremental—attack it early."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (Complex):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportGenerator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
  &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="c1"&gt;// 200 lines mixing:  &lt;/span&gt;
    &lt;span class="c1"&gt;// - Database queries  &lt;/span&gt;
    &lt;span class="c1"&gt;// - CSV/PDF formatting  &lt;/span&gt;
    &lt;span class="c1"&gt;// - Error handling  &lt;/span&gt;
    &lt;span class="c1"&gt;// - Email delivery  &lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (Simple):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Strategic decomposition  &lt;/span&gt;
&lt;span class="nx"&gt;dataClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReportDataClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="nx"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReportFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PDF&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="nx"&gt;deliveryService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EmailDeliveryService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SMTP_CONFIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="nx"&gt;formattedReport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="nx"&gt;deliveryService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formattedReport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Why it works:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Each class becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testable in isolation&lt;/li&gt;
&lt;li&gt;Reusable across projects&lt;/li&gt;
&lt;li&gt;Resilient to format/delivery changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  3. &lt;strong&gt;Tactical vs. Strategic&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Principle: "Strategic programming requires delaying gratification."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tactical Trap (Short-Term Win):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentProcessor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
    &lt;span class="c1"&gt;// Hardcoded tax rate  &lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 🚨 What if tax rules change?  &lt;/span&gt;
    &lt;span class="c1"&gt;// ...  &lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Strategic Solution (Long-Term Gain):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaxCalculator&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;TaxStrategy&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TaxCalculator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TaxStrategy&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  
  &lt;span class="o"&gt;}&lt;/span&gt;  

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  
  &lt;span class="o"&gt;}&lt;/span&gt;  
&lt;span class="o"&gt;}&lt;/span&gt;  

&lt;span class="c1"&gt;// Implementations:  &lt;/span&gt;
&lt;span class="c1"&gt;// - LocalSalesTaxStrategy()  &lt;/span&gt;
&lt;span class="c1"&gt;// - VATStrategy()  &lt;/span&gt;
&lt;span class="c1"&gt;// - ZeroTaxStrategy()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Ousterhout’s wisdom:&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A tactical programmer asks ‘Does it work?’ A strategic programmer asks ‘Will it still work in 2 years?’"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  The Pattern Emerges
&lt;/h4&gt;

&lt;p&gt;These examples embody Ousterhout’s core thesis:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hide the Volcano (Deep Modules)

&lt;ul&gt;
&lt;li&gt;Expose simple endpoints, bury complex magma chambers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Divide and Conquer (Complexity Reduction) 

&lt;ul&gt;
&lt;li&gt;When a class/file makes you scroll vertically and horizontally, split it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Code for the Next Developer (Strategic Thinking) 

&lt;ul&gt;
&lt;li&gt;Pretend your successor knows where you live.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As Ousterhout quips:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The most dangerous code is the kind that looks like it works."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These patterns turn that danger into deliberate design.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why This Book Matters Now
&lt;/h3&gt;

&lt;p&gt;In an era of &lt;em&gt;“move fast and break things,”&lt;/em&gt; Ousterhout’s philosophy feels radical. He’s not just teaching design—he’s advocating for &lt;strong&gt;software craftsmanship&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Teams that adopt these principles report:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 60% fewer &lt;em&gt;“WTF moments”&lt;/em&gt; during code reviews
&lt;/li&gt;
&lt;li&gt;📈 40% faster onboarding for new developers
&lt;/li&gt;
&lt;li&gt;💥 80% reduction in &lt;em&gt;“fear-driven development”&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The Verdict: Essential Reading for Code Survivors
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;A Philosophy of Software Design&lt;/em&gt; isn’t just a book—it’s a mindset shift. Ousterhout delivers a masterclass in:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building systems that age like fine wine, not milk
&lt;/li&gt;
&lt;li&gt;Writing code your future self won’t curse
&lt;/li&gt;
&lt;li&gt;Transforming complexity from foe to ally
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Who needs this book?&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔥 Developers tired of rewriting &lt;em&gt;“working”&lt;/em&gt; code
&lt;/li&gt;
&lt;li&gt;🧩 Tech leads battling legacy system hydras
&lt;/li&gt;
&lt;li&gt;🚀 Engineers aiming for promotions (clean code gets noticed)
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Final Thought:&lt;/strong&gt; This isn’t just about writing better software. It’s about reclaiming the joy of programming. Because when you slay complexity and embrace deep design, coding stops feeling like combat—and starts feeling like creation.  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ready to level up? &lt;a href="https://a.co/d/ady0CG8" rel="noopener noreferrer"&gt;This book’s your ticket&lt;/a&gt;.&lt;/em&gt; 🚀  &lt;/p&gt;

</description>
      <category>books</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Balancing High-Quality Code with Rapid Development</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Thu, 11 Jan 2024 01:47:26 +0000</pubDate>
      <link>https://forem.com/ernestohs/balancing-high-quality-code-with-rapid-development-1kip</link>
      <guid>https://forem.com/ernestohs/balancing-high-quality-code-with-rapid-development-1kip</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the fast-paced world of software development, the balance between writing high-quality code and meeting rapid development schedules is a constant challenge. As a Software Architect, I've navigated this balance through various projects and want to share insights and strategies that can help fellow developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Importance of High-Quality Code
&lt;/h2&gt;

&lt;p&gt;High-quality code is clean, understandable, maintainable, and testable. It ensures long-term project health, reduces technical debt, and minimizes bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Components of High-Quality Code:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Readability&lt;/strong&gt;: Code should be easy to read and understand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularity&lt;/strong&gt;: Breaking down code into logical, reusable components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt;: Comprehensive tests to validate code against expected outcomes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: Well-documented code to aid future development efforts.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Need for Rapid Development
&lt;/h2&gt;

&lt;p&gt;The tech industry is highly competitive, with constant pressure to release new features and updates. Rapid development helps in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Staying ahead in the market.&lt;/li&gt;
&lt;li&gt;Quickly responding to user needs.&lt;/li&gt;
&lt;li&gt;Iterating and improving products efficiently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Strategies for Balancing Quality and Speed
&lt;/h2&gt;

&lt;p&gt;Balancing these two aspects isn't straightforward and requires a strategic approach:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Implement Agile Methodologies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Iterative Development&lt;/strong&gt;: Break down projects into smaller, manageable iterations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous Integration/Continuous Deployment (CI/CD)&lt;/strong&gt;: Automate testing and deployment to streamline the development process.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Code Reviews and Pair Programming
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Peer Reviews&lt;/strong&gt;: Encourage learning and catching errors early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pair Programming&lt;/strong&gt;: Combines two perspectives, reducing the likelihood of mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Emphasize on Automated Testing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests&lt;/strong&gt;: Ensure individual components perform as expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Tests&lt;/strong&gt;: Check if different parts of the application work together correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-End Tests&lt;/strong&gt;: Validate the workflow of the application as a whole.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Refactoring and Technical Debt Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Regularly refactor code to maintain quality.&lt;/li&gt;
&lt;li&gt;Prioritize technical debt to prevent it from accumulating.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Invest in Developer Training
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Continuous learning keeps developers updated with best practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Leverage Advanced Tools and Frameworks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Utilize tools that increase development speed without compromising on quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Balancing high-quality code with rapid development is achievable with the right strategies and mindset. It requires a culture that values both quality and efficiency, supported by practices that integrate these values into the development lifecycle. Remember, in software development, speed should never compromise quality.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding Webhooks: The Slack Notification System</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Wed, 10 Jan 2024 02:20:56 +0000</pubDate>
      <link>https://forem.com/ernestohs/understanding-webhooks-the-slack-notification-system-4d15</link>
      <guid>https://forem.com/ernestohs/understanding-webhooks-the-slack-notification-system-4d15</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In today's fast-paced digital world, efficient communication and real-time updates are crucial for effective collaboration. This is where the concept of webhooks becomes invaluable, especially in popular collaboration tools like Slack. This blog post aims to demystify webhooks, using Slack’s notification system as a practical example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are Webhooks?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At its core, a webhook is a user-defined HTTP callback. It's a mechanism where an app provides other applications with real-time information. A webhook delivers data to other applications as it happens, meaning you get data immediately. Unlike typical APIs where you need to poll for data frequently, webhooks are more efficient for both the provider and consumer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Do Webhooks Work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine a scenario where an application needs to know when a specific event happens in real time. Traditionally, this would involve regularly checking (polling) the service to see if the event has occurred. This process is not only inefficient but can also lead to delays in response. Webhooks solve this by acting like a phone call to notify when specific events occur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhooks in Action: Slack Notifications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Slack, a popular team collaboration tool, uses webhooks extensively for its notification system. Let's break down how this works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Setting up a Webhook in Slack&lt;/strong&gt;: Users or administrators can create a 'webhook URL' through Slack's interface. This URL is unique to a Slack channel or workspace.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event Trigger&lt;/strong&gt;: An event occurs in an external service that needs to be communicated to Slack users. This could be anything from a new customer ticket, a source code push, or a sales lead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sending Data to Slack&lt;/strong&gt;: The external service makes an HTTP request to the webhook URL provided by Slack. This request includes the data or information about the event.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Slack Processes the Request&lt;/strong&gt;: Upon receiving the HTTP request, Slack processes the data and converts it into a message format suitable for the channel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Notification Delivery&lt;/strong&gt;: The message is then posted to the specified Slack channel, instantly notifying team members.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Advantages of Using Webhooks&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time Data Transmission&lt;/strong&gt;: Webhooks provide immediate information, making them ideal for time-sensitive operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficiency&lt;/strong&gt;: They eliminate the need for constant polling, reducing the load on both the server and the network.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Customization and Flexibility&lt;/strong&gt;: Webhooks can be customized to transmit specific information based on the event, making them versatile.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Webhooks offer a powerful way to integrate different software and services, providing real-time updates and efficient data transmission. Slack's use of webhooks for notifications is a prime example of how this technology can enhance communication and productivity in a collaborative environment.&lt;/p&gt;

&lt;p&gt;By embracing webhooks, developers can build more interconnected and responsive applications, fostering a more dynamic and efficient digital ecosystem.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Web Workers API</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Wed, 10 Jan 2024 02:15:22 +0000</pubDate>
      <link>https://forem.com/ernestohs/web-workers-api-2mj3</link>
      <guid>https://forem.com/ernestohs/web-workers-api-2mj3</guid>
      <description>&lt;p&gt;The Web Workers API, as detailed on &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API" rel="noopener noreferrer"&gt;Mozilla Developer Network (MDN)&lt;/a&gt;, offers a way for web content to execute scripts in background threads, separate from the main execution thread of a web application. This separation allows for more intensive processing without blocking or slowing down the user interface thread. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Types of Web Workers:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated Workers:&lt;/strong&gt; These are utilized by a single script and operate within a &lt;code&gt;DedicatedWorkerGlobalScope&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Workers:&lt;/strong&gt; These can be used by multiple scripts, even if they run in different windows, IFrames, or workers, as long as they are in the same domain. They operate within a &lt;code&gt;SharedWorkerGlobalScope&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Workers:&lt;/strong&gt; These act like proxy servers between web applications, the browser, and the network. They are designed to enable offline experiences, intercept network requests, update server assets, and provide access to push notifications and background sync APIs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Key Features and Functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Communication with Workers:&lt;/strong&gt; Data is exchanged between workers and the main thread through a system of messages using the &lt;code&gt;postMessage()&lt;/code&gt; method and the &lt;code&gt;onmessage&lt;/code&gt; event handler. The data is copied, not shared, between them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling:&lt;/strong&gt; Workers have an &lt;code&gt;onerror&lt;/code&gt; event handler for runtime errors, providing details like the error message, filename, and line number where the error occurred.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subworkers:&lt;/strong&gt; Workers can spawn new workers (sub-workers), as long as these sub-workers are hosted within the same origin as the parent page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Script Importing:&lt;/strong&gt; Workers use &lt;code&gt;importScripts()&lt;/code&gt; to import scripts, which are executed synchronously and in the order they are passed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Global Contexts and Functions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workers run in a different global context than the &lt;code&gt;window&lt;/code&gt; and have access to functions like &lt;code&gt;atob()&lt;/code&gt;, &lt;code&gt;btoa()&lt;/code&gt;, &lt;code&gt;fetch()&lt;/code&gt;, and &lt;code&gt;setInterval()&lt;/code&gt; through their own &lt;code&gt;WorkerGlobalScope&lt;/code&gt;-derived contexts.&lt;/li&gt;
&lt;li&gt;However, workers cannot directly manipulate the DOM or use some default methods and properties of the &lt;code&gt;Window&lt;/code&gt; object.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Supported Web APIs in Workers:&lt;/strong&gt;&lt;br&gt;
Workers have access to various Web APIs, including but not limited to the Canvas API, Fetch API, File API, IndexedDB API, and WebSockets API. It's important to check browser compatibility for specific worker types and functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constructing and Using Workers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To create a dedicated worker, the &lt;code&gt;Worker("path/to/worker/script")&lt;/code&gt; constructor is used. For a shared worker, &lt;code&gt;SharedWorker("scriptURL")&lt;/code&gt; is employed.&lt;/li&gt;
&lt;li&gt;These workers handle messages and errors through event handlers and methods like &lt;code&gt;postMessage()&lt;/code&gt; and &lt;code&gt;terminate()&lt;/code&gt;. The shared worker also involves a &lt;code&gt;port&lt;/code&gt; object for communication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Shared Worker Specifics:&lt;/strong&gt;&lt;br&gt;
Shared workers differ from dedicated workers in their ability to be accessed from several browsing contexts. They require the use of a &lt;code&gt;MessagePort&lt;/code&gt; object for communication, and the worker's lifetime is tied to its global scope's owner set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thread Safety and Security:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web workers spawn real OS-level threads but are designed to avoid typical concurrency issues as they have controlled communication points and no access to non-threadsafe components or the DOM.&lt;/li&gt;
&lt;li&gt;Workers have their own execution context and are generally not governed by the content security policy of the document that created them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Web Workers API is a powerful tool for web developers, enabling more efficient and responsive web applications by offloading tasks to background threads. For more detailed information and examples, you can refer directly to the MDN Web Docs on &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;Using Web Workers&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API" rel="noopener noreferrer"&gt;Web Workers API&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker" rel="noopener noreferrer"&gt;Worker&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker" rel="noopener noreferrer"&gt;SharedWorker&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
