<?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: John Franey</title>
    <description>The latest articles on Forem by John Franey (@johnfraney).</description>
    <link>https://forem.com/johnfraney</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%2F1048774%2Fd46a76ef-7b4b-4851-a90e-fb3abefb1c6b.png</url>
      <title>Forem: John Franey</title>
      <link>https://forem.com/johnfraney</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/johnfraney"/>
    <language>en</language>
    <item>
      <title>How to write user stories that actually get done</title>
      <dc:creator>John Franey</dc:creator>
      <pubDate>Sun, 07 Dec 2025 15:51:56 +0000</pubDate>
      <link>https://forem.com/johnfraney/how-to-write-user-stories-that-actually-get-done-4eo8</link>
      <guid>https://forem.com/johnfraney/how-to-write-user-stories-that-actually-get-done-4eo8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6epihwx843sxoiolkjj7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6epihwx843sxoiolkjj7.webp" alt="Mr. Bean closing a book" width="498" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;At each stop in my career, I seem to develop a reputation for being a Fussy Franey with user stories.&lt;br&gt;
I've seen some eye rolls when I've asked for one story to be broken into three, and I've faced incredulity when pointing out that one "user story" is actually an entire epic---or even so broad in scope that it could be an entirely separate business.&lt;/p&gt;

&lt;p&gt;I like to get things done, and I've found that small, well-written user stories are the single biggest enabler of dev velocity.&lt;/p&gt;

&lt;h2&gt;
  
  
  User stories should be small
&lt;/h2&gt;

&lt;p&gt;Very small.&lt;br&gt;
How small?&lt;/p&gt;

&lt;p&gt;They should encompass a &lt;em&gt;single&lt;/em&gt; user interaction, single visual element, or single application reaction.&lt;/p&gt;

&lt;p&gt;Reducing scope for a feature or launch should not require rewriting user stories&lt;/p&gt;

&lt;h3&gt;
  
  
  What counts as a user interaction?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Viewing something, like a list of contacts&lt;/li&gt;
&lt;li&gt;Navigating to a page&lt;/li&gt;
&lt;li&gt;Entering a value in a form field&lt;/li&gt;
&lt;li&gt;Submitting a form

&lt;ul&gt;
&lt;li&gt;This doesn't include form submission accoutrements, like showing a loading icon (separate story), showing an error message (separate story), or showing a success message (separate story)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  What counts as a visual element?
&lt;/h3&gt;

&lt;p&gt;Viewing is an action, and seeing a visual/UI element counts as a user story.&lt;br&gt;
Visual elements can be tiny, too, like a red notification dot you might see on a new menu link.&lt;/p&gt;

&lt;p&gt;It might seem like overkill to have an entire user story for a notification dot, but the menu item &lt;em&gt;can&lt;/em&gt; exist without the notification dot.&lt;br&gt;
Because the notification dot depends on the menu item, it can be implemented separately from the menu item and prioritized separately, too.&lt;/p&gt;

&lt;h3&gt;
  
  
  What counts as an application reaction?
&lt;/h3&gt;

&lt;p&gt;An application reaction is a change in the application that happens as a result of a user action or as a result of other application logic.&lt;br&gt;
The reaction could take place within an application (a toast notification) or across applications (sending an SMS).&lt;/p&gt;

&lt;p&gt;Some quick examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validating an input&lt;/li&gt;
&lt;li&gt;Showing a toast&lt;/li&gt;
&lt;li&gt;Showing a loading element&lt;/li&gt;
&lt;li&gt;Showing a logout warning&lt;/li&gt;
&lt;li&gt;Sending a confirmation email&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Q: When is a story not a story?
&lt;/h3&gt;

&lt;p&gt;A: When it's an epic!&lt;/p&gt;

&lt;p&gt;For example, let's take the following story:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a user, I can delete a to-do item&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds like a small, regular story, right?&lt;br&gt;
Not so fast.&lt;/p&gt;

&lt;p&gt;A story like that could have a number of requirements before it could be considered done (optional requirements marked with asterisks):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Showing a confirmation modal when clicking the delete button*&lt;/li&gt;
&lt;li&gt;Writing the API delete endpoint&lt;/li&gt;
&lt;li&gt;Performing the API delete request on click&lt;/li&gt;
&lt;li&gt;Disabling the button while the request is processing*&lt;/li&gt;
&lt;li&gt;Removing the to-do item on a success response&lt;/li&gt;
&lt;li&gt;Showing a success toast*&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's a lot happening there.&lt;br&gt;
There's enough happening here to make deleting a to-do item an &lt;strong&gt;epic&lt;/strong&gt; rather than a single story.&lt;/p&gt;

&lt;p&gt;And what's an epic?&lt;br&gt;
An epic is "a large, high-level piece of work" (&lt;a href="https://resources.scrumalliance.org/Article/epic-agile" rel="noopener noreferrer"&gt;ScrumAlliance&lt;/a&gt;) that consists of multiple user stories and other pieces of work, like non-user-facing dev tasks.&lt;/p&gt;

&lt;p&gt;Atlassian has a helpful explanation of epics as a strategy to break down work into smaller pieces:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Epics are a helpful way to organize your work and to create a hierarchy. The idea is to break work down into shippable pieces so that large projects can actually get done and you can continue to ship value to your customers on a regular basis. Epics help teams break their work down, while continuing to work towards a bigger goal. (Atlassian, &lt;a href="https://www.atlassian.com/agile/project-management/epics" rel="noopener noreferrer"&gt;"Agile epics: definition, examples, and templates"&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, an epic captures an entire feature flow, like the end-to-end (and frontend-to-backend) process of creating a to-do item, and it consists of one or more user stories and/or dev tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;:&lt;br&gt;
"Managing" something should never be a single user story.&lt;br&gt;
If your user story starts, "As a user, I can manage...", it's likely a project, and each of the &lt;a href="https://en.wikipedia.org/wiki/Create%2C_read%2C_update_and_delete" rel="noopener noreferrer"&gt;Create, Read, Update, and Delete&lt;/a&gt; actions may well be its own epic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can stories have multiple acceptance criteria?
&lt;/h3&gt;

&lt;p&gt;They can!&lt;/p&gt;

&lt;p&gt;But they still need to be small, and the acceptance criteria need to be so intimately related that separating them would be impossible to implement or impossible to verify.&lt;br&gt;
So what is a story that can have multiple acceptance criteria?&lt;br&gt;
The second requirement in the epic above is a good example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a user, I can confirm whether I want to delete a to-do item&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This story might have acceptance criteria like the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;GIVEN I am a user viewing a to-do item&lt;/p&gt;

&lt;p&gt;WHEN I click the delete button&lt;/p&gt;

&lt;p&gt;THEN I am shown a confirmation modal&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GIVEN I am a user confirming whether to delete a to-do item&lt;/p&gt;

&lt;p&gt;WHEN I click the close button on the confirmation modal&lt;/p&gt;

&lt;p&gt;THEN the modal is closed AND the to-do item is not deleted&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GIVEN I am a user viewing the delete to-do confirmation modal&lt;/p&gt;

&lt;p&gt;WHEN I confirm my intent to delete the to-do item&lt;/p&gt;

&lt;p&gt;THEN the item is deleted AND the modal is closed&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Showing a confirmation modal (application reaction) when clicking the delete button (user action) encompasses two UI elements and one action+reaction, but still makes sense to have in the same user story because there's no way to validate that the delete button behaves as expected without having a confirmation modal to view.&lt;/p&gt;

&lt;p&gt;The story could be broken into subtasks to parallelize development by creating a subtask for the design of the confirmation modal.&lt;br&gt;
Ideally, the confirmation modal would be reusable and wouldn't have to be implemented separately for each action.&lt;br&gt;
For more on this, see the "User stories should be new" section below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;:&lt;br&gt;
Multiple user stories can be coded in a single branch and merged in a single pull request.&lt;br&gt;
For example, showing a success message on a successful form submission and showing an error message on an unsuccessful one could make sense to code together.&lt;br&gt;
They are different enough that they should be in separate user stories, though, because one could be done without doing the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  User stories should be explicit
&lt;/h2&gt;

&lt;p&gt;A story's definition of "done" should be unambiguous.&lt;/p&gt;

&lt;p&gt;Acceptance criteria should be specific and comprehensive, and it should be described in writing rather than being hidden in designs.&lt;/p&gt;

&lt;p&gt;If a story involves changing text, for example, include the old and new text in the acceptance criteria.&lt;br&gt;
When acceptance criteria read something like "Update wording to match designs", changes are easy to miss, which can throw off estimates and lead to QA rejections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://cucumber.io/docs/gherkin/reference/#steps" rel="noopener noreferrer"&gt;Gherkin syntax&lt;/a&gt; (GIVEN/WHEN/THEN) is helpful to describe a story's scope.&lt;br&gt;
If a story is difficult to describe in the GIVEN/WHEN/THEN format, it's also going to be difficult to code, review, and QA.&lt;/p&gt;

&lt;h2&gt;
  
  
  User stories should be new
&lt;/h2&gt;

&lt;p&gt;Existing capabilities can be acceptance criteria, while new capabilities should be user stories.&lt;/p&gt;

&lt;p&gt;If you have a form component that shows an error message on a 500 response, for example, there doesn't need to be a new user story to use this behaviour in a new form.&lt;br&gt;
For another example, if you have a phone number field and you want to see a phone number input on a mobile device, because this is a built-in capability of an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/tel" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;input type="tel"&amp;gt;&lt;/code&gt;&lt;/a&gt;, it doesn't need to be its own user story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;:&lt;br&gt;
Subtle changes to existing behaviour should be captured in a separate ticket.&lt;br&gt;
It's a good idea to have one or more developers audit acceptance criteria to identify new features that may be masquerading as acceptance criteria.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lightning summary ⚡
&lt;/h2&gt;

&lt;p&gt;While it may seem tedious to have stories with such a narrow scope, large user stories often mask complexity in a project, posing challenges to estimating, sequencing, QAing, and descoping work.&lt;/p&gt;

&lt;p&gt;User stories that actually get done are small, explicit, and new.&lt;/p&gt;

</description>
      <category>agile</category>
      <category>softwaredevelopment</category>
      <category>product</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AWS documentation frustrations</title>
      <dc:creator>John Franey</dc:creator>
      <pubDate>Mon, 04 Sep 2023 14:25:14 +0000</pubDate>
      <link>https://forem.com/johnfraney/aws-documentation-frustrations-385d</link>
      <guid>https://forem.com/johnfraney/aws-documentation-frustrations-385d</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AWS has a ton of documentation, and sometimes it misses the mark.&lt;br&gt;
Recently, I came across two different examples of AWS documentation either perplexing or frustrating me.&lt;/p&gt;

&lt;p&gt;Note:&lt;br&gt;
By "frustrating", I mostly mean it in the "hinder or prevent (the efforts, plans, or desires) of" meaning of the word (&lt;a href="https://www.wordwebonline.com/search.pl?w=frustrating" rel="noopener noreferrer"&gt;WordWeb&lt;/a&gt;).&lt;br&gt;
Mostly.&lt;/p&gt;

&lt;p&gt;Now, I don't want to come across as a complainy Franey, but since I've been working on &lt;a href="https://blurry-docs.netlify.app/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for &lt;a href="https://github.com/blurry-dev/blurry" rel="noopener noreferrer"&gt;Blurry&lt;/a&gt;, a static site generator I open-sourced (and that builds this site), I've been thinking about what makes good technical documentation good.&lt;br&gt;
I don't think I've figured it out &lt;em&gt;quite&lt;/em&gt; yet, but I have found two traits of AWS docs that can hurt documentation, and maybe doing the opposite can improve it.&lt;/p&gt;

&lt;p&gt;Read on for a break-down of two AWS documentation breakdowns and what I learned therefrom.&lt;/p&gt;
&lt;h2&gt;
  
  
  Trait 1: perplexing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjohnfraney.ca%2Fblog%2Fvideos%2Faws-vs-code-extension-installation.mp4" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjohnfraney.ca%2Fblog%2Fvideos%2Faws-vs-code-extension-installation.mp4" alt="Video: Installing the AWS VS Code extension from the Lambda console" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How many steps does it take to get a direct link to the VS Code extension?&lt;/p&gt;

&lt;p&gt;From the Lambda function console page, it took 4 clicks, reading, and scrolling to get to the actual VS Code extension.&lt;br&gt;
Even from the &lt;a href="https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html" rel="noopener noreferrer"&gt;AWS Toolkit for Visual Studio Code&lt;/a&gt; site, it was still two clicks to get to the page with the direct VS Code link (and that link was below-the-fold).&lt;/p&gt;

&lt;p&gt;That's quite a long walk for what ended up being a link that opened the extension in VS Code itself.&lt;br&gt;
Four clicks for something that could be one click?&lt;br&gt;
That's pretty perplexing.&lt;/p&gt;

&lt;p&gt;There's some risk in having that much documentation about a VS Code extension, too.&lt;br&gt;
Having a documentation site separate from the README could mean:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You have to maintain documentation in multiple places, which can be difficult to keep in sync&lt;/li&gt;
&lt;li&gt;If documentation in one of the two places isn't unique enough to have to keep in sync, that means the docs may be general enough to be not &lt;em&gt;that&lt;/em&gt; helpful&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;: I do need to give credit where it's due: an earlier version of the VS Code extension documentation didn't have a link that opened the extension in VS Code; after all that clicking, it asked that we open VS Code and search for the extension.&lt;/p&gt;
&lt;h2&gt;
  
  
  Trait 2: frustrating
&lt;/h2&gt;

&lt;p&gt;It's frustrating when documentation is incomplete---especially in code examples.&lt;/p&gt;

&lt;p&gt;Take this Python code sample for &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_lambda_python_alpha/PythonFunction.html" rel="noopener noreferrer"&gt;AWS CDK's &lt;code&gt;PythonFunction&lt;/code&gt;&lt;/a&gt;, for instance:&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="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DockerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PythonFunction&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PYTHON_3_8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bundling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BundlingOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;build_args&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;PIP_INDEX_URL&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;https://your.index.url/simple/&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;PIP_EXTRA_INDEX_URL&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;https://your.extra-index.url/simple/&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linting the code using Pylint via (&lt;a href="https://pyrfecter.com/" rel="noopener noreferrer"&gt;Pyrfecter&lt;/a&gt;), we can see a number of linting issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2:9: undefined name 'DockerImage'
4:1: undefined name 'python'
4:23: undefined name 'self'
6:13: undefined name 'Runtime'
7:14: undefined name 'python'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple missing imports that were easy to sort out (&lt;code&gt;Runtime&lt;/code&gt;, &lt;code&gt;DockerImage&lt;/code&gt;), but &lt;code&gt;python&lt;/code&gt;?&lt;br&gt;
I couldn't find that in the &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/home.html" rel="noopener noreferrer"&gt;CDK Developer Guide&lt;/a&gt; or the &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_lambda_python_alpha.html" rel="noopener noreferrer"&gt;CDK API documentation&lt;/a&gt; or the &lt;a href="https://github.com/aws-samples/aws-cdk-examples" rel="noopener noreferrer"&gt;AWS CDK Examples&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After getting help from a search engine, I found that the missing &lt;code&gt;python&lt;/code&gt; import is:&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;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_python_alpha&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the PyPI package to install is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws-cdk-aws-lambda-python-alpha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;self&lt;/code&gt;?&lt;br&gt;
The &lt;code&gt;python.PythonFunction&lt;/code&gt; call shoud be inside a CDK &lt;code&gt;Stack&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we check &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/stacks.html" rel="noopener noreferrer"&gt;the documentation for that&lt;/a&gt;, we get an example of an &lt;code&gt;App&lt;/code&gt; and a &lt;code&gt;Construct&lt;/code&gt;, but the &lt;code&gt;Stack&lt;/code&gt; classes are stubbed:&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;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;constructs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;

&lt;span class="c1"&gt;# imagine these stacks declare a bunch of related resources
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ControlPlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataPlane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Monitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Construct&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;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&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;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prod&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;super&lt;/span&gt;&lt;span class="p"&gt;().&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;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# we might use the prod argument to change how the service is configured
&lt;/span&gt;    &lt;span class="nc"&gt;ControlPlane&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;DataPlane&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;Monitoring&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mon&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;beta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prod&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are no linting errors, at least!&lt;/p&gt;

&lt;p&gt;For a code example of a &lt;code&gt;Stack&lt;/code&gt;, I went to the &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/apps.html" rel="noopener noreferrer"&gt;documentation for &lt;code&gt;App&lt;/code&gt;&lt;/a&gt; and found this:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyFirstStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&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;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&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;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&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;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyFirstBucket&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;And the linting output?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:20: undefined name 'Stack'
3:31: undefined name 'Construct'
6:9: undefined name 's3'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get the &lt;code&gt;Stack&lt;/code&gt; and &lt;code&gt;Construct&lt;/code&gt; imports from the code sample in the &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/stacks.html" rel="noopener noreferrer"&gt;Stacks page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So.&lt;br&gt;
After checking the the &lt;code&gt;aws_cdk.aws_lambda_python_alpha&lt;/code&gt; API docs, multiple pages of the AWS CDK Developers Guide, the GitHub examples repo, and some searching to find &lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-lambda-python-alpha-readme.html" rel="noopener noreferrer"&gt;the docs for "@aws-cdk/aws-lambda-python-alpha module"&lt;/a&gt; (which is separate from the CDK API docs), we have enough documentation to use these new (and very helpful!) Python CDK constructs.&lt;/p&gt;

&lt;p&gt;But, boy, did I have to work for it.&lt;br&gt;
I can't remember how much time it took for me to get a complete working example, and I shudder to think of how many dev-hours have been spent in a similar pursuit.&lt;/p&gt;

&lt;p&gt;A complete, working code example would save oodles of time, and I'm happy to oblige:&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;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_dynamodb&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_lambda&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;lambda_&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_event_sources&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;event_sources&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aws_lambda_python_alpha&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;lambda_python&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;aws_cdk.aws_lambda_python_alpha&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PythonFunction&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;constructs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WorkingStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&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;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&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;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&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;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Resources
&lt;/span&gt;        &lt;span class="n"&gt;python_dependencies_layer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lambda_python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PythonLayerVersion&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PoetryLayer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./src/layer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;compatible_runtimes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lambda_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PYTHON_3_10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;dynamodb_table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WorkingTable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;partition_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&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;PK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STRING&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StreamViewType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NEW_IMAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;sort_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&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;SK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;removal_policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;billing_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BillingMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PAY_PER_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Functions
&lt;/span&gt;        &lt;span class="n"&gt;react_to_new_entries_function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PythonFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PythonFunction&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ReactToNewEntries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./src/functions&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;react_to_new_entries.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lambda_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PYTHON_3_10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;python_dependencies_layer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;environment&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;TABLE_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;dynamodb_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Event handling
&lt;/span&gt;        &lt;span class="n"&gt;react_to_new_entries_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_event_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;event_sources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DynamoEventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;dynamodb_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;starting_position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lambda_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartingPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TRIM_HORIZON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;bisect_batch_on_error&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;retry_attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&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;span class="n"&gt;dynamodb_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grant_read_write_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;react_to_new_entries_function&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;Info&lt;/strong&gt;:&lt;br&gt;
I'm working on another post showing a complete AWS CDK project with typed Python.&lt;br&gt;
Stay tuned!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Documentation should yield more answers than questions
&lt;/h3&gt;

&lt;p&gt;If your documentation requires multiple other (possibly unlinked) documentation sources to be helpful, there's a problem.&lt;br&gt;
Code samples, especially, should be complete, to make it easy to get started using your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;: Maintaining complete and accurate code samples can be hard to maintain and verify, which is one of the reasons I once made &lt;a href="https://github.com/johnfraney/flake8-markdown" rel="noopener noreferrer"&gt;&lt;code&gt;flake8-markdown&lt;/code&gt;&lt;/a&gt;, a little package to run &lt;code&gt;flake8&lt;/code&gt; on Python code blocks embedded in Markdown files.&lt;/p&gt;

&lt;p&gt;(I should probably be using it on &lt;a href="https://johnfraney.ca" rel="noopener noreferrer"&gt;johnfraney.ca&lt;/a&gt;, now that I think of it.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation should anticipate the information a reader is most likely looking for and make that easy to find
&lt;/h3&gt;

&lt;p&gt;If someone clicks a link about a VS Code extension, make the call-to-action to download that extension dead-simple to find.&lt;br&gt;
Additional information can be helpful, too, especially for someone learning a new skill or concept, but make it easy for someone who has a good idea what they're looking for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Last &lt;del&gt;rites&lt;/del&gt; &lt;del&gt;writes&lt;/del&gt; words
&lt;/h3&gt;

&lt;p&gt;Writing documentation is hard.&lt;br&gt;
And it's easy to pick on AWS, not only because they have &lt;em&gt;so much&lt;/em&gt; documentation, but also because they have the resources to maintain great docs.&lt;/p&gt;

&lt;p&gt;But reading documentation is much easier than writing it, and complaining about documentation is much easier than fixing it.&lt;br&gt;
So keep an eye out for things that make documentation great and grody, send up PRs, and write some code...y.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>documentation</category>
      <category>python</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Show: Responsive Image Generator - multiple WEBPs and responsive HTML from a single image</title>
      <dc:creator>John Franey</dc:creator>
      <pubDate>Sun, 26 Mar 2023 22:33:49 +0000</pubDate>
      <link>https://forem.com/johnfraney/show-responsive-image-generator-multiple-webps-and-responsive-html-from-a-single-image-50ld</link>
      <guid>https://forem.com/johnfraney/show-responsive-image-generator-multiple-webps-and-responsive-html-from-a-single-image-50ld</guid>
      <description>&lt;p&gt;Hello! Today I made public a little tool I've been working on that makes it easy to add a responsive image to your website. &lt;/p&gt;

&lt;p&gt;It uses Imagemagick via Web Assembly to generate multiple resized &amp;amp; converted-to-WEBP images from a single image you upload.&lt;/p&gt;

&lt;p&gt;The styling might leave a fair bit to be desired, but I wanted to get this into the world as soon as it worked. I hope someone finds it useful!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://johnfraney.ca/tools/responsive-image-generator/" rel="noopener noreferrer"&gt;https://johnfraney.ca/tools/responsive-image-generator/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>showdev</category>
      <category>webassembly</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
