<?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: Adam McKerlie</title>
    <description>The latest articles on Forem by Adam McKerlie (@adammckerlie).</description>
    <link>https://forem.com/adammckerlie</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%2F33733%2F9414fac5-2dfb-4670-8206-fec3e6edfff2.png</url>
      <title>Forem: Adam McKerlie</title>
      <link>https://forem.com/adammckerlie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/adammckerlie"/>
    <language>en</language>
    <item>
      <title>Improving the sharing experience in Astro</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Thu, 02 Nov 2023 10:00:00 +0000</pubDate>
      <link>https://forem.com/adammckerlie/improving-the-sharing-experience-in-astro-bo9</link>
      <guid>https://forem.com/adammckerlie/improving-the-sharing-experience-in-astro-bo9</guid>
      <description>&lt;p&gt;A couple months ago I migrated my blog from Hugo to Astro because I was more familiar with the underlying language (Javascript) and I wanted to learn this relatively new framework. Overall, the process was extremely easy but I was missing something that came included previously, sharing links to allow people to share my blog posts on social media. That's where &lt;a href="https://www.npmjs.com/package/astro-social-share"&gt;Astro Social Share&lt;/a&gt; comes in, a fully configurable set of social media buttons for your website. Here's how I went about designing, building and releasing my first NPM package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building v0
&lt;/h2&gt;

&lt;p&gt;The first step in making something that others could use was to make it work for myself. I started by opening up my editor and adding in a couple stubs for the sites I knew I'd want to support, Twitter, Reddit, Hackernews and LinkedIn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Twitter&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Reddit&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hackernews&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;LinkedIn&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I had to figure out which URLs to actually send people to and what variables they each take. This was the most difficult step since each site is different. Reddit, for example, would try to load an invalid subreddit unless I encoded the sharing URL before sending people to it. Twitter is the only site that allows tagging the author using the &lt;code&gt;via&lt;/code&gt; tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encoded_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://twitter.com/intent/tweet?url={url}&amp;amp;text={description}&amp;amp;via={username}"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Twitter&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.reddit.com/submit?url=${encoded_url}&amp;amp;title=${title}"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Reddit&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"http://news.ycombinator.com/submitlink?u=${url}&amp;amp;t=${title}"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hackernews&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.linkedin.com/sharing/share-offsite/?url=${url}"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;LinkedIn&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I had the links working I just had to find icons to use. One way to do this is to Google &lt;code&gt;&amp;lt;company&amp;gt; logo svg&lt;/code&gt; but that would lead to styles that don't match. I ended up finding a &lt;a href="https://simpleicons.org/"&gt;website&lt;/a&gt; that had all of the company logos as well as their company colour to use when I got around to styling the hover state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://twitter.com/intent/tweet?url={url}&amp;amp;text={description}&amp;amp;via={username}"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"img"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;X&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt;
      &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, I had a working social media sharing bar for this website. Now I just needed to make it easy to use and publish it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it Configurable
&lt;/h2&gt;

&lt;p&gt;Armed with a working MVP I was now challenged to make it easy to use and configurable. Just because I like a certain set of websites to share or a given order doesn't mean everyone will.&lt;/p&gt;

&lt;p&gt;The first iteration of this was moving each component into its own Astro file (&lt;code&gt;.astro&lt;/code&gt;) and showing people how to import these components. This allows people to include the components they want, in the order they want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// reddit.astro
const { url = Astro.request.url, title } = Astro.props;

const encoded_url = encodeURIComponent(url);
let URL = `https://www.reddit.com/submit?url=${encoded_url}&amp;amp;title=${title}`;
---

&amp;lt;a href={URL}&amp;gt;
  &amp;lt;svg&amp;gt;&amp;lt;/svg&amp;gt;
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// post.astro
import { RedditShareButton } from "./reddit.astro";
---

&amp;lt;RedditShareButton title={title} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next up was figuring out how to allow people to only use the single share component while also giving them the flexibility around which share buttons to include and the order of those buttons. At first, I thought about using &lt;a href="https://docs.astro.build/en/guides/integrations-guide/"&gt;Astro's integration framework&lt;/a&gt; and storing the configuration there. My main concern with this was around usability, the settings would live in a different file than the component and I worried that it'd be confusing to figure out.&lt;/p&gt;

&lt;p&gt;After thinking about it for a while I settled on a different approach, creating an optional array of components that could be passed in. This would allow the user to define the order and show buttons alongside the actual component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// SocialShare.astro

import HackerNewsShareButton from "./HackerNews.astro";
import LinkedInShareButton from "./LinkedIn.astro";
import RedditShareButton from "./Reddit.astro";
import TwitterShareButton from "./Twitter.astro";

const DEFAULT_COMPONENTS = [
  TwitterShareButton,
  HackerNewsShareButton,
  LinkedInShareButton,
  RedditShareButton,
];

const {
  buttons = DEFAULT_COMPONENTS,
  url = Astro.request.url,
  title,
  description,
  via,
} = Astro.props;
---

&amp;lt;div&amp;gt;
  {
    buttons.map(Button =&amp;gt; (
      &amp;lt;Button url={url} description={description} via={via} title={title} /&amp;gt;
    ))
  }
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// post.astro
import {
  LinkedInShareButton,
  TwitterShareButton,
  SocialShare,
} from "./SocialShare.astro";

// Optional
const BUTTONS = [TwitterShareButton, LinkedInShareButton];
---

&amp;lt;SocialShare
  buttons={BUTTONS}
  description="Description of the page/post"
  via="YourTwitterAccount"
  title="Page Title"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This final solution felt simple enough to explain and with that, I moved on to turning it into its own package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an NPM package
&lt;/h2&gt;

&lt;p&gt;With everything ready to go, I now needed to learn how to turn this into an NPM package that people could install and use. The first step is to create a new directory and initialize both git and NPM.&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;mkdir &lt;/span&gt;astro-social-share &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$_&lt;/span&gt;
git init
npm init

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

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;npm init&lt;/code&gt; will walk you through a few basic questions to help you create your new package. There are also a few optional fields you can include to help others find your new package or help them know where to go for bug reports.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;package name: &lt;span class="o"&gt;(&lt;/span&gt;astro-social-share&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;enter&amp;gt;
version: &lt;span class="o"&gt;(&lt;/span&gt;1.0.0&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;enter&amp;gt;
description: Social media share buttons &lt;span class="k"&gt;for &lt;/span&gt;your Astro site &amp;lt;enter&amp;gt;
entry point: &lt;span class="o"&gt;(&lt;/span&gt;index.js&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;enter&amp;gt;
&lt;span class="nb"&gt;test command&lt;/span&gt;: &amp;lt;enter&amp;gt;
git repository: https://github.com/silent1mezzo/astro-social-share
keywords: &amp;lt;enter&amp;gt;
author: Adam McKerlie
license: &lt;span class="o"&gt;(&lt;/span&gt;ISC&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;I chose MIT, up to you.&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;If you're wanting to test out your package locally you can do so through &lt;code&gt;npm link&lt;/code&gt; which allows you to install a local version of your package and use it in another project. To do this you first call &lt;code&gt;npm link&lt;/code&gt; in your package's directory. Then you run &lt;code&gt;npm link &amp;lt;package name&amp;gt;&lt;/code&gt; in your other projects directory to install it. Now any changes to your package will be reflected without having to publish a new version first. This is really helpful while building out new features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing
&lt;/h2&gt;

&lt;p&gt;The last step to all of this was actually publishing this to NPM. The first thing I needed to do was login to npm via the command line &lt;code&gt;npm login&lt;/code&gt;. This opens up a browser window to authenticate with npm. After that, I ran &lt;code&gt;npm publish --dry-run&lt;/code&gt; to see if everything was working before publishing the package. Once I was ready, I removed the &lt;code&gt;--dry-run&lt;/code&gt; flag and published the package directly to npm. With the package published, I installed the package like you would any other and updated this website to pull from the package vs the local copy.&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;astro-social-share &lt;span class="c"&gt;# put in the package name you created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// post.astro
import { SocialShare } from "astro-social-share";
---

&amp;lt;SocialShare
  description="Description of the page/post"
  via="YourTwitterAccount"
  title="Page Title"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, I added a &lt;code&gt;postpublish&lt;/code&gt; script to my package.json file to also tag a GitHub release&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;// package.json&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postpublish&lt;/span&gt;&lt;span class="dl"&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;PACKAGE_VERSION=$(cat package.json | grep &lt;/span&gt;&lt;span class="se"&gt;\\\"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="se"&gt;\\\"&lt;/span&gt;&lt;span class="s2"&gt; | head -1 | awk -F: '{ print $2 }' | sed 's/[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,]//g' | tr -d '[[:space:]]') &amp;amp;&amp;amp; git tag v$PACKAGE_VERSION &amp;amp;&amp;amp; git push --tags&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The first feature request
&lt;/h2&gt;

&lt;p&gt;It took approximately one week for the &lt;a href="https://github.com/silent1mezzo/astro-social-share/issues/7"&gt;first feature request&lt;/a&gt; to come in. While a fairly simple request, just asking for &lt;code&gt;rel="noopener noreferrer"&lt;/code&gt; to be added, it was a great milestone for me. It's the first time someone cared enough about something I built to ask for something.&lt;/p&gt;

&lt;p&gt;Overall, it's been a fun project to get to know Astro better. It solved a need I had, helped me better understand how Astro works and seems useful for the community. There isn't a lot more I need to do it, other than add more providers and allow users to use their own logos, it's feature complete. If there's something you'd like to see feel free to &lt;a href="https://github.com/silent1mezzo/astro-social-share/issues"&gt;add a feature request&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>astro</category>
      <category>blog</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The best advice for shipping software</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Mon, 30 Oct 2023 00:00:00 +0000</pubDate>
      <link>https://forem.com/adammckerlie/the-best-advice-for-shipping-software-1o5c</link>
      <guid>https://forem.com/adammckerlie/the-best-advice-for-shipping-software-1o5c</guid>
      <description>&lt;p&gt;This is the best piece of advice I’ve ever received about building software.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re not embarrassed by the thing you shipped, you waited too long&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When people first hear this, they think it means shipping buggy code. It isn’t.&lt;/p&gt;

&lt;p&gt;It’s also not about shipping broken experiences.&lt;/p&gt;

&lt;p&gt;The mindset revolves around the idea that you’ll learn what you need to build when your customers first start using it. If you wait until you have a full-featured, polished application there’s a good chance you’ve waisted time building the wrong thing.&lt;/p&gt;

&lt;p&gt;Figure out what your MVP is and ship it two weeks early.&lt;/p&gt;

&lt;p&gt;Talk with customers and see if it resonates. Get comfortable shipping early and often.&lt;/p&gt;

&lt;p&gt;Iterate and repeat. Happy shipping! 🚢&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>As a manager, what is your ideal time spent on coding?</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Sat, 27 Aug 2022 12:04:48 +0000</pubDate>
      <link>https://forem.com/adammckerlie/as-a-manager-what-is-your-ideal-time-spent-on-coding-4g53</link>
      <guid>https://forem.com/adammckerlie/as-a-manager-what-is-your-ideal-time-spent-on-coding-4g53</guid>
      <description>&lt;p&gt;This question was recently asked in a &lt;a href="https://torontojs.slack.com"&gt;local engineering slack organization&lt;/a&gt; and it got me thinking, what would the ideal amount of time spent on technical contributions be for me? When I first &lt;a href="https://mckerlie.com/posts/from-developer-to-manager"&gt;transitioned from a developer to a manager&lt;/a&gt; I didn't know how I provided value. I assumed that being a manager was largely the same as being a developer except now I had to talk to other developers and help them become better somehow. As meetings, writing more and more documentation, project planning, people planning and other managerial responsibilities started to pile up I started blocking the team on all of my technical tasks. What used to take me a day started taking three or four. Projects that I would have finished in a month stretched on for a quarter and the more technical work that I took on the less happy both my team and I was. I started to burn out and my team started to get frustrated with the way I blocked them on their own work. It took me over 18 months in this first management role before I figured out how I &lt;a href="https://mckerlie.com/posts/you-want-to-be-an-engineering-manager/#providing-value-as-an-engineering-manager"&gt;provided value as an Engineering Manager (EM)&lt;/a&gt; and started reducing my technical contributions. &lt;/p&gt;

&lt;h2&gt;
  
  
  So, how much time should you spend?
&lt;/h2&gt;

&lt;p&gt;It depends! Your job as an Engineering Manager is to unblock your team and help your developers grow. Depending on the size and makeup of your team this can mean very different things. It can also mean different things at different times in your career.&lt;/p&gt;

&lt;p&gt;If you have a really junior team, you may have to be more hands-on, mentoring them, and teaching them how to build software and grow as an engineer. This could take the form of pair-programming through different features and projects. You may spend more time reviewing pull requests or sitting down and walking them through architecting a solution. Time spent here growing their own engineering skills will hopefully help them down the road as they grow into more senior engineers.&lt;/p&gt;

&lt;p&gt;If you've got a more senior team or a team of really strong developers your technical contributions may fall to nothing. Sure, you may want to review a PR or two to make sure you have a handle on how your team is doing. You may also want to unblock your team by taking small, one-off requests that come up but you shouldn't be taking on large features or complex tasks. Taking on this work will only block your team and take away opportunities for your team to shine.&lt;/p&gt;

&lt;h2&gt;
  
  
  But I love coding!
&lt;/h2&gt;

&lt;p&gt;Me too! Being a great developer is often what gets us to the manager position in the first place and it can be difficult to give up something you enjoy doing. This is where it's important to know what you want to get out of your career and to &lt;a href="https://mckerlie.com/posts/tips-for-early-career/#find-a-mentor-and-ask-for-help"&gt;find a mentor&lt;/a&gt;. Going from an IC to a Manager is a career change. This change can be difficult, especially if you weren't expecting it. Having someone to support you during this change and taking the time to really understand what you want from your career can help you let go of coding.&lt;/p&gt;

&lt;p&gt;It's also important to find a creative outlet. &lt;a href="https://medium.com/@chulcher/coding-is-creative-c85b7566a41b"&gt;Coding is a creative&lt;/a&gt; career and as you become a manager and your day-to-day changes you'll often find yourself yearning. Lots of Engineering Managers build side projects or contribute to open source projects after hours. This provides a creative release and also allows you to stay technical. Others take up a &lt;a href="https://www.zainrizvi.io/blog/why-software-engineers-like-woodworking/"&gt;hobby like woodworking&lt;/a&gt; or &lt;a href="https://www.indeed.com/career-advice/career-development/hobbies-that-improve-coding"&gt;photography&lt;/a&gt;. These kinds of hobbies provide a creative outlet and fill the hole left by coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  How will my team know what I'm doing if I'm not coding?
&lt;/h2&gt;

&lt;p&gt;One common fear of new managers, especially those that are now managing their peers, is the feeling that they're not doing &lt;em&gt;real&lt;/em&gt; work. Because you're no longer hands-on with the code base and often have your schedules chock-full with meetings it's easy to worry that you'll be judged. One thing you can do is be transparent about what you're working on. This not only helps you stay accountable for what you're thinking about and working on but it also helps give your team insight.&lt;/p&gt;

&lt;p&gt;Every Monday morning I like to post a quick message to the team about what I'm focused on for the week. These can be presentations that I'm preparing, &lt;a href="https://mckerlie.com/posts/meetings-can-be-poisonous/"&gt;meetings that I'm having&lt;/a&gt;, strategy documents, and conversations that I'm having. You do have to be careful to not talk about confidential things you're working on or things that may be sensitive to some people. These messages help inform the team of what you're doing and help provide transparency. They can also help if you're mentoring any of your engineers into a management position.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;It can be difficult and scary to give up programming as a manager, especially if you've been doing it for a long time. As a manager, your job is to help your team grow into better engineers and build a team that delivers great software. Depending on your time size and makeup sometimes this means that you still need to code regularly, helping unblock your team. More often than not, your technical contributions will go down as you grow as a manager so make sure you find another creative outlet!&lt;/p&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
    </item>
    <item>
      <title>2020 review of the books I read</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Mon, 04 Jan 2021 17:37:38 +0000</pubDate>
      <link>https://forem.com/adammckerlie/2020-review-of-the-books-i-read-211i</link>
      <guid>https://forem.com/adammckerlie/2020-review-of-the-books-i-read-211i</guid>
      <description>&lt;p&gt;In 2020 I read &lt;a href="https://www.goodreads.com/review/list/7269489-adam?order=d&amp;amp;shelf=2020"&gt;25 books&lt;/a&gt;. This was 5 less than the goal I set in &lt;a href="https://mckerlie.com/2019-review-of-the-books-i-read/"&gt;2019&lt;/a&gt;, but, as everyone knows, 2020 was a &lt;a href="https://media.giphy.com/media/XdIOEZTt6dL7zTYWIo/giphy.gif"&gt;doozy&lt;/a&gt;. I often use my commute to read and with Covid happening, I started working from home which reduced my reading time significantly. In June I also switched companies which reduced the time I had to read even further. Overall I’m happy with the number of books that I finished, including some really good ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Favourite Books
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://amzn.to/38eXyQK"&gt;Resilient Management – Lara Hogan&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was the most useful book I read last year, probably because I &lt;a href="https://mckerlie.com/running-an-engineering-management-book-club/"&gt;led a book club&lt;/a&gt; on it which forced me to really pay attention and write down questions for each chapter. &lt;a href="https://larahogan.me/"&gt;Lara Hogan&lt;/a&gt; has always been a source of inspiration and actionable insights into how I can be a better manager and Resilient Management was no different. Whether you’re new to management or a veteran I strongly suggest reading this book.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://amzn.to/3sdU3BY"&gt;I Am C-3PO: The Inside Story – Anthony Daniels&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I love Star Wars and a good number of the books I read are related to it. Getting an inside look into Anthony Daniels’ experience playing C-3PO was a part of the story I hadn’t heard yet. I listened to this on Audible and hearing it from Anthony Daniels directly made it even better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://amzn.to/3br39VR"&gt;The Better Allies Approach to Hiring – Karen Catlin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This book opened my eyes to areas that I can improve upon while hiring and building more inclusive teams.  Examples such as tagging photos for accessibility on your career website, rethinking the “dude wall” and understanding the importance of retention definitely resonated with me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://amzn.to/2Xtb24S"&gt;A Christmas Carol – Charles Dickens&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Growing up &lt;a href="https://en.wikipedia.org/wiki/The_Muppet_Christmas_Carol"&gt;The Muppet Christmas Carol&lt;/a&gt; was my favourite movie but until this year I had never actually read the original. I was surprised at how close the Muppets version was to the original and really enjoyed reading it for the nostalgia it brought on. It’s a pretty quick read and one I’ll be doing every Christmas now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Statistics
&lt;/h2&gt;

&lt;p&gt;Last year I read/listened to 25 books. You can find the full list &lt;a href="https://www.goodreads.com/review/list/7269489-adam?order=d&amp;amp;shelf=2020"&gt;here&lt;/a&gt;. I listened to 18 audiobooks (at 1.5x speed) for a total of 98 hours of listening time (4.1 days). This is one of the reasons why I was able to get through so many books. On average it took me 5.2 days to get through an audiobook vs 43.3 days to get through a hard copy. Of the 6 physical books, I read a total of 1182 pages.&lt;/p&gt;

&lt;p&gt;Of the 25 books, only 10 were written by women (40%). This was an increase over &lt;a href="https://mckerlie.com/2019-review-of-the-books-i-read/"&gt;2019&lt;/a&gt; where 35% were from female authors and a significant increase over &lt;a href="https://mckerlie.com/2018-review-of-the-books-i-read/"&gt;2018&lt;/a&gt; where only 14% of the books I read were written by women. I plan on continuing this trend into 2021.&lt;/p&gt;

&lt;p&gt;The trend continues where I rated books slightly more positive than the rating on Good Reads. In 18 cases I rated the book more favourably than the community, though my average rating was 4.48/5 vs 4.05/5 so the difference is very minimal. I rated books pretty evenly regardless of the gender of the author but I did notice that I rated physical books much higher than audiobooks in general (4.83 vs 4.33). This makes sense since I tend to only buy books that I think I’ll be interested in.&lt;/p&gt;

&lt;p&gt;This year I was much more consistent in the genres of books I read. Last year 23% of the books I read fell under the business category. This year Children, Business, Science Fiction, Politics, Self Help and Fantasy all tied for 11.5%&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X5CybNZT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2021/01/Screen-Shot-2021-01-04-at-12.35.02-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X5CybNZT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2021/01/Screen-Shot-2021-01-04-at-12.35.02-PM.png" alt="" width="600" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Onwards
&lt;/h2&gt;

&lt;p&gt;I already have a couple of goals for 2021. I want to continue being mindful of the books I’m reading and who’s writing them and I want to make sure that I’m reading books from a diverse set of authors.&lt;/p&gt;

&lt;p&gt;The second goal is to read at least 30 books. This is the same as my goal for 2020 but more than I actually read last year. As with last year, I want to be more consistent with the number of books I read in a month. I often get on a roll and then burn out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qabs3KPV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2020/11/1_-R98QwSjnwcHHoGTDynBXw-1024x632.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qabs3KPV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2020/11/1_-R98QwSjnwcHHoGTDynBXw-1024x632.png" alt="" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hoD1FqiP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2020/11/1_RWSAVY4Yn7t-Fo6EU-66QQ-1024x634.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hoD1FqiP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2020/11/1_RWSAVY4Yn7t-Fo6EU-66QQ-1024x634.png" alt="" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DnIK83rf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2021/01/Screen-Shot-2021-01-06-at-10.05.46-PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DnIK83rf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2021/01/Screen-Shot-2021-01-06-at-10.05.46-PM.png" alt="" width="598" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My final goal is to read more physical books. I love audiobooks because I can listen to them while I’m walking and driving but I’d really like to get to more physical books. There’s something about flipping through the pages and finally getting to the end that is so much more satisfying than finishing an audiobook.&lt;/p&gt;

&lt;p&gt;Here’s to &lt;a href="https://www.goodreads.com/review/list/7269489-adam?shelf=2021"&gt;2021 and more hopefully many more books&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://mckerlie/2020-review-of-the-books-i-read/"&gt;2020 review of the books I read&lt;/a&gt; appeared first on &lt;a href="https://mckerlie.com"&gt;Procrastinating Manager&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>reading</category>
      <category>management</category>
      <category>data</category>
    </item>
    <item>
      <title>Meetings can be Poisonous</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Tue, 22 Dec 2020 21:03:00 +0000</pubDate>
      <link>https://forem.com/adammckerlie/meetings-can-be-poisonous-2k9h</link>
      <guid>https://forem.com/adammckerlie/meetings-can-be-poisonous-2k9h</guid>
      <description>&lt;p&gt;Eight years ago I wrote about how &lt;a href="https://procrastinatingmanager.com/meetings-are-poisonous/"&gt;meetings are poisonous&lt;/a&gt;. At the time I was the senior developer on my team and I was getting pulled into 4-5 meetings a day. This killed my productivity and made it difficult to get any of my IC work done. I struggled for many months until I realized that I could just say no to meetings. Since then I’ve transitioned from &lt;a href="https://procrastinatingmanager.com/from-developer-to-manager/"&gt;developer to manager&lt;/a&gt;, manager to director and then back to manager. My feelings on meetings have changed over the years but the general advice from eight years ago still stays the same. If you’re interested in reading more about meetings Leigh Espy has a great book called “&lt;a href="https://amzn.to/3riD8Oc"&gt;Bad Meetings Happe To Good People&lt;/a&gt;” which is worth a read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have a Purpose
&lt;/h2&gt;

&lt;p&gt;The single most important thing you can do to make sure your meeting is successful is to have a purpose. People often set up meetings without having a good understanding of why they need the meeting in the first place. Pure status updates can be done more efficiently over email, slack or project management tools. Meetings where the tone is important or decisions need to be made are good candidates for a successful meeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have an Agenda
&lt;/h2&gt;

&lt;p&gt;Once you’ve figured out the purpose of the meeting the next most important step is having an agenda. Agendas inform participants whether they need to attend or not, help keep the meeting on track and are the starting point for keeping notes. They can be used to summarize the talking points and show the required outcomes from a meeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  50 or 25 minutes meetings
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.m.wikipedia.org/wiki/Parkinson%27s_law"&gt;Parkinson’s Law&lt;/a&gt; is as true for writing code as it is for meetings. The meeting expands so as to fill the time available for its completion. If you schedule an hour the meeting will more likely take an hour, even if you only need 50 minutes or less. Google Calendar even has a setting to help you called Speedy Meetings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oq0e2SfJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2020/12/7C171686-A237-4F93-BFF7-8E6F87C2B7BB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oq0e2SfJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://procrastinatingmanager.com/wp-content/uploads/2020/12/7C171686-A237-4F93-BFF7-8E6F87C2B7BB.png" alt="Google Speedy Meeting settings" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enabling this setting means your 30-minutes meetings will be by default 25-minutes long, 45-minutes meetings will be 40-minutes long, and 60-minutes meetings will be 50-minutes long. This also helps give people a small break between meetings to get a glass of water, use the restroom or catch-up on notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Laptops or Phones
&lt;/h2&gt;

&lt;p&gt;Eight years ago when I first wrote about this most of my meetings were in person. Putting your laptop and phone away helped reduce distractions and kept meetings on track. Now that most of the &lt;a href="https://www.forbes.com/sites/adigaskell/2020/07/08/are-we-entering-a-new-world-of-remote-work/?sh=6de114745959"&gt;world has gone remote&lt;/a&gt; and will continue to do so for the foreseeable future it’s all about minimizing distractions. Close everything except for the video call and wherever you take notes, put your laptop and phone into do not disturb and resist the temptation to open a browser window and fall down the rabbit hole that is the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Take Charge
&lt;/h2&gt;

&lt;p&gt;The person who calls the meeting should take charge or talk to someone beforehand that will lead it. The goal of this moderator is to make sure the meeting sticks to the agenda, allows the conversation to wander when productive but brings it back as soon as people get off track. Depending on the personalities in the meeting this can be a very difficult job. It can be a fine line between a productive conversation and one that’s completely out of control. Your job as a moderator is to also make sure everyone has a chance to talk. That means calling on the quiet ones and giving them a space to talk.&lt;/p&gt;

&lt;h2&gt;
  
  
  As few people as possible
&lt;/h2&gt;

&lt;p&gt;Meetings are one of the most costly parts of your business. Research shows that across the United States, meetings that are not even necessary waste over &lt;a href="https://meetingking.com/37-billion-per-year-unnecessary-meetings-share/"&gt;$25 million every single day, or about $37 billion every year&lt;/a&gt;. Smaller meetings not only reduce the cost of the meeting they also make it easier to stick to the agenda, make sure everyone has a say and make decisions. If you’re finding your meetings are starting to bloat try using the RACI framework to determine who needs to be there. RACI stands for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who are the ones who will be Responsible for implementing decisions made (Developers, ICs)&lt;/li&gt;
&lt;li&gt;Who is Accountable for decisions made (Management generally)&lt;/li&gt;
&lt;li&gt;Who needs to be Consulted before decisions are made (Experts, Stakeholders)&lt;/li&gt;
&lt;li&gt;Who simply needs to be Informed of the outcome if it impacts them (everyone else)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Often only those Responsible and Accountable need to be in the meeting. They should consult with the experts and stakeholders beforehand and a summary with the meeting notes and decisions made should be sent out to those that need to be informed afterwards. This helps keep meetings to a manageable amount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Action Plan
&lt;/h2&gt;

&lt;p&gt;Every meeting should end with an action plan. This is a list of meeting notes, a list of the decisions made in the meeting and a list of action items with due dates and assignees. If a follow-up meeting is needed this should be done in the meeting as well. This list of action items should be clear and concise and people should walk away knowing what’s required of them and when it’s due.&lt;/p&gt;




&lt;p&gt;There’s a lot that needs to go into making a meeting productive and valuable but if you spend a few minutes on each meeting everyone involved will benefit. What are your favourite tips for running good meetings?&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://procrastinatingmanager.com/meetings-can-be-poisonous/"&gt;Meetings can be Poisonous&lt;/a&gt; appeared first on &lt;a href="https://procrastinatingmanager.com"&gt;Procrastinating Manager&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>general</category>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Running an Engineering Management Book Club</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Wed, 16 Dec 2020 16:26:58 +0000</pubDate>
      <link>https://forem.com/adammckerlie/running-an-engineering-management-book-club-4a2d</link>
      <guid>https://forem.com/adammckerlie/running-an-engineering-management-book-club-4a2d</guid>
      <description>&lt;p&gt;Being a manager can be extremely hard, it can feel isolating at times especially if you don’t have a good support network of peers. But how do you cultivate this team of peers when your days are filled with 1:1s, meetings, code reviews and other things you need to get done?&lt;/p&gt;

&lt;p&gt;Earlier this year I was struggling with this question. I needed to expand and nurture my peer group but was too busy with the day to day tasks of leading multiple teams to do anything regularly. That’s when I found this excellent post on &lt;a href="https://blog.danielna.com/starting-an-engineering-management-book-club/"&gt;starting an engineering manager book club&lt;/a&gt; by Daniel Na. I asked my peers if they’d be interested in starting a book club for engineering managers. They said yes and I got started thinking about what it could look like.&lt;/p&gt;

&lt;p&gt;I wanted the book club to, first and foremost, be a place where we could help one another with challenges in our day to day jobs. Sometimes helping someone is as simple as listening to their problems. Sometimes you have experience and can offer more targeted advice and sometimes people just need to vent. I wanted a place where this could happen freely. I also wanted us to grow as a team, learning new ways to manage our engineers, new techniques to motivate and help grow people’s careers.&lt;/p&gt;

&lt;p&gt;Having now led a few of these I thought I’d share some tips that have worked well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Keep it small, 8 people or less&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the goals is to allow everyone a chance to discuss the book and talk about how that’s impacting their day to day challenges. If the group is too big it can make it difficult for a couple of reasons. One reason, logistically speaking, is that it’ll be hard to get everyone a chance to talk in the allotted time. The other problem is a little more nuanced. As the group grows people can be worried about sharing their opinion with a large audience. You may also have someone who dominates the conversation. It’s the job of the moderator to keep the loud ones from &lt;a href="https://www.psychologytoday.com/ca/blog/the-couch/201204/5-steps-dealing-people-who-talk-too-much"&gt;talking too much&lt;/a&gt; and to get the quiet ones to share their opinions. Keeping the group smaller helps with both of these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Choose the book together&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can be easier to want to read your favourite management book. You know it’s great and will help others just like it helped you. It’s important to choose the book as a group though because it broadens the potential list of books and gives everyone an opportunity to suggest their favourite book. I asked for people’s favourite management books and then did a Slack poll allowing people to vote for their top three. We then read the most voted on the book. This helped ensure the majority of people were actually interested in the book club and helped with attendance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Schedule every meeting from the start&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the things that worked really well was scheduling every meeting in advance. I planned out every chapter that we’d be reading and scheduled it for the same time every week. We all have busy calendars and planning in advance made it easier to carve out a chunk in everyone’s day. It also helped me plan for days that didn’t work for the majority and allowed me to not have to skip a week because I couldn’t find a time slot that worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Have the meeting early in the morning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one is very team and timezone dependent. We found that early mornings before the rush of the day started worked best. People were less likely to have conflicts or meetings that went long and it helped set the tone for the day. I always felt refreshed after the book club, feeling like I could be a better manager to my team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Keep it manageable each week (1 or 2 chapters)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one is very book dependent. Some books throw a lot of discussable content into each chapter. Some books have very short chapters with themes that span multiple chapters. Plan the book to read 30-60 pages a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Have some prepared questions but allow the conversation to flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal of a moderator is to guide the conversation. Sometimes that means letting people go where they need to go and sometimes that means guiding the conversation with specific questions. I usually find 3 or 4 questions is the right number for an hour-long discussion with &amp;lt;8 people.&lt;/p&gt;

&lt;p&gt;Icebreakers are a good way to get everyone comfortable talking. I like asking questions like “What’s the best piece of advice you’ve ever been given?” or “What’s the most pressing decision you need to make right now”. Sometimes these icebreakers take the entire meeting and that’s ok! If you’re looking for some good icebreakers Vanessa Van Edwards has a &lt;a href="https://www.scienceofpeople.com/meeting-icebreakers/"&gt;great article on them&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’s also ok to have periods of silence. &lt;a href="https://www.forbes.com/sites/averyblank/2017/06/20/6-ways-leaders-use-silence-to-increase-their-power-that-you-can-do-too/?sh=75b5922427c8"&gt;Silence is a tactic&lt;/a&gt; you can use to empower others to participate, to drive home an important answer or just to figure out what you want to say next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Call on the quiet people&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not everyone is comfortable sharing. Some people take longer to warm up than others and some people feel like they have nothing valuable to share. Every manager has a unique perspective on things and when they contribute the whole group grows. It’s the job of the moderator to know who’s participating and who isn’t and call on the quiet ones to provide their opinion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Vegas Rules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What happens in the book club stays in the book club. One of the most important rules of running a book club is creating psychological safety. If people don’t feel safe in sharing their management challenges then the discussion will never move past superficial conversation. If the group commits to open and honest conversation that stays within the walls of the book club you can really make something great.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Know when to quit a book&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not every book is a winner. You and your book club don’t have to power through a book that isn’t working for you. After the first few meetings, it’s a good idea to check in on everyone and see how they’re enjoying the book. If it feels like you’re trying to pull teeth getting people to share it may be time to move on to another book.&lt;/p&gt;




&lt;p&gt;These are the things that helped our group have a successful book club. I hope you all can level up your peer group with your own book club and I’d love to hear how it goes.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://procrastinatingmanager.com/running-an-engineering-management-book-club/"&gt;Running an Engineering Management Book Club&lt;/a&gt; appeared first on &lt;a href="https://procrastinatingmanager.com"&gt;Procrastinating Manager&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>reading</category>
      <category>management</category>
    </item>
    <item>
      <title>What's the worst piece of advice you've ever received?</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Sun, 31 May 2020 12:58:21 +0000</pubDate>
      <link>https://forem.com/adammckerlie/what-s-the-worst-piece-of-advice-you-ve-ever-received-438o</link>
      <guid>https://forem.com/adammckerlie/what-s-the-worst-piece-of-advice-you-ve-ever-received-438o</guid>
      <description>&lt;p&gt;I've had a few throughout my career:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You should really focus on C, don't bother learning Python.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Focus on whatever makes you happy. You'll be way more likely to succeed. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slow down&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are definitely times where it makes sense to slow down, especially if you're burning out. But momentum can be extremely useful throughout your career. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You shouldn't try to go to university, you don't have the attention span for it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was my grade 11 music teacher. If I had listened I wouldn't have gone into Computer Science. Don't listen to people who try to limit what you can accomplish.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>career</category>
    </item>
    <item>
      <title>What's the one thing you always need to google?</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Sat, 30 May 2020 01:42:41 +0000</pubDate>
      <link>https://forem.com/adammckerlie/what-s-the-one-thing-you-always-need-to-google-3p4m</link>
      <guid>https://forem.com/adammckerlie/what-s-the-one-thing-you-always-need-to-google-3p4m</guid>
      <description>&lt;p&gt;For me, it's reading/writing CSV's in Python. I've done it a thousand times but I can never remember the syntax.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>7 Musings on Minion Management</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Tue, 19 May 2020 13:35:57 +0000</pubDate>
      <link>https://forem.com/adammckerlie/7-musings-on-minion-management-1ebp</link>
      <guid>https://forem.com/adammckerlie/7-musings-on-minion-management-1ebp</guid>
      <description>&lt;p&gt;So, you’ve just been promoted to the dark side of management but you don’t know how to lead your minions? Here are a few tips on keeping your plebeians in line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Your Minions Do the Dirty Work
&lt;/h2&gt;

&lt;p&gt;You shouldn’t worry yourself about work that’s underneath you, you get paid way to much. You’ve hired coder monkeys to handle everything from downed servers at 3 am to documentation to those pesky clients. All you have to do is sit back and make sure they don’t lose focus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t Give Any Praise
&lt;/h2&gt;

&lt;p&gt;Your minions just put in 100 hour weeks to get the feature launched on time? They get paid to do that work so there’s really no need to tell them they’re doing their job. Work horses don’t need to be told they’re doing a good job every time so why should your employees? As soon as you’re done you should give them more work since that’s what they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Belittle Your Minions
&lt;/h2&gt;

&lt;p&gt;You’re the manager so that makes you better than everyone else. If they were as good as you, they’d be managing people themselves. This means you’ve earned the right to make the others know that they aren’t as good as you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Always Leave Before Your Minions
&lt;/h2&gt;

&lt;p&gt;Your time is way more valuable and you don’t want to wear yourself out. This means you should come to work later and leave earlier than your team. Besides, as long as you’re doing a good job guiding your newbies they should be able to carry on their work when you’re gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid All Responsibility
&lt;/h2&gt;

&lt;p&gt;If your staff messes something up it’s their fault, not yours. Why should you be held responsible for something they’ve done. When the top brass asks you what happened make sure they know exactly who screwed up. This also secures your position because you’ll never do anything wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Believe That Your Job Is More Valuable
&lt;/h2&gt;

&lt;p&gt;Obviously the people underneath you wouldn’t be able to do their job without you. Who would lead them? You get paid more so that means you’re way more valuable than them. Make sure that they know you’re more valuable to the company.&lt;/p&gt;

&lt;h2&gt;
  
  
  Always Think You Are Right
&lt;/h2&gt;

&lt;p&gt;Again, you’ve probably been there longer and you get paid more so that means you’re always right. When someone tries to tell you otherwise make sure you make them look bad to their co-workers; this will show them who’s boss.&lt;/p&gt;




&lt;p&gt;If you’ve gotten to this point and haven’t realized you shouldn’t do any of these things you probably shouldn’t be managing people. If you realized that you should be doing exactly the opposite than go forth and manage!&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://procrastinatingdev.com/7-musings-on-minion-management/"&gt;7 Musings on Minion Management&lt;/a&gt; appeared first on &lt;a href="https://procrastinatingdev.com"&gt;Procrastinating Developer&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
    </item>
    <item>
      <title>Using Python to generate over 10,000 unique 8-bit lightsabers</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Mon, 04 May 2020 10:44:00 +0000</pubDate>
      <link>https://forem.com/adammckerlie/using-python-to-generate-over-10-000-unique-8-bit-lightsabers-31ae</link>
      <guid>https://forem.com/adammckerlie/using-python-to-generate-over-10-000-unique-8-bit-lightsabers-31ae</guid>
      <description>&lt;p&gt;Python is great for a number of things. It powers &lt;a href="https://w3techs.com/technologies/details/pl-python"&gt;1.4% of the internet&lt;/a&gt;, &lt;a href="https://code.nasa.gov/?q=python"&gt;Nasa uses it a lot!&lt;/a&gt; and you can use it to &lt;a href="https://simpleprogrammer.com/python-generative-art-math/"&gt;create art&lt;/a&gt;. In honour of &lt;a href="https://en.m.wikipedia.org/wiki/Star_Wars_Day"&gt;Star Wars Day&lt;/a&gt;, I wanted to create a program that dynamically generated lightsabers and tweeted them out once a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I created a computer program that generates a unique lightsaber made out of four different parts (blade, hilt, pommel and button) and &lt;a href="https://twitter.com/dailylightsaber/status/1252351344834297860"&gt;tweets it out&lt;/a&gt; once a day along with some statistics about the lightsaber.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_87BKkp_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/EWE-6TpU8AEscQK.jpeg%3Fw%3D770%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_87BKkp_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/EWE-6TpU8AEscQK.jpeg%3Fw%3D770%26ssl%3D1" alt=""&gt;&lt;/a&gt;Luke briefly had a red lightsaber&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The overall premise of this script is fairly simple. It randomly selects four pieces of a lightsaber and then pieces them together. To achieve this I used an online tool called &lt;a href="https://www.piskelapp.com/"&gt;Piskel&lt;/a&gt; to generate all of the 8-bit parts. While Piskel is normally used to create animated sprites for websites it’s one of the better 8-bit art editors. My favourite feature from it is the ability to mirror lines. This simplifies the process of making symmetrical lightsabers.&lt;/p&gt;

&lt;p&gt;Once I created a base set of parts put them in an &lt;code&gt;images&lt;/code&gt; directory. This directory has 5 folders, one for each part and an output folder (&lt;code&gt;lightsabers&lt;/code&gt;). This allows me to keep everything organized as well as helps later on when generating lightsabers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;images/
    blades/
        b1.png
        b2.png
    hilts/
        h1.png
        h2.png
    buttons/
        u1.png
    pommels/
        p1.png
    lightsabers/
        h1b2u1p1.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To do all of this I installed three python packages. I use &lt;a href="https://pypi.org/project/Pillow/"&gt;Pillow&lt;/a&gt; (version 7.1.1) for all of the image processing, &lt;a href="https://pypi.org/project/tweepy/"&gt;Tweepy&lt;/a&gt; (version 3.8.0) to send out the tweets and numpy (version 1.18.3) to do some colour processing that I’ll discuss later on. Overall it’s a very simple requirements.txt&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# requirements.txt
numpy==1.18.3
Pillow==7.1.1
tweepy==3.8.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The first version of this script was really this simple. Call &lt;code&gt;generate_lightsaber&lt;/code&gt; and that’s it. Up next I’ll talk about how I generated the initial lightsaber (just the blade and hilt), then how I added buttons and pommels and finally how I tweeted the final image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lightsaber.py
import os, random
from pathlib import Path, PurePath
# This is actually Pillow but it's API is backwards compatible with the older PIL
from PIL import Image

def generate_lightsaber():
    # Will talk about this more next

if __name__ == " __main__":
    generate_lightsaber()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding Blades and Hilts
&lt;/h2&gt;

&lt;p&gt;In my initial design, I only had blades and hilts so that’s where I started. My first goal was to get a single blade and a single hilt merged and properly lined up on a single image. I first added code to fetch a unique lightsaber part based on the directory layout above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lightsaber.py
# Set some constants to keep the code clean and consistent
IMAGE_PATH = Path('../images')
BLADE_PATH = IMAGE_PATH / 'blades'
HILT_PATH = IMAGE_PATH / 'hilts'
OUTPUT_PATH = IMAGE_PATH / 'lightsabers'

def fetch_lightsaber_parts():
    hilt = Path(f"{HILT_PATH}/{random.choice(os.listdir(HILT_PATH))}")
    blade = Path(f"{BLADE_PATH}/{random.choice(os.listdir(BLADE_PATH))}")

    return (hilt, blade)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;First, we have &lt;code&gt;os.listdir()&lt;/code&gt;, which returns a list of filenames in the directory given. The list is in arbitrary order and it does not include the special entries ‘.’ and ‘..’ even if they are present in the directory. It does, however, include any folders or special files (like .DS_store in OS X) so make sure your folder only includes the image files. We pass that filename into &lt;code&gt;random.choice&lt;/code&gt; which will choose a random filename. Finally, we create the full file path and return that for both the hilt and the blade.&lt;/p&gt;

&lt;p&gt;Once we’ve gotten the image paths we can start piecing them together into a final image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lightsaber.py
def generate_lightsaber():
    blade_path, hilt_path = fetch_lightsaber_parts()

    # Open the images using Pillow
    blade = Image.open(blade_path, 'r')
    hilt = Image.open(hilt_path, 'r')

    # Open the output image. Twitter displays the entire image if it's 1024x512
    output = Image.new("RGB", (1024, 512), (255, 255, 255))

    # Paste the blade and hilt onto the output image. 
    output.paste(blade, blade_offset, mask=blade)
    output.paste(hilt, hilt_offset, mask=hilt)

    # Save the output image to disk
    img.save("{}/{}.png".format(OUTPUT_PATH, output_filename))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;First, we’ll fetch the paths as described above. After that, we’ll use Pillow to open the images as well as create a new output image with the width and height specified by Twitter to allow us to show the entire image as a preview. Finally we paste the two original images and save the output.&lt;/p&gt;

&lt;p&gt;When pasting the images onto the output you’ll see two arguments that I haven’t shown yet, &lt;code&gt;&amp;lt;part&amp;gt;_offset&lt;/code&gt; and &lt;code&gt;mask&lt;/code&gt;. The offset is used to determine where the parts are placed on the output image. When you get this wrong, funny things can happen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ixp1HQqb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/nub-1.png%3Fw%3D770%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ixp1HQqb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/nub-1.png%3Fw%3D770%26ssl%3D1" alt=""&gt;&lt;/a&gt;The tiniest of blades&lt;/p&gt;

&lt;p&gt;To calculate the offset, where the image gets placed, I need to get and store all of the dimensions of the images. Then we calculate the middle position for the width &lt;code&gt;(output_w - blade_w) // 2&lt;/code&gt; this aligns it horizontally on the image. This is true for both the blade and the hilt.&lt;/p&gt;

&lt;p&gt;To position the blade and hilt vertically it requires a little more math. For the hilt, we take the height of the output image (512px) and subtract the height of the hilt image (between 70 and 120px depending on the design). This makes the hilt start at the bottom of the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lightsaber.py
output_w, output_h = output.size
blade_w, blade_h = blade.size
hilt_w, hilt_h = hilt.size

blade_offset = ((output_h - blade_h - hilt_h + hilt_offset), (output_w - blade_w) // 2)
hilt_offset = (output_h - hilt_h, (output_w - hilt_w) // 2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vz8eUWoG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-27-at-8.28.01-AM.png%3Ffit%3D770%252C517%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vz8eUWoG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-27-at-8.28.01-AM.png%3Ffit%3D770%252C517%26ssl%3D1" alt=""&gt;&lt;/a&gt;You can think of the image like an array&lt;/p&gt;

&lt;p&gt;Using the array above as an example, if we had a lightsaber 1px wide and 2px (1px hilt, 1px blade) tall the math would be.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
blade_offset = ((4 - 1 - 1), (5 - 1) // 2 ) # (2, 2)
hilt_offset = ((4 - 1), (5 - 1) // 2) # (3, 2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V2j3vl-V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-27-at-8.38.20-AM.png%3Ffit%3D770%252C524%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V2j3vl-V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-27-at-8.38.20-AM.png%3Ffit%3D770%252C524%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we have to pass in the original image as a mask, otherwise, Pillow will default the transparent backgrounds into black. By using a mask Pillow only writes data to the output image for the pixels that have colour. The image below on the left has a mask applied, the one on the right does not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K8LS3a14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h5b2u5p1-1.png%3Fssl%3D1%26resize%3D512%252C512" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K8LS3a14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h5b2u5p1-1.png%3Fssl%3D1%26resize%3D512%252C512" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E0ylOPGx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h5b2u5p1.png%3Fssl%3D1%26resize%3D512%252C512" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E0ylOPGx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h5b2u5p1.png%3Fssl%3D1%26resize%3D512%252C512" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Hilt Offsets and Extra Information
&lt;/h2&gt;

&lt;p&gt;When calculating the blade offset you may have noticed a variable &lt;code&gt;hilt_offset&lt;/code&gt;. In some designs, the part where the blade comes out of the hilt isn’t the start of the lightsaber. In these cases I needed the blade to start “below” where the hilt starts. Knowing that I needed to do this but also store metadata on hilts, blades, buttons and pommels down the road I created a manifest.py file to store these defaults.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# manifest.py
MANIFEST = {
    "hilt": {
        "h1": {
            "offsets": {
                "blade": 5
            }
        }
    }
}

# lightsaber.py
from manifest import MANIFEST

def get_hilt_offset(hilt):
    return MANIFEST['hilt'][hilt]['offsets']['blade']

def generate_lightsaber():
    ...
    hilt_offset = get_hilt_offset(hilt)
    blade_offset = ((output_h - blade_h - hilt_h + hilt_offset), (output_w - blade_w) // 2)

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



&lt;p&gt;Without this offset, I was originally getting a floating blade for certain designs. You can see below that the bottom of the blade lines up with the top of the rightmost side of the lightsaber but it still looks funny.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pOGaIJrI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/floating-saber.png%3Fw%3D770" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pOGaIJrI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/floating-saber.png%3Fw%3D770" alt=""&gt;&lt;/a&gt;The force must be keeping this thing together&lt;/p&gt;

&lt;p&gt;This manifest file also holds information on button offsets, colours and tweet information but I’ll go more in-depth into that later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Buttons
&lt;/h2&gt;

&lt;p&gt;After getting the blades and hilts lined up I realized that do hit 10,000 unique lightsabers with only blades and hilts I would need to design 100 of each (200 total). My 8-bit art skills just aren’t up to that. By adding buttons I could cut down the unique designs needed only 22 each (66 total). Buttons were added to the final image in exactly the same way as hilts and blades.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lightsaber.py
# Set some constants to keep the code clean and consistent
IMAGE_PATH = Path('../images')
BLADE_PATH = IMAGE_PATH / 'blades'
HILT_PATH = IMAGE_PATH / 'hilts'
BUTTON_PATH = IMAGE_PATH / 'buttons'
OUTPUT_PATH = IMAGE_PATH / 'lightsabers'

def fetch_lightsaber_parts():
    hilt = Path(f"{HILT_PATH}/{random.choice(os.listdir(HILT_PATH))}")
    blade = Path(f"{BLADE_PATH}/{random.choice(os.listdir(BLADE_PATH))}")
    button = Path(f"{BUTTON_PATH}/{random.choice(os.listdir(BUTTON_PATH))}")

    return (hilt, blade, button)

def generate_lightsaber():
    blade_path, hilt_path, button_path = fetch_lightsaber_parts()

    # Open the images using Pillow
    blade = Image.open(blade_path, 'r')
    hilt = Image.open(hilt_path, 'r')
    button = Image.open(button_path, 'r')

    # Open the output image. Twitter displays the entire image if it's 1024x512
    output = Image.new("RGB", (1024, 512), (255, 255, 255))

    output_w, output_h = output.size
    blade_w, blade_h = blade.size
    hilt_w, hilt_h = hilt.size
    button_w, button_h = button.size

    hilt_offset = get_hilt_offset(hilt)
    blade_offset = ((output_h - blade_h - hilt_h + hilt_offset), (output_w - blade_w) // 2)
    hilt_offset = (output_h - hilt_h, (output_w - hilt_w) // 2)

    # Paste the blade and hilt onto the output image. 
    output.paste(blade, blade_offset, mask=blade)
    output.paste(hilt, hilt_offset, mask=hilt)
    output.paste(button, get_button_offset(hilt), mask=button)

    # Save the output image to disk
    img.save("{}/{}.png".format(OUTPUT_PATH, output_filename))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The main complication with buttons is that they can be placed in a square on the hilt and this square is different for each hilt. There are a few different ways we could do this automatically (detect edges, detect width and height of the blade, etc…) but for simplicity’s sake, I just decided to manually calculate it for each hilt and store it in the manifest file. The x&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# manifest.py
"hilt": {
    "h1": {
        "offsets": {
            "blade": 0,
            "button": {
                "x": (8, 9),
                "y": (110, 111)
            },
        },
    },
}

# lightsaber.py
def get_button_offset(hilt):
    between_x = MANIFEST['hilt'][hilt]['offsets']['button']['x']
    between_y = MANIFEST['hilt'][hilt]['offsets']['button']['y']

    return (random.randint(between_x[0], between_x[1]), random.randint(between_y[0], between_y[1]))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding Pommels
&lt;/h2&gt;

&lt;p&gt;Once again, pommels were added to the image in much the same way as the others only this time I needed to update the height of both the blade and the hilt to accommodate the pommel. This didn’t go well at first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QrFupwnM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/bad-pommel.png%3Fw%3D770%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QrFupwnM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/bad-pommel.png%3Fw%3D770%26ssl%3D1" alt=""&gt;&lt;/a&gt;This is what happens when you get your +’s and -‘s mixed up&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# lightsaber.py
# Set some constants to keep the code clean and consistent
IMAGE_PATH = Path('../images')
BLADE_PATH = IMAGE_PATH / 'blades'
HILT_PATH = IMAGE_PATH / 'hilts'
BUTTON_PATH = IMAGE_PATH / 'buttons'
POMMEL_PATH = IMAGE_PATH / 'pommels'
OUTPUT_PATH = IMAGE_PATH / 'lightsabers'

def fetch_lightsaber_parts():
    hilt = Path(f"{HILT_PATH}/{random.choice(os.listdir(HILT_PATH))}")
    blade = Path(f"{BLADE_PATH}/{random.choice(os.listdir(BLADE_PATH))}")
    button = Path(f"{BUTTON_PATH}/{random.choice(os.listdir(BUTTON_PATH))}")
    pommel = Path(f"{POMMEL_PATH}/{random.choice(os.listdir(POMMEL_PATH))}")

    return (hilt, blade, button, pommel)

def generate_lightsaber():
    blade_path, hilt_path, button_path, pommel_path = fetch_lightsaber_parts()

    # Open the images using Pillow
    blade = Image.open(blade_path, 'r')
    hilt = Image.open(hilt_path, 'r')
    button = Image.open(button_path, 'r')
    pommel = Image.open(pommel_path, 'r')

    # Open the output image. Twitter displays the entire image if it's 1024x512
    output = Image.new("RGB", (1024, 512), (255, 255, 255))

    output_w, output_h = output.size
    blade_w, blade_h = blade.size
    hilt_w, hilt_h = hilt.size
    button_w, button_h = button.size
    pommel_w, pommel_h = pommel.size

    pommel_offset = pommel_h
    hilt_offset = get_hilt_offset(hilt_name) - pommel_offset
    button_offset = get_button_offset(hilt_name)  

    blade_offset = ((output_h - blade_h - hilt_h + hilt_offset), (output_w - blade_w) // 2)
    hilt_offset = (output_h - hilt_h - pommel_offset, (output_w - hilt_w) // 2)
    pommel_offset = (output_h - pommel_h, (output_w - pommel_w) // 2)

    # Paste the blade and hilt onto the output image. 
    output.paste(blade, blade_offset, mask=blade)
    output.paste(hilt, hilt_offset, mask=hilt)
    output.paste(button, get_button_offset(hilt), mask=button)

    # Save the output image to disk
    img.save("{}/{}.png".format(OUTPUT_PATH, output_filename))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The next challenge I encountered with pommels was designing them so they looked nice across all hilts. When I first designed the lightsaber I included both the hilt and the pommel. When I realized I needed to split them out to allow for more unique combinations I simply cut the old 8-bit images in two. The problem with this is that the colour schemes between hilts and pommels just didn’t match.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ugpjj-I1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h1b1u4p2.png%3Fssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ugpjj-I1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h1b1u4p2.png%3Fssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GnHWnP2M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h2b1u1p1.png%3Fssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GnHWnP2M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h2b1u1p1.png%3Fssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a lot of thinking I decided that I could do something similar to how green screens work. If I designed the images with specific colours (in this case Red = Primary, Blue = Secondary and Green = Tertiary) I could pull them out using Pillow and Numpy and substitute them for the colours I want.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NTBPgyg9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-28-at-7.50.37-AM-1.png%3Fresize%3D352%252C389%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NTBPgyg9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/Screen-Shot-2020-04-28-at-7.50.37-AM-1.png%3Fresize%3D352%252C389%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# manifest.py
"hilt": {
    "h1": {
        "offsets": {
            "blade": 0,
            "button": {
                "x": (8, 9),
                "y": (110, 111)
            },
        },
        "colours": {
            "primary": (216,216,216), #d8d8d8
            "secondary": (141,141,141), #8d8d8d
            "tertiary": (180, 97, 19), #b46113
        },
    },
}

# lightsaber.py
import numpy as np

def convert_colours(img, hilt):
    img = img.convert('RGBA')
    data = np.array(img)

    # Grab the pixels which are 100% red, 100% blue and 100% green
    red, green, blue, alpha = data.T
    primary = (red == 255) &amp;amp; (blue == 0) &amp;amp; (green == 0)
    secondary = (red == 0) &amp;amp; (blue == 255) &amp;amp; (green == 0)
    tertiary = (red == 0) &amp;amp; (blue == 0) &amp;amp; (green == 255)

    # Substitute out the colours for the hilt colour scheme
    data[..., :-1][primary.T] = MANIFEST['hilt'][hilt_name]['colours']['primary']
    data[..., :-1][secondary.T] = MANIFEST['hilt'][hilt_name]['colours']['secondary']
    data[..., :-1][tertiary.T] = MANIFEST['hilt'][hilt_name]['colours']['tertiary']

    return Image.fromarray(data)

def generate_lightsaber():
    ... 
    pommel = Image.open(pommel_path, 'r')
    pommel = convert_colours(pommel, hilt_name)
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once I did that everything was all set. The pommels now have the correct colour scheme as the lightsaber.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q4sBCFdW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h2b9u1p1.png%3Fssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q4sBCFdW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h2b9u1p1.png%3Fssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eCe_qQus--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h1b9u1p2-1.png%3Fssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eCe_qQus--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/procrastinatingdev.com/wp-content/uploads/2020/04/h1b9u1p2-1.png%3Fssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Random Tweet Text
&lt;/h2&gt;

&lt;p&gt;The final part of all of this is generating the actual tweet. I added a number of fields to the manifest file including the hilt length and material, the blade colour, crystal and who used it and finally the pommel length. After that a new function, &lt;code&gt;generate_tweet_text&lt;/code&gt;, that pulls all of this new information together and generates the text to tweet out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# manifest.py
"hilt": {
    "h1": {
        "offsets": {
            "blade": 0,
            "button": {
                "x": (8, 9),
                "y": (110, 111)
            },
        },
        "colours": {
            "primary": (216,216,216), #d8d8d8
            "secondary": (141,141,141), #8d8d8d
            "tertiary": (180, 97, 19), #b46113
        },
        "length": 24,
        "materials": "Alloy metal/Salvaged materials"
    },
},
"blade": {
    "b1": {
        "colour": "Red",
        "crystal": ["Ilum crystal", "Ultima Pearl"],
        "type": "Sith"
    },
},
"pommel": {
    "p1": {
        "length": 5,
    },
}

# lightsaber.py
AVERAGE_HILT_LENGTH = 25
AVERAGE_POMMEL_LENGTH = 3
AVERAGE_BLADE_LENGTH = 90

NAMES = ['List', 'of', 'generated', 'names']

def generate_tweet_text(hilt, blade, pommel):
    hilt_details = MANIFEST['hilt'][hilt]
    blade_details = MANIFEST['blade'][blade]
    pommel_details = MANIFEST['pommel'][pommel]

    hilt_length = hilt_details['length']
    pommel_length = pommel_details['length']

    total_length = hilt_length + pommel_length
    average_length = AVERAGE_HILT_LENGTH + AVERAGE_POMMEL_LENGTH
    blade_length = int(AVERAGE_BLADE_LENGTH * (total_length / average_length))

    title = blade_details['type']
    if type(title) is list:
        title = random.choice(title)

    crystal = MANIFEST['blade'][blade]['crystal']
    if type(crystal) is list:
        crystal = random.choice(crystal)

    name = f"{title} {random.choice(NAMES)}"

    tweet = f'''Owner: {name}
Hilt Length: {total_length} cm
Blade Length: {blade_length} cm
Blade Colour: {MANIFEST['blade'][blade]['colour']}
Kyber Crystal: {crystal}

#StarWars
'''

    return tweet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To generate the unique names I used this &lt;a href="https://www.dimfuture.net/starwars/random/generate.php"&gt;Star Wars name generator&lt;/a&gt; to generate a number of names. I then randomly choose one to use.&lt;/p&gt;

&lt;p&gt;To tweet out the final tweet I use the python library &lt;a href="https://www.tweepy.org/"&gt;Tweepy&lt;/a&gt; to do all of the heavy lifting for me. Since I already have the image saved on file all I need to do is grab the credentials from an environment variable, upload the media and then post the tweet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;consumer_key = os.getenv('CONSUMER_KEY')
consumer_secret = os.getenv('CONSUMER_SECRET')

access_token = os.getenv('ACCESS_TOKEN')
access_token_secret = os.getenv('ACCESS_TOKEN_SECRET')

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth)

tweet_text = generate_tweet_text(hilt, blade, pommel)

media = api.media_upload(path)
api.update_status(status=tweet_text, media_ids=[media.media_id,])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If this is your first time setting up a twitter bot you’ll need to &lt;a href="https://developer.twitter.com/apps"&gt;create an application first&lt;/a&gt;, grab your credentials and then store them in an environment variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it all up
&lt;/h2&gt;

&lt;p&gt;Now that everything’s running correctly, I’ve got it generating a unique lightsaber and posting it to Twitter I just have to &lt;a href="https://procrastinatingdev.com/adding-cron-to-a-digital-ocean-droplet/"&gt;add it to a Cron&lt;/a&gt; to run every day.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12 21 * * * python3 lightsaber.py &amp;gt;&amp;gt; lightsaber.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will run the program, every day at 9:12 pm. And with that, we’re done! We’ve got a Python program that generates a unique lightsaber, every single day, and tweets it out. Which Jedi/Sith owns your favourite lightsaber?&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://procrastinatingdev.com/using-python-to-generate-over-10000-unique-8-bit-lightsabers/"&gt;Using Python to generate over 10,000 unique 8-bit lightsabers&lt;/a&gt; appeared first on &lt;a href="https://procrastinatingdev.com"&gt;Procrastinating Developer&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>python3</category>
      <category>imageprocessing</category>
      <category>starwars</category>
    </item>
    <item>
      <title>Migrating our API Gateway from Flask to Django</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Tue, 17 Apr 2018 16:12:56 +0000</pubDate>
      <link>https://forem.com/gadventurestech/migrating-our-api-gateway-from-flask-to-django-56k0</link>
      <guid>https://forem.com/gadventurestech/migrating-our-api-gateway-from-flask-to-django-56k0</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AT-NaIlxpxpB0uvmZ." class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AT-NaIlxpxpB0uvmZ."&gt;&lt;/a&gt;“Fresh pineapple sitting on the ocean shoreline” by &lt;a href="https://unsplash.com/@pineapple?utm_source=medium&amp;amp;utm_medium=referral" rel="noopener noreferrer"&gt;Pineapple Supply Co.&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At G Adventures, we’ve been building a &lt;a href="https://tech.gadventures.com/why-we-created-a-rest-api-at-g-adventures-b9a99abc86d" rel="noopener noreferrer"&gt;publicly accessible API&lt;/a&gt; for the past three years. We’ve been pushing hard to make the G Adventures API a standard within the Adventure Travel industry, and we believe we’re doing so by providing superb documentation &amp;amp; support, as well as enabling our customers with a standards-compliant JSON/XML API, powered by a suite of services and tools.&lt;/p&gt;

&lt;p&gt;When we first tackled the construction of our API, we wanted to keep things simple. We have many systems at G Adventures which own data, and we wanted to ensure that the API would not be an owner of data, simply focused on delivering data.&lt;/p&gt;

&lt;p&gt;Thus, we looked towards frameworks that kept things simple. In the Python ecosystem, one of the most popular frameworks for keeping things simple is Flask.&lt;/p&gt;

&lt;p&gt;Flask is a micro-framework that truly does not get in your way. It ensures you can write plain Python, with additional batteries included. This was great for our needs, as our mandate was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a system that can capture a single request, and transform that into potentially multiple requests to source systems. Effectively, an API Gateway&lt;/li&gt;
&lt;li&gt;Ensure we don’t re-invent the wheel when it comes to HTTP Standards&lt;/li&gt;
&lt;li&gt;Keep it lean and fast. Speed is important. Both from development ramp-up and data delivery perspectives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus we began iterating on our API prototype. Flask truly did not get in our way. In a couple of days, we had a prototype that would proxy a single client request into multiple requests behind-the-scenes. We iterated on this prototype until we came closer to a reasonable product. Building out our resource definitions in pure Python, we were able to define common patterns, and store them as simple dictionaries. We’ll touch a bit more on what this looks like today shortly.&lt;/p&gt;

&lt;p&gt;Eventually, we launched our API. It was a success! We had many internal, and external clients consuming G Adventures data. We began expanding our system by offering more resources, tools like OAuth, payload validation, and so on. As we began to add these tools, we introduced concepts like an ORM (Via SQLAlchemy) and migrations (Alembic) to our infrastructure.&lt;/p&gt;

&lt;p&gt;These aren’t foreign concepts, and the tools mentioned are superb, but we’d began to slide away from our core fundamentals. Our API was becoming larger in scope, we began re-inventing the wheel, and our simple approach of dictionaries for resource definition worked, but didn’t. Essentially, we’ve hit a point where we could identify that if we continue to build upon this micro-framework, we’d build upon our technical debt. This was an opportunity to rethink&lt;/p&gt;

&lt;p&gt;We began to think of how we move forward. We want to keep the API layer simple, and allow other microservices to provide the various features of the G Adventures API, but we wanted to have a consistent, well-structured set of ideas we could leverage and build from.&lt;/p&gt;

&lt;p&gt;And thus, we waved hello to Django Rest Framework. Django Rest Framework is a perfect companion to a system where you have Django Models, and you want to expose an API to them. It’s so simple that most of our internal systems that expose an API to our API Gateway use it. However, we were in a different situation than a typical Django project.&lt;/p&gt;

&lt;p&gt;We were actively running an API service which was receiving millions of hits per week, we wanted to leverage Django Rest Framework, but would need to pivot it to fit our needs of multiple HTTP backend services. Essentially, we had to make Django Rest Framework work in the ways we’ve structured our API. Let’s take a look at what that looked like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrating while the machine is running&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the API, we support 90 resources, each with a different degree of complexity. A read-only resource on one backend is easier to test than a read-write resource using many backends. At the end of the day, migrating more than 5 simple resources at once is asking for trouble. Our decision was to migrate a few at a time and proxy the requests to the appropriate process (Flask or DRF). This allowed us to migrate slowly, communicate with our clients in case of errors and easily route back to Flask if needed.&lt;/p&gt;

&lt;p&gt;Being able to revert to Flask served us well. You can migrate resources with precise respect to the documentation, but that does not guarantee success. Django Rest Framework could reject a request while the Flask framework wouldn’t. For example, Flask will happily process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST to /bookings POST to /bookings/resource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While Django Rest Framework will only accept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST to /bookings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our documentation always said to use bookings. bookings/resource was not prohibited in our documentation, it simply wasn’t mentioned. One of our clients used bookings/resource, it went through and they continued to use it. When we migrated bookings to DRF, the client messaged us about it. Flask overlooked this error but DRF did not. Well, we were surprised bookings/resource was a valid request on our own API. I told the client to remove resource for all our resources. Now imagine we migrated all 90 resources at once — all integrations using &amp;lt;resource&amp;gt;/resource would be sad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customizing our endpoints&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Django Rest Framework, a resource is served by binding an endpoint to a view. For example, to serve bookings on the API, we simply had to register it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.register(bookings, BookingView)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using DRF generic router, this will support requests on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bookings/\&amp;lt;booking\_id\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our Flask supported other URL variations, and one of our challenges was to properly route all existing requests. Here is what we needed to serve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;resource\&amp;gt;/\&amp;lt;id\&amp;gt;/ \&amp;lt;resource\&amp;gt;/\&amp;lt;id\&amp;gt;/\&amp;lt;resource\_2\&amp;gt; \&amp;lt;resource\&amp;gt;/\&amp;lt;id\&amp;gt;/\&amp;lt;variation\_id\&amp;gt; \&amp;lt;resource\&amp;gt;/\&amp;lt;id\&amp;gt;/\&amp;lt;variation\_id\&amp;gt;/\&amp;lt;resource\_2\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we found how DRF parses URL: &lt;a href="https://github.com/encode/django-rest-framework/blob/1c53fd32125b4742cdb95246523f1cd0c41c497c/rest_framework/routers.py#L230" rel="noopener noreferrer"&gt;https://github.com/encode/django-rest-framework/blob/1c53fd32125b4742cdb95246523f1cd0c41c497c/rest_framework/routers.py&lt;/a&gt;&lt;a href="https://github.com/encode/django-rest-framework/blob/master/rest_framework/routers.py#L240" rel="noopener noreferrer"&gt;#L241.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means our bookings/&amp;lt;id&amp;gt; will be valid through DRF’s default regex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base\_regex=’(?P\&amp;lt;{lookup\_prefix}{looking\_url\_kwarg}\&amp;gt;{lookup\_value})’
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;URLs are constructed in get_urls, we can easily overwrite this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def get\_urls(self): urls = super().get\_urls() return urls + \&amp;lt;our custom urls\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our custom URLs will take care of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;resource\&amp;gt;/\&amp;lt;id\&amp;gt;/\&amp;lt;resource\_2\&amp;gt; \&amp;lt;resource\&amp;gt;/\&amp;lt;id\&amp;gt;/\&amp;lt;variation\_id\&amp;gt; \&amp;lt;resource\&amp;gt;/\&amp;lt;id\&amp;gt;/\&amp;lt;variation\_id\&amp;gt;/\&amp;lt;resource\_2\&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s say we had an itineraries resource with ID 2222, a possible variation ID of 3333 and also a nested resource of itinerary_maps. These are all valid requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Variation 1: itineraries/2222/3333 Variation 2: itineraries/2222/itinerary\_maps Variation 3: itineraries/2222/3333/itinerary\_maps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we overwrote this method &lt;a href="https://github.com/encode/django-rest-framework/blob/1c53fd32125b4742cdb95246523f1cd0c41c497c/rest_framework/routers.py#L230" rel="noopener noreferrer"&gt;https://github.com/encode/django-rest-framework/blob/1c53fd32125b4742cdb95246523f1cd0c41c497c/rest_framework/routers.py#L23&lt;/a&gt;0 to add extra regexes, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\&amp;lt;base\&amp;gt; + "/\*(?P\&amp;lt;variation\_id\&amp;gt;[^/]\*)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice in our get_urls method, we call super() before we create our nested URLs. super().get_urls() calls self.get_lookup_regex(viewset) which constructs our variation_ids before constructing our nested URLs (i.e. those ending with &amp;lt;resource_2&amp;gt;).&lt;/p&gt;

&lt;p&gt;The call to super() creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;itineraries/2222 itineraries/2222/3333
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While making our nested URLs, we append these urls, also creating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;itineraries/2222/itinerary\_maps itineraries/2222/3333/itinerary\_maps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By constructing our regex orderly, we are able to create these custom URLs and bind them to the appropriate views.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Routing our backends&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I mentioned backends a few times in this article, but never formally defined them. A backend is what we call one of our internal systems that provides data into the API. Our reservation system provides bookings, our Salesforce piece provides contact data, our operations tool provides tour data and so on. This means our API must know which backend(s) to use for which resource. We did this by defining a set of backends and binding resources (example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Backend: config\_variable = 'B1'

def request: \&amp;lt;initializes a request with the system\&amp;gt; def get\_object: \&amp;lt;uses request to get an object\&amp;gt; def update\_object: \&amp;lt;uses request to update an object\&amp;gt; def create\_object: \&amp;lt;uses request to create an object\&amp;gt;

class Resource: object\_backends = [(READ\_WRITE, 'B1'), (READ\_ONLY, 'B2')]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above declares Resource to read and write from B1 and then use B2. Notice the order, B2 will actually override data from B2 if fields collide. Now we just need to patch DRF to use this structure, and this is easy. Here is just the function to override: &lt;a href="https://github.com/encode/django-rest-framework/blob/7078afa42c1916823227287f6a6f60c104ebe3cd/rest_framework/generics.py#L77" rel="noopener noreferrer"&gt;https://github.com/encode/django-rest-framework/blob/7078afa42c1916823227287f6a6f60c104ebe3cd/rest_framework/generics.py#L77&lt;/a&gt; . We did the same for update_object and create_object, allowing us to select backends for resources. Our Flask application operated the same way, but the code for this functionality was much more complex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now and future of the API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our DRF framework has been serving all resources for over a year now and we could not be happier. While Flask served us well initially, we eventually found ourselves reinventing the wheel. With Flask, all the API basics had to be done from scratch. With DRF, we get basic functionalities and an easy way to add our own. A structured framework allowed us to focus on the actual API while allowing us to slightly customize the framework. DRF is just flexible enough to give us endless possibilities. We are currently customizing the API to route different requests to different reservation systems, stay tuned!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Keep following us on&lt;/em&gt; &lt;a href="https://tech.gadventures.com/" rel="noopener noreferrer"&gt;&lt;em&gt;Medium&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; &lt;a href="https://twitter.com/gadventurestech" rel="noopener noreferrer"&gt;&lt;em&gt;Twitter&lt;/em&gt;&lt;/a&gt; &lt;em&gt;for more snippets of the Tech world according to G!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to help G Adventures change people’s lives and travel the world?&lt;/em&gt; &lt;a href="https://gadventures.com/careers" rel="noopener noreferrer"&gt;&lt;em&gt;Check out all our jobs and apply today&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>api</category>
      <category>softwaredevelopment</category>
      <category>django</category>
      <category>python</category>
    </item>
    <item>
      <title>Why we created a REST API at G Adventures</title>
      <dc:creator>Adam McKerlie</dc:creator>
      <pubDate>Wed, 11 Apr 2018 13:43:55 +0000</pubDate>
      <link>https://forem.com/gadventurestech/why-we-created-a-rest-api-at-g-adventures-1imf</link>
      <guid>https://forem.com/gadventurestech/why-we-created-a-rest-api-at-g-adventures-1imf</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RwOQ2-X7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A8yz3hkO3ynHV2qYGARynJg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RwOQ2-X7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A8yz3hkO3ynHV2qYGARynJg.jpeg" alt=""&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/photos/b18TRXc8UPQ?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Artem Sapegin&lt;/a&gt; on &lt;a href="https://unsplash.com/search/photos/internet?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a senior dev at G Adventures, I’ve had the great opportunity to work on the &lt;a href="https://developers.gadventures.com"&gt;REST API&lt;/a&gt; here. This post is the first of many detailing how we got to where we are.&lt;/p&gt;

&lt;p&gt;Our core business at G Adventures is small group travel, a social enterprise that strives to show the world to our travellers and in turn improve both the lives of our travellers and the lives of those who live at the destinations we visit.&lt;/p&gt;

&lt;p&gt;As one can imagine lots goes into planning a tour on the other side of the planet to ensure everything goes smoothly from a day one. As our business grew so did our various systems. Eventually, it was necessary to have one common interface (or one common language) that our systems could use to talk to each other. We chose REST API which stands for Representational State Transfer and API stands for Application Programming Interface.&lt;/p&gt;

&lt;p&gt;We use this interface to expose our business systems to developers both internal and external. As an example&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jS0hIrM1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AdHoovjeEG3t9rs4hUc8GoQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jS0hIrM1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AdHoovjeEG3t9rs4hUc8GoQ.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;contains all kinds of useful details about our &lt;a href="https://www.gadventures.com/trips/peru-panorama/PPP/"&gt;Peru Panorama&lt;/a&gt; tour, including things such as tour’s itinerary or links to specific departures.&lt;/p&gt;

&lt;p&gt;This means of communication has been a huge factor for our business growth as it allows our partners to integrate with us in a much more cost-effective way (in the past booking a tour with us required the use of a specialized website and often even a phone call). With REST API it’s a few HTTP requests instead.&lt;/p&gt;

&lt;p&gt;Internally it allowed us to integrate our various systems and hide implementation details so that application developers no longer have to have intimate knowledge of every single system that we have. Once a developer becomes familiar with the REST API they can use any of our systems in uniform fashion whether they are interacting with our inventory management system or our customer management system (CRM), or anything else we expose via API.&lt;/p&gt;

&lt;p&gt;The existence of this uniform interface even allowed whole new applications previously not possible. Such as our automated email engine or universal &lt;a href="https://developers.gadventures.com/docs/searching.html"&gt;search functionality&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not long after we launched the API did we discover the need to also be aware of when things change. E.g. if the above Peru Panorama trip no longer starts in Lima, we need to notify the website, our booking engine, all our API clients, who often display the trip on their own websites.&lt;/p&gt;

&lt;p&gt;One way to do this is for every developer to check the above URL at specific intervals (polling). But this isn’t really a good strategy because we run around a thousand different tours with most of them having at least one departure every 10 days. That is a lot of URLs to poll.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhooks (don’t call us we will call you)
&lt;/h3&gt;

&lt;p&gt;Webhooks can be compared to push notifications and are a simple mechanism to inform our API users when something in our ecosystem changes. In other words, rather than our users always checking the above URL to see if anything changed we will let everyone subscribed know if there are changes to the Peru Panorama tour.&lt;/p&gt;

&lt;p&gt;These webhooks enable a universal mechanism of notifications that various business units use to obtain relevant info as soon as possible. Side effects included us being able to keep a replica of our data that we used for uniform searching and caching to increase functionality and responsiveness of our API.&lt;/p&gt;

&lt;p&gt;Our external partners can use the webhooks to observe our inventory, watch for special deals or be immediately informed when anything happens on a booking they made with us.&lt;/p&gt;

&lt;p&gt;I am proud of what we were able to accomplish with our API, but in my next installment I’ll get more technical as the story of REST API at G Adventures is far from finished.&lt;/p&gt;

&lt;p&gt;Stay tuned.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Keep following us on&lt;/em&gt; &lt;a href="https://tech.gadventures.com/"&gt;&lt;em&gt;Medium&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; &lt;a href="https://twitter.com/gadventurestech"&gt;&lt;em&gt;Twitter&lt;/em&gt;&lt;/a&gt; &lt;em&gt;for more snippets of the Tech world according to G!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to help G Adventures change people’s lives and travel the world?&lt;/em&gt; &lt;a href="https://gadventures.com/careers"&gt;&lt;em&gt;Check out all our jobs and apply today&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>restapi</category>
      <category>travel</category>
      <category>softwaredevelopment</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
