<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[JW.PE's RSS Feed]]></title><description><![CDATA[Articles on software engineering, engineering management, and the outdoors by Jon Evans.]]></description><link>https://www.jw.pe</link><generator>GatsbyJS</generator><lastBuildDate>Tue, 04 Aug 2026 18:08:06 GMT</lastBuildDate><item><title><![CDATA[Establishing A Career Framework]]></title><description><![CDATA[A short guide on how to establish a career framework in your engineering organisation.]]></description><link>https://www.jw.pe/blog/establishing-a-career-framework/</link><guid isPermaLink="false">https://www.jw.pe/blog/establishing-a-career-framework/</guid><pubDate>Tue, 04 Aug 2026 19:05:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the projects that I&apos;ve now led twice - once at TravelLocal, and once, more recently, for a client - is the establishment of a software engineering career framework. Nowadays, career frameworks for engineering organisations are ubiquitous, especially in larger teams. However, when I started my software engineering journey in the early 2010s, they had not yet been popularised. I distinctly remember the first time I encountered one, reading &lt;a href=&quot;https://dresscode.renttherunway.com/blog/ladder&quot;&gt;this blog post&lt;/a&gt; by Camille Fournier about her experience introducing an engineering career framework at Rent The Runway. More than anything, I was impressed by the intent behind the framework - providing clarity to team members wishing to progress in their roles.&lt;/p&gt;
&lt;p&gt;In the 10+ years since then, examples of career frameworks have proliferated, with plenty of high-profile examples in the public domain - &lt;a href=&quot;https://handbook.gitlab.com/handbook/engineering/careers/matrix/&quot;&gt;GitLab&apos;s&lt;/a&gt; and &lt;a href=&quot;https://docs.google.com/spreadsheets/d/131XZCEb8LoXqy79WWrhCX4sBnGhCM1nAIz4feFZJsEo/edit?gid=0#gid=0&quot;&gt;CircleCI&apos;s&lt;/a&gt; are two that I&apos;ve referenced myself in the past. There&apos;s even a whole website, &lt;a href=&quot;https://progression.fyi/&quot;&gt;https://progression.fyi/&lt;/a&gt;, dedicated to sharing open examples. In that time, I&apos;ve also come to believe that a well-designed career framework is a key tool in successfully growing an engineering org.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#what-is-a-career-framework&quot;&gt;What is a career framework?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#why-do-we-need-a-career-framework&quot;&gt;Why do we need a career framework?&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#clarity&quot;&gt;Clarity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#consistency&quot;&gt;Consistency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#ambition&quot;&gt;Ambition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-do-we-get-started&quot;&gt;How do we get started?&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#wait-until-you-need-it&quot;&gt;Wait until you need it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#keep-it-simple&quot;&gt;Keep it simple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#copy-and-customise&quot;&gt;Copy and customise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#aim-for-good-enough-then-evolve&quot;&gt;Aim for good enough, then evolve&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#a-note-about-salaries&quot;&gt;A note about salaries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#embedding-the-career-framework&quot;&gt;Embedding the career framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;what-is-a-career-framework&quot;&gt;What is a career framework?&lt;/h1&gt;
&lt;p&gt;To put it simply, a career framework is a document or tool that outlines the expectations for each role in your engineering organisation. These expectations are usually defined as a set of behaviours or competencies that are expected to be fulfilled by a person working in that specific role. Unlike a job description (more on that later), a career framework is a living document that grows and evolves with your organisation.&lt;/p&gt;
&lt;h1 id=&quot;why-do-we-need-a-career-framework&quot;&gt;Why do we need a career framework?&lt;/h1&gt;
&lt;p&gt;It can be tempting to think that establishing a career framework is unnecessary work when your team is small - and if you&apos;re a handful of engineers, you&apos;re probably right. However, a timely introduction of a career framework can make your life as an engineering leader significantly easier.&lt;/p&gt;
&lt;p&gt;To me, a career framework solves three core challenges for your team. These are the challenges of &lt;strong&gt;clarity&lt;/strong&gt;, &lt;strong&gt;consistency&lt;/strong&gt;, and &lt;strong&gt;ambition&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;clarity&quot;&gt;Clarity&lt;/h2&gt;
&lt;p&gt;In many jobs, your job description is the only written record of your expectations in the role. However, job descriptions are designed for a specific purpose - hiring - and so are not optimised for people already in the role. They become outdated as soon as the role is filled. What this means is that starting from day one, a team member has a growing gap around the question &quot;What is expected of me?&quot;&lt;/p&gt;
&lt;p&gt;Good managers will regularly fill this gap in 1:1 conversations, performance reviews, and by defining clear goals for their team members. However, in all of these cases, the context for expectations is largely in the heads of manager and team member. If, as an engineer, I want to clearly establish what I need to do to succeed, I have no quick point of reference. If, as a manager, I want to evaluate the progress and contributions of my team, I have no easy and consistent method of comparison.&lt;/p&gt;
&lt;p&gt;A career framework solves this issue by taking the majority of this context, and making it explicit. A document can&apos;t - and shouldn&apos;t try to - replace the relationship between team member and manager. But by establishing an agreed-upon set of behaviours for each role, it provides a shared understanding and point of reference to answer the key question of &quot;What is expected of me?&quot; This gives agency to team members to own their own performance and progression, and provides a fair and equal baseline for managers to evaluate the success of their team.&lt;/p&gt;
&lt;h2 id=&quot;consistency&quot;&gt;Consistency&lt;/h2&gt;
&lt;p&gt;Fairness leads directly to the second key challenge answered by a career framework. Establishing a shared understanding of what is expected of team members in each role means that everyone is being evaluated against the same, known behaviours. This can go a long way towards preventing a situation in which success in the organisation is predicated purely on the whims of your manager. Nobody wants to work in an organisation where people succeed or fail thanks to some opaque system of assessment that involves pleasing certain key figures - and yet, many orgs operate this way. By putting the expectations out in the open - and using them in processes such as hiring, performance management, and promotions - team members can feel confident that they are aiming for the same benchmark as their peers.&lt;/p&gt;
&lt;p&gt;&quot;But I judge the performance of my team fairly and objectively!&quot; Sorry, but you probably don&apos;t. Unconscious bias is a real phenomenon, and no matter your intentions, it&apos;s all too easy to see one person&apos;s confident decision-making as another&apos;s reckless risk-taking. Establishing clear behaviours beforehand goes some way to eliminating these inconsistent perceptions of the same actions across your team. This is particularly relevant for team members from minority backgrounds, who are disproportionately negatively impacted by opaque hiring or promotion processes.&lt;/p&gt;
&lt;h2 id=&quot;ambition&quot;&gt;Ambition&lt;/h2&gt;
&lt;p&gt;People enjoy challenge and progression in their work. By clearly documenting expectations at each level of your engineering organisation, a career framework gives team members a signpost for what they could achieve within the org. This allows them to own their self-development in a structured way. For example, perhaps a key behaviour for Senior Engineers in your organisation is technical leadership of projects. An aspiring engineer might choose to focus on developing this behaviour by shadowing an existing tech lead, or proactively requesting to take on the tech lead role for a smaller project.&lt;/p&gt;
&lt;p&gt;Without clarity around the roles that exist in the org and what they entail, ambitious team members may feel that their opportunities to grow within the team are limited. This can lead to challenges with retention as high performers seek more well-defined opportunities by taking another job.&lt;/p&gt;
&lt;p&gt;With a career framework in place, conversations with high-performing team members can focus on what behaviours they should develop, not whether opportunities for growth in the team exist.&lt;/p&gt;
&lt;h1 id=&quot;how-do-we-get-started&quot;&gt;How do we get started?&lt;/h1&gt;
&lt;p&gt;Hopefully I&apos;ve convinced you of the value of establishing a career framework for your team. So how do we go about doing that?&lt;/p&gt;
&lt;h2 id=&quot;wait-until-you-need-it&quot;&gt;Wait until you need it&lt;/h2&gt;
&lt;p&gt;It can be tempting to think that before you start hiring for your fledgling engineering team, you need to have a career framework in place. In my experience, making a career framework too early is almost as bad as not making one at all. For one, it may be wasted work - in an early-stage organisation, nothing is guaranteed, and you could almost certainly be focussing on a higher-leverage activity that would increase the org&apos;s chances of success. Secondly, you simply won&apos;t have enough information to clearly define the potential roles in your team. While some behaviours are fairly consistent across organisations, the culture and best practices of your team, and the specific goals of your org will significantly affect what &apos;good&apos; looks like. Trying to anticipate this before you&apos;ve even hired your first team member is unlikely to produce anything that has meaning and relevance once your team has grown and established its ways of working.&lt;/p&gt;
&lt;p&gt;A better time to make a career framework is when you are starting to feel the friction of &lt;em&gt;not&lt;/em&gt; having one. Once you have a few engineers who are working effectively, and the core of the team is established, your focus may turn to developing your existing team or expanding the team with new skills. At this point, questions like &quot;How can I grow in my role?&quot; or &quot;How do we fairly evaluate these candidates for a new hire?&quot; may come to the fore. This is a great time to make a first pass of your framework, knowing that it will have immediate applications.&lt;/p&gt;
&lt;h2 id=&quot;keep-it-simple&quot;&gt;Keep it simple&lt;/h2&gt;
&lt;p&gt;A good career framework provides enough information to give clear direction to the team, but not so much that it&apos;s overwhelming. What that looks like will be different depending on your team size and seniority mix - a team of five engineers with around the same experience needs way less detail than a team of 100 engineers with a wide variety of experience levels. But in all cases, try to keep things a simple as possible. A framework with tens of behaviours at each level simply won&apos;t be read and used by the majority of your team.&lt;/p&gt;
&lt;p&gt;To be concrete about this, when you&apos;re starting out, &lt;strong&gt;make a short list of key behaviours for your most common role&lt;/strong&gt;. These should cover the core activities and responsibilities of the job. If most people are Senior Engineers, define that role first, as you&apos;ll have the most actual experience to draw on in terms of what is working and what isn&apos;t in your team. Structure the behaviours as clear, one-or-two sentence statements, for example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I deliver high-quality, readable code that follows the team’s conventions. I seek feedback through pairing and reviews to improve the quality of my contributions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Behaviours should be things that people &lt;strong&gt;do&lt;/strong&gt;! The framework should consist of actions that it is possible to clearly demonstrate, not intangibles like &apos;has good architecture sense&apos;.&lt;/p&gt;
&lt;p&gt;Once you&apos;ve established a baseline of key behaviours for your most common role, sense check them with those people actually doing that job. Do the behaviours reflect their experience of their day-to-day work? If not, why not? Iterate until you&apos;ve got something that feels true of your org as it is now. Only then should you start to deepen and expand.&lt;/p&gt;
&lt;h2 id=&quot;copy-and-customise&quot;&gt;Copy and customise&lt;/h2&gt;
&lt;p&gt;As mentioned above, you&apos;ll need to have an understanding of your team as a living, working entity to be able to make a useful career framework. However, that doesn&apos;t mean that you have to start completely from scratch. Unless you&apos;re working on something truly unique, or working in a radically different way from every other engineering team out there (and you probably aren&apos;t), the behaviours needed to be an effective software engineer at your organisation are likely to overlap significantly with those at other orgs.&lt;/p&gt;
&lt;p&gt;Take some time to read through the existing state of the art, looking at both industry-leading orgs for inspiration, but also for orgs that are more reflective of your scale and maturity. If you see behaviours that reflect the best practices you feel should exist in your team, feel free to incorporate them. Think about the categories of behaviour that are covered by existing frameworks - does your framework broadly cover the same main aspects of everyone&apos;s job?&lt;/p&gt;
&lt;p&gt;While you may need to customise behaviours to fit into the context of your team, drawing on existing open frameworks will give you a good sense of whether your framework is covering the correct bases.&lt;/p&gt;
&lt;h2 id=&quot;aim-for-good-enough-then-evolve&quot;&gt;Aim for good enough, then evolve&lt;/h2&gt;
&lt;p&gt;As mentioned above, a career framework should be a living document. The documented behaviours should reflect the expectations of the team you have now, not the team you want to have when your org has grown to 1000 engineers. With this in mind, challenge yourself to get something simple and meaningful in front of your engineers as soon as possible. By starting with your most common role, you can validate the approach early and see if it resonates with your existing team members.&lt;/p&gt;
&lt;p&gt;Once you&apos;ve got the framework out in the open in the team, actively solicit feedback while building it out to cover all of the existing roles in your team. For small teams, this might only be two or three roles: Engineer, Senior Engineer, and Staff Engineer, for example. Only once you&apos;ve got clear expectations for each &lt;strong&gt;existing&lt;/strong&gt; role should you start thinking about extending the framework to cover aspects of your hypothetical org chart.  Try to link behaviours from one level to the next - behaviours at Senior level should be a natural progression from those at Engineer, and so on. Some behaviours will be the same in multiple roles, and that&apos;s fine!&lt;/p&gt;
&lt;p&gt;You can evolve your framework in several ways as the situation demands. Maybe you&apos;ve hired your first engineering manager - this could be a good time to split your framework into distinct Individual Contributor and Management tracks. Or you might find your most senior engineer is going above and beyond what&apos;s defined in their role; this could be a signal to introduce a Principal Engineer role for them to aim for. Perhaps you find yourself in a situation where you have lots of team members at the Engineer level, and some clearly have greater responsibilities than others. Maybe you need to start considering if you need multiple intra-role levels (e.g. E1, E2).&lt;/p&gt;
&lt;p&gt;I personally find it helpful to write a short accompanying document alongside the career framework itself, explaining where and how it will be used. This explainer will also evolve as you embed the framework in more aspects of your process.&lt;/p&gt;
&lt;p&gt;Finally, as time goes on, what behaviours are expected of each role will likely evolve as the team does. It&apos;s good to build in a process to review the framework on e.g. an annual basis, to ensure it&apos;s still relevant for the team. The best way for a framework to become obsolete is to let it diverge too far from the reality of your team.&lt;/p&gt;
&lt;h2 id=&quot;a-note-about-salaries&quot;&gt;A note about salaries&lt;/h2&gt;
&lt;p&gt;I am strongly of the opinion that you should align salary bands within your org with your career framework. It simplifies a whole class of difficult management problems around fair compensation if you can confidently state that everyone in a certain role is within a given salary band. Whether you choose to transparently surface these bands alongside the roles, how wide the bands are, and whether the bands between different roles overlap - all of these will depend on your own situation, constraints and preferences. But whether or not the bands are visible to the team, being able to comfortably justify to yourself why someone&apos;s salary is at a certain level relative to their peers will set you up for productive conversations with team members and prospective hires on the topic of remuneration.&lt;/p&gt;
&lt;h1 id=&quot;embedding-the-career-framework&quot;&gt;Embedding the career framework&lt;/h1&gt;
&lt;p&gt;Well done - you&apos;ve done the hard work of designing and rolling out your career framework. Now you have to use it! In future posts, I&apos;ll cover three areas where you can make the career framework a core part of your process: hiring, performance management, and professional development.&lt;/p&gt;
&lt;p&gt;Until then, if you have any thoughts about the post, or questions about establishing your own career framework, drop me an email at &lt;code&gt;work at jw.pe&lt;/code&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[October 2025 Good Things]]></title><description><![CDATA[Better late than never on my October good things!]]></description><link>https://www.jw.pe/blog/october-2025-good-things/</link><guid isPermaLink="false">https://www.jw.pe/blog/october-2025-good-things/</guid><pubDate>Mon, 01 Dec 2025 12:58:00 GMT</pubDate><content:encoded>&lt;p&gt;Better late than never on my October good things!&lt;/p&gt;
&lt;h1 id=&quot;getting-married&quot;&gt;Getting Married!&lt;/h1&gt;
&lt;p&gt;The main reason that I didn&apos;t write this post at the end of last month was that my (now) wife and I were busy getting married! It was absolutely amazing. I felt incredibly lucky: not only to be marrying someone as wonderful and well-suited to me as my partner, but also to be able to do so in the presence of so many people we love.&lt;/p&gt;
&lt;p&gt;There&apos;s so much I could say about the day, and so many incredible memories I will cherish for years to come, but for the moment I am just feeling grateful. The love that we felt from friends and family alike was in some ways overwhelming - it&apos;s not often that you&apos;re the at the centre of attention of everyone you care about - but ultimately very validating. It really reminded me how much I value my relationships, and how investing in them is one of the most worthwhile things I can do.&lt;/p&gt;
&lt;h1 id=&quot;sparks-co-working&quot;&gt;Sparks Co-Working&lt;/h1&gt;
&lt;p&gt;While getting married was very much the priority in October, I did make another positive change during the month - I started working out of a co-working space! I&apos;ve been wanting to do this for some time - working from home every day for more than three years has been quite isolating, and terrible for my focus. I will always advocate for flexible and remote working, as I think people should have the option to work from wherever makes the most sense for them. With that said, losing the ability to work from the office during COVID deprived me of a healthy routine of getting out of the house, getting some fresh air, and getting some free exercise on the way to work.&lt;/p&gt;
&lt;p&gt;I&apos;ve finally rectified this by signing up to work at &lt;a href=&quot;https://sparksbristol.co.uk/&quot;&gt;Sparks Bristol&lt;/a&gt;. For anyone who isn&apos;t familiar - Sparks is a re-imagining of a department store in the old Marks &amp;#x26; Spencer building in Broadmead. Instead of being run by a single company, the building is managed by &lt;a href=&quot;https://globalgoalscentre.org/&quot;&gt;two&lt;/a&gt; &lt;a href=&quot;https://artspace.uk/&quot;&gt;charities&lt;/a&gt;, and the ground floor is occupied by a whole bunch of small retail businesses, charity installations, and the like. It&apos;s a great place to come and shop for art, jewellery, clothing, or homeware from local independent businesses. However, until this month, I wasn&apos;t aware that Sparks also offered desk spaces and artist studio spaces on the second floor of the building. Fortunately, my friend and fellow consultant &lt;a href=&quot;https://simseneca.design/&quot;&gt;Sim&lt;/a&gt; was also looking for a co-working space around the same time as me, and invited me to tag along on his visit to the space.&lt;/p&gt;
&lt;p&gt;To cut a long story short: we both really liked Sparks, and have signed up for desks. The small team who manage the building are really friendly and helpful, the desks in the White Studio (where I work) are extremely affordable at £120 / month, and the space is charming, quirky, and functional.&lt;/p&gt;
&lt;p&gt;It&apos;s hard to imagine what working in a cavernous former department store is like until you try it, but it has something of the energy of being in school after everyone has gone home that I remember from occasions in my childhood. It feels somewhat like you shouldn&apos;t be allowed to be there, like you&apos;re peeking behind the curtain of normal everyday life. It adds an element of fun to coming to work, and because Sparks is very DIY, it feels like there is a lot of flexibility and possibility with how the space can be used.&lt;/p&gt;
&lt;p&gt;For the more practically-minded, here are the facilities that Sparks has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adjustable-height desks (not quite adjustable enough to be standing desks) and office chairs.&lt;/li&gt;
&lt;li&gt;Kitchen with kettle, microwaves, fridges, etc.&lt;/li&gt;
&lt;li&gt;Functional heating (important in a relatively old building with single-glazed windows).&lt;/li&gt;
&lt;li&gt;Good Wi-Fi - I have done plenty of video calls and had no issues whatsoever with bandwidth.&lt;/li&gt;
&lt;li&gt;A secure bike rack indoors, although it does require carrying your bike up stairs, so not great for accessibility considerations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And things I&apos;m not aware it has, which other, more expensive co-working spaces might have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Showers / changing facilities.&lt;/li&gt;
&lt;li&gt;Dedicated meeting rooms - although the space is big enough that you could easily find a space to have a meeting without disturbing anyone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously I am not affiliated with Sparks, and these facilities and prices might change, but I am currently really enjoying working here. I would encourage anyone in Bristol looking for co-working with a twist to come join us! You can enquire here: &lt;a href=&quot;https://sparksbristol.co.uk/hire-a-space/desks/&quot;&gt;https://sparksbristol.co.uk/hire-a-space/desks/&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;running&quot;&gt;Running&lt;/h1&gt;
&lt;p&gt;I&apos;ve been running twice a week pretty consistently since the middle of last year. As exercise goes, running is something that works well for me: low-cost, gets me outdoors, and I can fit a short run into my work day quite easily. In October I passed my annual goal of 60 runs for the year, which felt satisfying.&lt;/p&gt;
&lt;p&gt;Something I really value about my running routine is that it demonstrated my ability to form new, healthy habits. The science of habits is something I&apos;m very interested in, but last year I was in a headspace where positive change felt really difficult, if not impossible. Starting my running habit provided me with concrete evidence that I could make beneficial changes in my life that would stick.&lt;/p&gt;
&lt;h1 id=&quot;other-stuff&quot;&gt;Other Stuff&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;We saw Australian comedy trio &lt;a href=&quot;https://www.auntydonna.com/&quot;&gt;Aunty Donna&lt;/a&gt; at the Hippodrome - completely unhinged and hilarious.&lt;/li&gt;
&lt;li&gt;I read the final part of Philip Pullman&apos;s Book of Dust trilogy, &lt;a href=&quot;https://www.goodreads.com/book/show/228587642-the-rose-field&quot;&gt;The Rose Field&lt;/a&gt;. I devoured it in two sittings, both thanks to the incredible prose, and wanting to discover the ending of a journey starting 30 years ago with Northern Lights. I am still mulling over my opinions on the narrative, but I can say that I found the book compelling and beautiful.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[September 2025 Good Things]]></title><description><![CDATA[Some things that I've enjoyed or found interesting this month.]]></description><link>https://www.jw.pe/blog/september-2025-good-things/</link><guid isPermaLink="false">https://www.jw.pe/blog/september-2025-good-things/</guid><pubDate>Tue, 30 Sep 2025 22:46:00 GMT</pubDate><content:encoded>&lt;p&gt;In my ongoing quest to write more, here are some things I&apos;ve been enjoying this month. If I can, I&apos;ll write one of these each month.&lt;/p&gt;
&lt;h1 id=&quot;hollow-knight-silksong&quot;&gt;Hollow Knight: Silksong&lt;/h1&gt;
&lt;p&gt;This game has taken over my free time since I bought it earlier this month. I loved the original Hollow Knight, with its melancholy, brooding atmosphere, beautiful art, and tough but satisfying gameplay. In fact, I replayed it (including beating The Radiance, which I hadn&apos;t done before) before I allowed myself to buy Silksong, just to get in the right frame of mind.&lt;/p&gt;
&lt;p&gt;Having spent many hours with the game, I can confidently say that Silksong is a worthy successor to the original Hollow Knight. The incredible music and art direction are back, with stunning environments that feel very alive and at times very hostile. The story, focussing on Hornet as the protagonist, ties in satisfyingly with the original game without being limited by it. I won&apos;t go into detail to avoid spoilers, but there are also some incredible twists later in the game that will leave you amazed at the sheer ambition of Team Cherry.&lt;/p&gt;
&lt;p&gt;The biggest change is the gameplay. While the game follows the tried-and-true Metroidvania formula, the world is significantly bigger than Hollow Knight, and the challenges noticeably harder. Having just replayed the original, I found the first Act of Silksong manageable in comparison, but from Act II the difficulty spikes significantly. While bosses are a bit tougher than Hollow Knight, the real difference in challenge for me came from the environment and regular enemies. Benches (save points) are few and far-between, and some of the compulsory platforming sections are extremely tough, interspersed with high-damage enemies for that extra level of risk.&lt;/p&gt;
&lt;p&gt;While the increased difficulty is at times frustrating, the feeling of satisfaction at defeating a particularly powerful boss - or navigating an impossible-feeling platforming sequence - is commensurately higher. Without wishing to spoil anything, I can say that beating the game&apos;s final boss left me suffused with adrenaline and feeling triumphant for hours afterwards.&lt;/p&gt;
&lt;p&gt;Hollow Knight is one of my favourite games ever, and Silksong is as good a sequel as I could have hoped for, so I&apos;d give it a 9.5/10.&lt;/p&gt;
&lt;h1 id=&quot;tame-impala&quot;&gt;Tame Impala&lt;/h1&gt;
&lt;p&gt;Everyone&apos;s favourite one-man psych rock polymath Kevin Parker is back at it again with a new album, &lt;em&gt;Deadbeat&lt;/em&gt;, due out in October. So far there are three singles out, &lt;em&gt;End of Summer&lt;/em&gt;, &lt;em&gt;Loser&lt;/em&gt;, and &lt;em&gt;Dracula&lt;/em&gt;. While the Tame Impala subreddit&apos;s reaction has been decidedly mixed - people are mad that the music isn&apos;t as exploratory and emotionally resonant as his old stuff - I have personally enjoyed all three tracks so far. &lt;em&gt;Dracula&lt;/em&gt; is definitely my favourite, an upbeat dance-pop number about a never-ending party with an extremely catchy chorus.&lt;/p&gt;
&lt;p&gt;Parker also did a wide-ranging and interesting &lt;a href=&quot;https://www.gq.com/story/tame-impala-gq-hype&quot;&gt;interview with GQ&lt;/a&gt; in which he talks about his childhood, his creative process, but also his - as far as it appears to me - intense self-criticism and dissatisfaction with his own achievements. For example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I know that everyone that sees me and my life and my career thinks that I am the furthest thing from being a deadbeat, because I’m successful,” he says in a rush. “But the more success that I have, the more I feel like I’m living a lie. It’s a sham.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&apos;s hard to believe that someone who has singlehandedly created a musical vehicle of the scope and level of success of Tame Impala would feel this way, but perhaps this inner critic has been a part of what has driven his constant reaching for musical growth. Nonetheless, it makes me a little sad that Parker isn&apos;t able to appreciate his achievements for what they are. As someone who struggles a lot with my own inner critic, it is quite humanising to see that even someone as conventionally successful as Kevin Parker faces a similar challenge. For me, it just reinforces the idea that satisfaction can&apos;t come from external validation - it has to come from self-acceptance and making your own meaning.&lt;/p&gt;
&lt;h1 id=&quot;lost-and-grounded-brewery&quot;&gt;Lost and Grounded Brewery&lt;/h1&gt;
&lt;p&gt;A couple of my friends and I went to a fun Oktoberfest event at Lost and Grounded the other day. They had some great live music, and an excellent Festbier on tap. Although they have a somewhat limited range, Lost and Grounded are definitely one of my favourite Bristol breweries - &lt;a href=&quot;https://www.lostandgroundedstore.co.uk/shop/p/keller-pils&quot;&gt;Keller Pils&lt;/a&gt; is a fantastic flagship beer.&lt;/p&gt;
&lt;h1 id=&quot;greens-organise&quot;&gt;Greens Organise&lt;/h1&gt;
&lt;p&gt;Zack Polanski&apos;s recent victory in the Green Party&apos;s internal leadership elections seems to have supercharged the movement somewhat, which is really exciting. We recently surpassed 80,000 members in the party, and Zack is doing a fantastic job of staying in the public eye with a series of high-profile news articles and interviews.&lt;/p&gt;
&lt;p&gt;Meanwhile, &lt;a href=&quot;https://greensorganise.uk/&quot;&gt;Greens Organise&lt;/a&gt; - a membership group within the Green Party with the aim of organising activists to achieve the party&apos;s eco-socialist and left-wing goals - had its first AGM yesterday. It was really energising to see lots of people coming together to discuss how to help the Green Party reach new heights, but also strengthen its ties with the wider Left.&lt;/p&gt;
&lt;p&gt;With Labour currently trying to pander to right-wing scaremongering about immigration, and showing no signs of the boldness needed to improve the lives of people in the UK, the Green Party needs to continue to grow and provide a viable left-wing alternative to stand up for the disenfranchised in society. If you&apos;re already a Green Party member, I&apos;d recommend signing up to Greens Organise. If you&apos;re not - what are you waiting for? &lt;a href=&quot;https://join.greenparty.org.uk/&quot;&gt;https://join.greenparty.org.uk/&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;other-stuff&quot;&gt;Other Stuff&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.next.co.uk/style/st502513/205993&quot;&gt;This rug&lt;/a&gt; - we bought it to put under our bed and it&apos;s very cosy.&lt;/li&gt;
&lt;li&gt;Morbid but quite gripping - &lt;a href=&quot;https://illwill.com/fascism-and-the-spectacle-of-death&quot;&gt;https://illwill.com/fascism-and-the-spectacle-of-death&lt;/a&gt; - an essay on the increasing presence of death in everyone&apos;s awareness due to capitalism, and how an indifference to death can shade into the glorification of death and violence inherent in fascism.&lt;/li&gt;
&lt;li&gt;An essay called &lt;a href=&quot;https://edwardpackard.com/wp-content/uploads/2025/09/Nine-Things-I-Learned-in-Ninety-Years.pdf&quot;&gt;Nine Things I Learned in Ninety Years&lt;/a&gt;, written by the creator of the Choose Your Own Adventure book format.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Website Refresh 2025]]></title><description><![CDATA[A detailed account of how I broke my website, then fixed it again, and maybe made it slightly better.]]></description><link>https://www.jw.pe/blog/website-refresh-2025/</link><guid isPermaLink="false">https://www.jw.pe/blog/website-refresh-2025/</guid><pubDate>Fri, 21 Feb 2025 22:18:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been wanting to write again recently, but I also find myself procrastinating a lot instead of writing. This month, my main source of procrastination has been &quot;fixing&quot; my website - by which I mean breaking it on purpose by upgrading things, and then fixing it again. This is somewhat of a cliché for anyone who maintains their own site. Why actually write when you could just tinker with things that aren&apos;t important to anyone except you? In an attempt to get something valuable out of the process, here&apos;s a &lt;del&gt;short&lt;/del&gt; description of the changes I made, if only for my own benefit when I inevitably forget what I did.&lt;/p&gt;
&lt;h2 id=&quot;step-1-pnpm&quot;&gt;Step 1: pnpm&lt;/h2&gt;
&lt;p&gt;The pulling of the long string began when I opened VS Code, intending to write something for my blog, and noticed that the copyright date was outdated. It was hard-coded to 2024. Since the site is built in &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt;, there&apos;s no reason why I couldn&apos;t alter the footer code to get the current year instead. So I did that, and it was of course very easy. But did I stop there? No, I did not. I thought &quot;Hmm, it&apos;s 2025 now, maybe I should stop using an odd combination of &lt;code&gt;yarn&lt;/code&gt; and &lt;code&gt;npm&lt;/code&gt; to install things and get with the times.&quot; So I switched my package manager to &lt;code&gt;pnpm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I should note that I&apos;ve used &lt;code&gt;pnpm&lt;/code&gt; in other projects, and I like it, it&apos;s great. The concept of not actually having to have a hundred versions of the same JS library stored in various &lt;code&gt;node_modules&lt;/code&gt; is a good one. However, switching to it from &lt;code&gt;yarn&lt;/code&gt; without stopping to think about how that might go is not a good idea. The crucial, and in hindsight, obvious step that I missed was ignoring my &lt;code&gt;yarn.lock&lt;/code&gt; and just reinstalling the project dependencies from &lt;code&gt;package.json&lt;/code&gt; with &lt;code&gt;pnpm&lt;/code&gt;. Literally 1 minute of searching after the fact showed me that I could have simply imported the contents of the lockfile into &lt;code&gt;pnpm&lt;/code&gt; using &lt;a href=&quot;https://pnpm.io/cli/import/&quot;&gt;&lt;code&gt;pnpm import&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Having not done this, and not having aggressively pinned the minor versions of my dependencies in the project definition, when I reinstalled the dependencies, things upgraded. Things upgraded and broke.&lt;/p&gt;
&lt;h2 id=&quot;step-2-gatsby-nightmares&quot;&gt;Step 2: Gatsby nightmares&lt;/h2&gt;
&lt;p&gt;When we were working on a recent project together, and I suggested Gatsby, my friend Sim alerted me to the fact that it was the second-most-disliked tool in the JavaScript ecosystem according to the &lt;a href=&quot;https://2024.stateofjs.com/en-US/libraries/#all_tools_experience&quot;&gt;State of JavaScript 2024 survey&lt;/a&gt;. I was surprised by this, as I&apos;d not really had any issues with it in the past. However, the next few hours made me truly understand where this sentiment comes from.&lt;/p&gt;
&lt;p&gt;I won&apos;t bore you and my future self with too much detail here, but post-reinstall my site would not build. I tracked this down to an issue with an outdated Gatsby plugin I use: &lt;code&gt;gatsby-remark-relative-images&lt;/code&gt; (an unmaintained plugin for... something to do with relative image paths). This plugin requires a very specific version of &lt;code&gt;cheerio&lt;/code&gt;, but doesn&apos;t specify that version in its own &lt;code&gt;package.json&lt;/code&gt;. What is &lt;code&gt;cheerio&lt;/code&gt;?  I&apos;m not sure, but it involves HTML parsing. It turns out that it had changed how it was exported in one of its releases in a way that broke the plugin.&lt;/p&gt;
&lt;p&gt;The upside of this problem was that I learned how to use &lt;code&gt;pnpm&lt;/code&gt;&apos;s &lt;a href=&quot;https://pnpm.io/package_json#pnpmoverrides&quot;&gt;&lt;code&gt;overrides&lt;/code&gt; feature&lt;/a&gt; to pin the version of &lt;code&gt;cheerio&lt;/code&gt; to  &lt;code&gt;0.22.0&lt;/code&gt; for that dependency only. It looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// package.json&lt;/span&gt;
 &lt;span class=&quot;token property&quot;&gt;&quot;pnpm&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;overrides&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;gatsby-remark-relative-images&gt;cheerio&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.22.0&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The downside was I spent about 2 hours debugging the issue, time that I was intending to spend on writing.&lt;/p&gt;
&lt;h2 id=&quot;step-3-more-gatsby-nightmares-feat-hydration&quot;&gt;Step 3: More Gatsby nightmares feat. hydration&lt;/h2&gt;
&lt;p&gt;However, this wasn&apos;t the end of my woes.  After fixing the first issue, my site would build, but on load would flash up with the content and then show an empty screen. Even better, I only discovered this issue when I deployed it to Netlify, since the build worked OK locally. But why?&lt;/p&gt;
&lt;p&gt;As is the fashion, the way Gatsby works by default when it receives a request is to load the static HTML page it has generated in its build step, followed by hydrating a React app to allow interactivity on the page. This is billed as a &quot;static site generator enhanced with &lt;a href=&quot;https://www.gatsbyjs.com/docs/conceptual/react-hydration/&quot;&gt;React Hydration&lt;/a&gt;.&quot; Never mind that my website is completely static and doesn&apos;t need to hydrate a React app.&lt;/p&gt;
&lt;p&gt;In its infinite wisdom, by default Gatsby doesn&apos;t use the same workflow in development as it does in production. The &lt;code&gt;gatsby develop&lt;/code&gt; command used in dev does not do the static HTML generation step, instead using a webpack server (I think?) to serve the pages. This means you get hot reloads and all that good stuff, but it crucially also means that any hydration errors are not seen in development.&lt;/p&gt;
&lt;p&gt;Hydration errors occur when the static HTML loaded doesn&apos;t match the HTML generated by React. This makes React upset. Sometimes it can recover from a hydration error by fully re-rendering the page, but sometimes it can&apos;t.&lt;/p&gt;
&lt;p&gt;To debug these issues, I discovered that &lt;a href=&quot;https://www.gatsbyjs.com/docs/reference/release-notes/v2.28/#feature-flags-in-gatsby-configjs&quot;&gt;setting Gatsby&apos;s &lt;code&gt;DEV_SSR&lt;/code&gt; flag&lt;/a&gt; forces pages to server render on dev, leading to the hydration workflow and at least allowing the errors to be seen locally. In my mind, it&apos;s not very intuitive that you&apos;d need to turn &lt;code&gt;DEV_SSR&lt;/code&gt; on to see a problem that is occurring between a pre-built HTML page and the React hydration step, but here we are.&lt;/p&gt;
&lt;p&gt;My hydration errors were caused by two sets of problems. The one that was completely bricking the site on client load was fixed by following a &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/issues/26785#issuecomment-1366431215&quot;&gt;random person&apos;s suggestion on a GitHub thread&lt;/a&gt; somewhere: installing the latest version of Gatsby&apos;s &lt;a href=&quot;https://github.com/gatsbyjs/reach-router&quot;&gt;modified &lt;code&gt;reach-router&lt;/code&gt;&lt;/a&gt;. It&apos;s still unclear to me why this wasn&apos;t already installed as a dependency of Gatsby itself, but perhaps it was a version mismatch. Once this was installed, the site would actually load correctly on build, but it was still doing a full client re-render to recover from a different hydration error. This somewhat defeats the point of a static site generator, in my opinion.&lt;/p&gt;
&lt;p&gt;The second issue was caused by an image component loading with the alt-text set on the server-side HTML, but empty in the client render. According to React, the &lt;code&gt;alt&lt;/code&gt; prop for this component was empty, even though I could happily log out the property in console.&lt;/p&gt;
&lt;p&gt;At this point, I gave up on trying to fix the hydration issue and instead just upgraded everything (again). This was partly inspired by the fact that Gatsby v5 has a mode called &lt;a href=&quot;https://www.gatsbyjs.com/docs/conceptual/partial-hydration/&quot;&gt;partial hydration&lt;/a&gt;, which basically allows you to turn hydration off for all components that don&apos;t need it. Imagine that, a static site generator that just generates static pages! Anyway, I still haven&apos;t tried this setting, but I&apos;m keen to.&lt;/p&gt;
&lt;h2 id=&quot;step-4-upgrades&quot;&gt;Step 4: Upgrades&lt;/h2&gt;
&lt;p&gt;It turns out that just upgrading everything &lt;em&gt;more&lt;/em&gt; fixed my problems. Replacing the defunct &lt;code&gt;gatsby-image&lt;/code&gt; plugin with &lt;code&gt;gatsby-plugin-image&lt;/code&gt; (great naming convention) made the hydration errors disappear. It also allowed me to delete a bunch of image code hanging around from the original starter repo, so it was a win-win.&lt;/p&gt;
&lt;p&gt;Feeling buoyed by my success, I upgraded Gatsby and its accompanying plugins to &lt;code&gt;v5&lt;/code&gt;. Amazingly, this time nothing broke. So now the site is using a version of Gatsby that works in the Year of our Lord 2025. This should tide me over until the inevitable &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; rewrite 🙃.&lt;/p&gt;
&lt;h2 id=&quot;step-5-bulma-and-styles&quot;&gt;Step 5: Bulma and styles&lt;/h2&gt;
&lt;p&gt;The last thing to upgrade was &lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt;, the CSS framework I use. This was reasonably painless, and moved me away from using &lt;code&gt;node-sass&lt;/code&gt; (bad, deprecated) to &lt;code&gt;dart-sass&lt;/code&gt; (good, modern). Honestly I couldn&apos;t care less what compiles my CSS, but I just want things to keep working. The curse of JavaScript web development is that everything goes out of date immediately and has to be replaced with whatever is new, cool, and popular.&lt;/p&gt;
&lt;p&gt;Anyway, this upgrade also allowed me to update my single &lt;code&gt;scss&lt;/code&gt; file, the aptly-named &lt;code&gt;all.scss&lt;/code&gt;, to remove a bunch of custom styles I no longer needed. The new version of Bulma is modular, and allows customisation of its various components by overriding Sass variables when you import them. Here&apos;s an example for the &lt;code&gt;title&lt;/code&gt; element:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;scss&quot;&gt;&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// all.scss&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$site-accent&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #004225cc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$site-title-family&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Playfair Display&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; serif&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;@use&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bulma/sass/elements/title&quot;&lt;/span&gt; &lt;span class=&quot;token module-modifier keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$title-family&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$site-title-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$subtitle-family&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$site-title-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$subtitle-color&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$site-accent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This took me a while to figure out, but led to a very slimmed-down stylesheet, which I like. The only frustration is that some customisation options that seem like they should have corresponding Bulma variables just don&apos;t (looking at you, default font size). So unfortunately I couldn&apos;t delete my own styles entirely.&lt;/p&gt;
&lt;p&gt;I took the opportunity to make the site look a little nicer and tidier. I&apos;m no designer - check out &lt;a href=&quot;https://simseneca.design/&quot;&gt;Sim&apos;s site&lt;/a&gt; if you want to see what happens when someone who &lt;em&gt;is&lt;/em&gt; a designer makes a personal website - but hopefully it&apos;s at least pretty readable and consistent.&lt;/p&gt;
&lt;h2 id=&quot;step-6-actually-write&quot;&gt;Step 6: Actually write&lt;/h2&gt;
&lt;p&gt;Today we reached the final step of actually writing something, to salvage some meaning from my self-inflicted labours. I have to admit that while the first part of the journey was largely frustrating, I am happy with the end product, so I suppose I shouldn&apos;t begrudge myself going down the rabbit hole.&lt;/p&gt;
&lt;p&gt;If you encounter any similar problems with your Gatsby site, I hope maybe you&apos;ll find a useful solution here. Otherwise, maybe you can take some inspiration to break things and then put them back together again better.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Book Review: In Ascension]]></title><description><![CDATA[A review of In Ascension by Martin MacInnes, as well as my own questions and theories about the plot.]]></description><link>https://www.jw.pe/blog/in-ascension-review/</link><guid isPermaLink="false">https://www.jw.pe/blog/in-ascension-review/</guid><pubDate>Fri, 31 May 2024 16:34:00 GMT</pubDate><content:encoded>&lt;p&gt;I read &lt;em&gt;In Ascension&lt;/em&gt; in March 2024, and at the time I wrote down quite a few thoughts about the book because I enjoyed it so much. I&apos;m now revisiting my notes to attempt to turn them into a coherent review of the book. Note: I&apos;ve tried to keep the review section fairly spoiler-free, but there is some discussion of the pacing and characterisation. The subsequent two sections - questions and theories about the book - are full of spoilers!&lt;/p&gt;
&lt;h1 id=&quot;review&quot;&gt;Review&lt;/h1&gt;
&lt;p&gt;First and foremost, I found &lt;em&gt;In Ascension&lt;/em&gt; very compelling. I read most of the book in a couple of sittings, and found it difficult to put it down. For me, this is an increasingly rare experience as I&apos;ve grown older, and the internet has rotted my attention span, so I always value it very highly.&lt;/p&gt;
&lt;p&gt;A way that &lt;em&gt;In Ascension&lt;/em&gt; stood out for me from other sci-fi that I&apos;ve read recently was the level of mystery. Science fiction often loves an explanation, and I think a mark of good sci-fi for me is getting the right balance of what&apos;s explained and what&apos;s implied. When a book is written well, concepts can be introduced tangentially and explored in their interactions with the characters and plot. &lt;em&gt;In Ascension&lt;/em&gt; took this to an extreme, where some of the ideas remain a mystery (at least to me) throughout the novel. This could have been frustrating in a less well-constructed book, but it worked very effectively with the pacing and plot - it felt like the mysteries and level of understanding I encountered as a reader were mirrored by the protagonist. It wasn&apos;t that the book was failing to explain things that the characters knew, but rather that there were things that couldn&apos;t be known to the characters or the reader, because they were so strange.&lt;/p&gt;
&lt;p&gt;To return to the pacing - one of the most effective aspects of &lt;em&gt;In Ascension&lt;/em&gt; for me was the gradual escalation of risk and strangeness throughout the story. For me, there was a sense of connectedness and seeming inevitability that built both within the plot, and the writing itself. By the denouement, it felt like everything in the book was rushing and growing to the point of conclusion. This was partly offset by a change in perspective late in the novel, but I&apos;m sure this was deliberate - the pulling of the punch, so to speak, just added to the idea that there are some things that can&apos;t be known.&lt;/p&gt;
&lt;p&gt;My one gripe with the book - and it&apos;s a small one -was that I found the protagonist somewhat difficult to empathise with. Leigh, while being an interesting and well-developed character, felt strangely flat. Throughout the story, the only real emotions we see her engage with are those of curiosity and guilt. Her hunger for knowledge of the unknown and the unknowable pulls her forward, and repeatedly wins out over her guilt at the things she has to sacrifice. However, it&apos;s hard to find much depth in these sacrifices, since ultimately Leigh doesn&apos;t actually seem to want the connections and relationships she&apos;s giving up - instead, the source of the guilt is feeling that she &lt;em&gt;should&lt;/em&gt; care more about them.&lt;/p&gt;
&lt;p&gt;It&apos;d be interesting to know if this characterisation was deliberate on the part of the author, or rather just not an area of focus in an otherwise extremely tightly-written novel. Unfortunately, I missed a Q&amp;#x26;A with Martin MacInnes at my local bookshop, so I may never know.&lt;/p&gt;
&lt;p&gt;I hope that it&apos;s fairly evident that I&apos;d wholeheartedly recommend &lt;em&gt;In Ascension&lt;/em&gt;. I think it appeals both to regular sci-fi readers, and to those who enjoy being drawn into a story where the veil of understanding is never fully lifted.&lt;/p&gt;
&lt;h1 id=&quot;questions&quot;&gt;Questions&lt;/h1&gt;
&lt;p&gt;Having finished the book, I was left with loads of questions. I&apos;ve collected some of these, stream-of-consciousness style, below. &lt;strong&gt;Spoilers for the whole novel follow.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is the significance of the vent that the characters visit on the ship? Is it really deeper than anywhere else in the ocean? Why do the divers experience euphoria and a subsequent illness on entering the water? If it&apos;s the deepest part of the ocean yet discovered, why is it not further explored by the end of the book?&lt;/li&gt;
&lt;li&gt;What is the alien artefact? Where does it appear from - I think this may be mentioned but I&apos;d need to reread to check. Is the artefact related to the vent? Did it come from the vent? What do the concentric oval patterns mean? Perhaps the weirdest thing - how does it communicate with the dreams of scientists / engineers to pass on knowledge of the propulsion system?&lt;/li&gt;
&lt;li&gt;Archaea and algae are big themes throughout. Why does the algae grow wildly after the acceleration of the spacecraft? What is the gene from the vent that is spliced into the algae?&lt;/li&gt;
&lt;li&gt;Is it the acceleration of the spacecraft that messes with the bodies of Leigh and her other crew members? Is it some side-effect of the propulsion system? Or is it to do with leaving the heliosphere?&lt;/li&gt;
&lt;li&gt;There&apos;s some ominous talk about the Great Filter being either space travel itself - travelling in interstellar space is impossible / destructive to a species - or what you find as a result of space travel. No conclusions here other than that humans&apos; first attempt was unsuccessful, but it doesn&apos;t appear to have destroyed the species immediately.&lt;/li&gt;
&lt;li&gt;It&apos;s heavily implied that the crew travel backwards in time before / while returning to Earth. Is this a consequence of leaving the heliosphere? Or is it something to do with the alien artefact?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;theories&quot;&gt;Theories&lt;/h1&gt;
&lt;p&gt;While I really enjoyed the experience of reading &lt;em&gt;In Ascension&lt;/em&gt;, I also think it&apos;s fun to speculate and discuss what everything meant - to attempt to make sense of some of the mysteries that linger at the end of the novel. With this in mind, I looked at some reviews to see if anyone had any good ideas for what was happening. Once again, &lt;strong&gt;spoilers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The best clue I got for building my own understanding was from someone who referenced the description of salmon in the book. There&apos;s a focus on salmon returning to their spawning grounds, and their bodies disintegrating after they&apos;ve laid their eggs to provide nutrients for their unborn children. This is a real phenomenon, although I&apos;m not sure about the nutrient bit. This, along with the strong emphasis on cycles throughout the book, provides a possible explanation for events.&lt;/p&gt;
&lt;p&gt;Leigh and her crewmates are participating in the &apos;life cycle&apos; of life on Earth, although this life cycle takes place in a non-linear way through time. A lot of focus, early in the book, is put on the significance of the moment that archaea and bacteria combine to make eukaryotic life. The suggestion could be that the crew&apos;s return to Earth, with the rapidly flourishing algae taking over their ship and bodies, is the event that precipitates the creation of eukaryotes. In this cyclic version of events, the eventual evolution of humans and their travel to space is both the cause and the effect, with the journey acting as the completion of a loop. This lines up strongly with the scant evidence we have of what happens to the crew post &apos;The Event&apos;. Their estimated &quot;when&quot;, from looking at the stars, is 2 billion years ago, fitting with our knowledge of when eukaryotes first appear. The deterioration of the crew&apos;s bodies as the algae become more vital mirrors the salmon, reinforcing the idea of them as &apos;parents&apos; bringing new life to Earth, their jobs done once the journey has taken place.&lt;/p&gt;
&lt;p&gt;While this is a satisfying theory to me, it by no means explains everything in the story. The significance of the vent is still unclear, as is the role of the alien artefact. It serves as the catalyst for the journey by providing the propulsion system to humans, but is it also the mediator for the physical transformation that the crew undergo, or their travel through time on leaving the heliosphere?&lt;/p&gt;
&lt;p&gt;One implication of the theory, at least for me, is that life on Earth is coming to a conclusion in the current time. This fits well with the backdrop of environmental degradation that the story is set against, as well as the increasing ill-effects that we see Helena and her children experiencing in the final chapters. In this reading, the mission can be seen as the final significant event of the planet in linear time, sowing the seeds for its own &apos;renewal&apos;. Anything afterwards is a sort of post-script. It&apos;s not the case that humans immediately cease to exist, but more the sense that a tipping point has been reached from which there is no way back.&lt;/p&gt;
&lt;p&gt;Do you have any interesting theories about the meaning of the book? If so, I&apos;d love to hear them.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Consulting Update - March 2023]]></title><description><![CDATA[A quarterly update on my software engineering and engineering management consulting business.]]></description><link>https://www.jw.pe/blog/consulting-update-march-2023/</link><guid isPermaLink="false">https://www.jw.pe/blog/consulting-update-march-2023/</guid><pubDate>Wed, 22 Mar 2023 19:16:00 GMT</pubDate><content:encoded>&lt;p&gt;Somehow, it&apos;s already March 2023, and past time for another update on the progress of my consulting business. Excitingly, it&apos;s going pretty well so far!&lt;/p&gt;
&lt;p&gt;After my &lt;a href=&quot;https://www.jw.pe/blog/consulting-launch-2022/&quot;&gt;launch in November&lt;/a&gt;, my focus for the last two months of 2022 was on building a basic version of the business, and promoting myself in order to generate inbound work. In 2023, I&apos;ve shifted gears to focus on longer-term client work. So far the project I&apos;m working on has been great fun, and has accelerated my timeline for achieving my initial business goals. Naturally, however, this focus on client work has impacted my available time for writing content and promoting my brand - hence the quarterly, rather than monthly, update.&lt;/p&gt;
&lt;h2 id=&quot;goals&quot;&gt;Goals&lt;/h2&gt;
&lt;p&gt;Taking stock of my initial business goals, progress has been promising. This gives me a good footing for continuing with the consulting experiment, and increases my confidence that moving into the world of self-employment was the right choice.&lt;/p&gt;
&lt;h3 id=&quot;️-create-sustainable-revenue&quot;&gt;&lt;strong&gt;♻️ Create sustainable revenue&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;em&gt;Reach an average revenue of £5000 / month&lt;/em&gt; - With the client work I&apos;ve taken on so far in 2023, I&apos;ve managed to hit my initial revenue goal from February onwards. However, in order to maintain a steady revenue stream, I need to continue to broaden and diversify my client base.&lt;/li&gt;
&lt;li&gt;✅ &lt;em&gt;Maintain a 4-day work schedule&lt;/em&gt; - I&apos;ve kept my client commitments within my 4-day work schedule. Having now maintained this schedule since the launch of my consultancy, I feel very validated to find that it works effectively for me and for clients. The shorter working week means that I&apos;m focussed and energised each day, and I feel that I&apos;m delivering a lot of value as a result. The three days away from work give me plenty of time to rest, sort out non-work chores and responsibilities, and enjoy time with my friends and family.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-help-organisations-that-align-with-my-values&quot;&gt;&lt;strong&gt;🌱 Help organisations that align with my values&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;em&gt;100% of clients pass the &apos;credible argument for good&apos; test&lt;/em&gt; - The long-term client project I&apos;ve been working on this quarter is in the renewable energy sector, and so comfortably meets this metric. I&apos;m continuing to use this metric as a barometer for taking on new work.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-build-a-reputation-for-excellence&quot;&gt;✨ Build a reputation for excellence&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;100% of clients would like to work with me again&lt;/em&gt; - I don&apos;t have enough data here because I haven&apos;t completed work with many clients yet. I&apos;m doing my best to make sure that my current clients would answer yes to this question, and make this metric 100% from the start.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;X% of new work comes from client recommendations&lt;/em&gt; - The long-term project that I&apos;ve been working on recently came to me via my network from previous full-time roles. While I wouldn&apos;t count this as a client recommendation, it&apos;s always nice when previous colleagues are excited to work with you again. I imagine it will take a while for me to settle on a reasonable target for this metric, but I&apos;ll keep it in mind when finishing work with clients.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;projects-and-initiatives&quot;&gt;Projects and initiatives&lt;/h2&gt;
&lt;p&gt;As mentioned above, I&apos;ve been focussed on client work recently. The long-term project that I&apos;m working on has a heavy focus on software engineering and architecture design, which has been refreshing and fun after several years of more leadership- and management-oriented work. It&apos;s gratifying to come back to coding and realise that you still know a few things.&lt;/p&gt;
&lt;p&gt;There&apos;s some good writing out there on the value of moving back and forth between engineering and leadership roles - see &lt;a href=&quot;https://charity.wtf/2017/05/11/the-engineer-manager-pendulum/&quot;&gt;The Engineer/Manager Pendulum&lt;/a&gt; as an example. I definitely feel the benefit of having been in an engineering leadership role when working on code - I find it much easier to have some emotional distance from the work I&apos;m creating, and I&apos;m much more implementation-agnostic than I used to be, because I care more about my clients&apos; business outcomes than the code itself. This isn&apos;t to say that I no longer care about code quality or good architecture; I&apos;m just more aware that it&apos;s possible to trade off, for example, an elegant implementation in order to gain operational simplicity. I also feel a lot more empathy for non-engineering stakeholders than perhaps I did earlier in my career, and my skills for engaging across different business functions - for diplomacy, if you will - have been helped by my time in leadership.&lt;/p&gt;
&lt;p&gt;Outside of client work, I&apos;ve been slowly improving the mechanics of my business. My instinct is to over-engineer processes, so I&apos;ve been trying to deliberately keep things simple. I&apos;m relying a lot on &lt;a href=&quot;https://www.notion.so/&quot;&gt;Notion&lt;/a&gt; to organise myself, from keeping daily notes to generating and tracking invoices.&lt;/p&gt;
&lt;h2 id=&quot;challenges&quot;&gt;Challenges&lt;/h2&gt;
&lt;p&gt;My content writing has fallen by the wayside due to my focus on client work. I&apos;m keen to write the (hopefully shorter) counterpart to my article on &lt;a href=&quot;https://www.jw.pe/blog/self-development-gaining/&quot;&gt;self-development as an early-stage engineering leader&lt;/a&gt;, as well as turning another half-dozen ideas I have floating around in my brain into articles. Finding a way to set aside consistent time to develop my content and brand, alongside client work, is a challenge I&apos;m keen to solve in the next few months.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;With the end of Q1 fast approaching, and Q2 looking fairly busy with existing clients, it&apos;s likely that project work will continue to be front and centre for the next few months. Alongside that, I&apos;ll aim to find a sustainable way to keep raising the profile of the consultancy. If you&apos;d like to work together in the second half of the year, now is a great time to get in touch with me. You can reach me by email at &lt;code&gt;work at jw.pe&lt;/code&gt;, or on &lt;a href=&quot;https://www.linkedin.com/in/jwpe&quot;&gt;LinkedIn&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/jwpe&quot;&gt;Twitter&lt;/a&gt; or &lt;a href=&quot;https://climatejustice.social/@jwpe&quot;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Consulting Update - December 2022]]></title><description><![CDATA[It’s been one month since I announced my consulting launch, so I thought I’d check in with an update on my progress so far.]]></description><link>https://www.jw.pe/blog/consulting-update-december-2022/</link><guid isPermaLink="false">https://www.jw.pe/blog/consulting-update-december-2022/</guid><pubDate>Tue, 06 Dec 2022 14:27:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s been one month since I announced my &lt;a href=&quot;https://www.jw.pe/blog/consulting-launch-2022/&quot;&gt;consulting launch&lt;/a&gt;, so I thought I’d check in with an update on my progress so far. I’m hoping to do this on a monthly basis, in order to stay true to my aim of building in the open.&lt;/p&gt;
&lt;h2 id=&quot;goals&quot;&gt;Goals&lt;/h2&gt;
&lt;p&gt;It’s still very early days for my experiment, so I haven’t got much to report on the goal front, but as a reminder, here are the goals I set myself:&lt;/p&gt;
&lt;h3 id=&quot;️-create-sustainable-revenue&quot;&gt;&lt;strong&gt;♻️ Create sustainable revenue&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Reach an average revenue of £5000 / month -&lt;/em&gt; I’ve been spending the last month trying to build my brand, and I have yet to make a dent in my revenue goals, so no update here. One interesting side effect of posting this revenue goal is that I’ve had some people make the mistake of trying to use it to reverse-engineer my day rate. Please don’t do this, as you’ll only be disappointed when it comes to the contract process. I see this as an initial revenue milestone to prove the validity of the business, nothing more or less.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Maintain a 4-day work schedule -&lt;/em&gt; I’ve found the lack of structure at this early stage of the business quite challenging, as I’ll discuss below. Nonetheless, I’ve been pretty good at keeping my work within these limits - the flexibility of choosing your own work time is nice, when working on ‘internal’ work such as brand building.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-help-organisations-that-align-with-my-values&quot;&gt;&lt;strong&gt;🌱 Help organisations that align with my values&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;100% of clients pass the &apos;credible argument for good&apos; test -&lt;/em&gt; This has been a useful way to screen initial approaches for work, so I’ll continue to use this metric.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-build-a-reputation-for-excellence&quot;&gt;✨ Build a reputation for excellence&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;100% of clients would like to work with me again&lt;/em&gt; - While I wouldn’t call them clients, it was really gratifying to see some of the responses and endorsements from my network when I initially announced this experiment. I’m very grateful to those people I’ve worked with in the past who boosted my announcement, or left positive feedback.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;X% of new work comes from client recommendations&lt;/em&gt; - It’s too early days to have a number for this, but some of the work I am exploring is as a direct result of previous roles, so this is starting out well.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;initiatives&quot;&gt;Initiatives&lt;/h2&gt;
&lt;p&gt;Over the last month, my main focus has been on promoting the existence of my consulting experiment, as well as trying to build my reputation through writing relevant content.&lt;/p&gt;
&lt;h3 id=&quot;promotion&quot;&gt;Promotion&lt;/h3&gt;
&lt;p&gt;The soft launch of the consultancy centred on blog content promoted through social media channels. I put out my launch article on LinkedIn and Twitter. Of those channels, LinkedIn was by far the most effective, resulting in around 70 reactions, comments and reposts, and approximately 3000 impressions. Twitter, by contrast, fell very flat, with single-digit engagement and only a couple of hundred impressions.&lt;/p&gt;
&lt;p&gt;I think that the issue here is that Twitter requires a very different posting strategy to LinkedIn. Instead of long-form content, effective use of Twitter seems to rely on drip-feeding interesting content via regular threads and tweets. Most of these might not land, but the occasional one will blow up and drive a lot of engagement. I’ve always been a fairly low-posting Twitter user, so I struggle with this pattern. However, given that Twitter has been undergoing a fairly rapid transformation into a dreadful tyre-fire thanks to the Musk acquisition, I’m leaning towards not using it as a platform for work promotion in future.&lt;/p&gt;
&lt;p&gt;The other avenue for promotion that I’ve relied on is word-of-mouth discussions with friends and ex-colleagues. This is a smaller but much more engaged group of people, and I’m hoping one that will continue to yield useful results and contacts.&lt;/p&gt;
&lt;h3 id=&quot;content&quot;&gt;Content&lt;/h3&gt;
&lt;p&gt;Aside from these update posts, I’ve been working on some more long-form content around engineering management and team growth. A couple of weeks ago, I published an article about &lt;a href=&quot;https://www.jw.pe/blog/self-development-gaining/&quot;&gt;self-development as an early-stage engineering leader&lt;/a&gt;. It ended up growing to a slightly unwieldy size, and took longer to finish than I’d hoped, but I was happy with the content. Disappointingly, it didn’t seem to resonate with my (admittedly small) audience. I have a couple of theories why this might have been the case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Too long&lt;/strong&gt; - The article wasn’t short enough to digest in five minutes. Even though I provided a TL;DR summary at the end, it’s possible people were put off by the volume of content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Too generic&lt;/strong&gt; - While I hope the advice in the article was useful, perhaps it wasn’t specific or engaging enough for my audience to apply easily.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not enough reach&lt;/strong&gt; - It could be that the article just didn’t find the right people to read it, because I have a small audience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m going to continue to experiment with this kind of content, but I’m really interested in any feedback - &lt;strong&gt;please, if you like or dislike any of my content, or think it could be improved in some way, I’d love to hear about it&lt;/strong&gt;. Critical feedback is particularly welcome and valuable.&lt;/p&gt;
&lt;h2 id=&quot;challenges&quot;&gt;Challenges&lt;/h2&gt;
&lt;p&gt;The biggest challenge I’ve experienced with my consulting experiment is consistently motivating myself to develop the business. I know from experience that I work effectively when I have extrinsic motivations - for example, accountability to an employer, a colleague, or a client. However, finding that inner drive to consistently do the work needed to get clients in the door - when the only person I’m accountable to is myself - doesn’t come easily to me. I’m very much of the mindset that consistency and intrinsic motivation are learnable skills, but I’m going to need to keep practicing them to make consulting a success.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;I’m going to continue building the brand and looking for potential clients for the next month. With the end of the year approaching, I’m conscious that many businesses will be winding down work and preparing for a big start in 2023. That said, for organisations with budget windows following the calendar year, some teams will be in a ‘use it or lose it’ situation with remaining budget - if that’s you, and you want to get a few days of useful consulting booked in before the holidays, let me know.&lt;/p&gt;
&lt;p&gt;I’ll keep on writing valuable content around my areas of expertise - I’m approaching this through the lens of “What would I have found useful to read when I was getting started in my engineering management career?” I’m also thinking about local organisations that I can work with to increase my profile within the Bristol tech community, as well as how I can make better use of in-person meetups and events to find potential clients.&lt;/p&gt;
&lt;p&gt;As a reminder, if you’d like for us to work together, you can contact me at &lt;code&gt;work at jw.pe&lt;/code&gt;, or send me a message through another medium if we&apos;re already in touch. I’ll look forward to hearing from you!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Self-Development As An Engineering Leader: Gaining]]></title><description><![CDATA[A key factor in your ability to successfully grow an engineering team is your capacity for self-development. In this article, we’ll focus on the question: “What do I need to gain to continue to be a good leader as my organisation grows?”]]></description><link>https://www.jw.pe/blog/self-development-gaining/</link><guid isPermaLink="false">https://www.jw.pe/blog/self-development-gaining/</guid><pubDate>Mon, 14 Nov 2022 13:36:00 GMT</pubDate><content:encoded>&lt;p&gt;A key factor in your ability to successfully grow an engineering team is your capacity for self-development. Over the course of scaling from zero to a team of double figures, your responsibilities and priorities will change dramatically. This may feel uncomfortable, as the skills that make you effective in your role as the sole engineer on the team will only be marginally helpful in managing a team of ten people. In fact, holding on too tightly to your original role and responsibilities will likely stifle the growth of your team and hold you back from being an effective leader.&lt;/p&gt;
&lt;p&gt;With this in mind, perhaps the most enduring skills that will help you on the journey are humility and a desire to learn. You&apos;ll often find yourself facing new situations where you won&apos;t immediately know the right way to proceed, and being able to admit this to yourself - and others - is very important. However, as a leader, your team will be relying on you to provide a path forward, so you&apos;ll need to be able to educate yourself quickly in order to make (or facilitate) a decision. The good news is that if you&apos;re reading about engineering leadership, you&apos;ve already demonstrated that you&apos;re keen to learn and able to recognise that you may not have all the answers.&lt;/p&gt;
&lt;p&gt;I like to consider self-growth in the context of engineering leadership through two lenses. These can be summed up as &lt;strong&gt;&quot;What do I need to gain?&quot;&lt;/strong&gt; and &lt;strong&gt;&quot;What do I need to let go of?&quot;&lt;/strong&gt;. Firstly, what are the areas I need to build on to lead my team and help the organisation succeed? This typically means developing new skills, gaining new knowledge and nurturing relationships. Secondly, and just as importantly, what are the things that I should let go of in order to maximise effectiveness and develop my team? I believe this question is often overlooked, but letting go of things is a powerful and necessary part of self-growth. This could be delegating responsibility to someone on your team in order to provide them with a learning opportunity, deprioritising an aspect of your role that isn&apos;t a valuable use of your time, or even letting go of a way of thinking or mental model that has become counterproductive. In this article, we’ll focus on the first of those two questions: “What do I need to gain to continue to be a good leader as my organisation grows?”&lt;/p&gt;
&lt;h2 id=&quot;develop-relevant-skills&quot;&gt;Develop relevant skills&lt;/h2&gt;
&lt;p&gt;A critical challenge for budding software leaders that feels so obvious that it shouldn’t need stating, but is frequently overlooked by organisations, is this: &lt;strong&gt;being a good software engineer does not automatically make you a good manager or leader.&lt;/strong&gt; Management and leadership are their own disciplines with their own sets of skills, which are broadly orthogonal to engineering skills. For new organisations with little experience of running a technology team, it’s common for engineers to be hired or promoted largely on the basis of their technical skill. This can lead to situations where an effective engineer is turned into an ineffective manager, at a net loss to the individual and the organisation.&lt;/p&gt;
&lt;p&gt;As the leader of a new or growing engineering team, you can set yourself up for success by approaching the running of a team as a new problem domain with new skills to be learnt. With leadership skills like effective communication and empathy, there’s often a (largely mistaken) view that they are innate, rather than learnable. Certainly, if you’re someone to whom skills like listening or reading others’ emotions comes easily, you’ll have a head start. But even if you find these areas challenging, practicing and soliciting feedback will allow you to improve, just like any other skill.&lt;/p&gt;
&lt;p&gt;It may be helpful to identify a peer or mentor who can help you practice and give you honest feedback - for example, through conducting mock 1:1s, or roleplaying challenging situations. Look around at your organisation or network for someone who you identify with being a ‘good’ leader, and see if they’re willing to share some time to help you grow. Most likely, they’ll find this experience beneficial too, and so will be happy to help.&lt;/p&gt;
&lt;p&gt;Another way to develop these skills is through reading, watching and listening to experts. As the technology industry continues to grow and mature, we’re being blessed with a variety of great books about engineering management. There’s also a conference organisation, &lt;a href=&quot;https://leaddev.com/&quot;&gt;LeadDev&lt;/a&gt;, which focusses exclusively on the skills and challenges of engineering leadership. There’s so much great content out there - it’s just a matter of going out and finding it.&lt;/p&gt;
&lt;p&gt;The skills that make a good technology leader are many and varied, but some areas that I’d really recommend focussing on as your team grows are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Listening effectively&lt;/li&gt;
&lt;li&gt;Providing positive and critical feedback&lt;/li&gt;
&lt;li&gt;Managing conflict&lt;/li&gt;
&lt;li&gt;Speaking and writing engagingly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on your previous experience, these may not be skills that you’ve approached as ‘things I can learn to be better at’. In your role as a leader, now is a great opportunity to start (or continue) cultivating these skills to make your team happier and more successful.&lt;/p&gt;
&lt;h2 id=&quot;broaden-your-knowledge&quot;&gt;Broaden your knowledge&lt;/h2&gt;
&lt;p&gt;When you’re the sole engineer on a project - especially a greenfield project, as is common in start-ups - you’re responsible for every aspect of the technology. As a result, you probably know all of the ins and outs of your codebase, all of the implementation details behind key features, and so on. You’re coding day in, day out, so you’re constantly exposed to the evolution of languages, frameworks, and libraries that power your product. As you work, your understanding of these tools grows. You revisit and evolve your assumptions and mental models, improving your design patterns over time.&lt;/p&gt;
&lt;p&gt;This deep technical knowledge and constant learning is part of the joy of being a subject matter expert. However, as your team grows and your responsibilities evolve, the importance of technical knowledge at this level of detail will decrease. In its place, knowledge about systems, organisations and best practices will become more valuable and relevant.&lt;/p&gt;
&lt;h3 id=&quot;zooming-out&quot;&gt;Zooming out&lt;/h3&gt;
&lt;p&gt;In practical terms, this may mean shifting your learning focus away from keeping up with every aspect of your stack, and instead exploring more abstract concepts. &lt;a href=&quot;https://lethain.com/systems-thinking/&quot;&gt;Systems thinking&lt;/a&gt; is a useful way of modelling both technical and organisational problems. Thinking about process development - how to intentionally design the way your team works to achieve the results you want - becomes increasingly important as your team scales beyond a few people.&lt;/p&gt;
&lt;p&gt;One way to think of this shift in focus is as a zooming-out process, from the detail level to the outcome level. While you may still be the subject matter expert in some areas, in others, you’ll be thinking less about the implementation and more about the inputs and outputs. One pleasant benefit I’ve experienced from this process is being able to let go of feelings about the ‘right’ way to do things, rooted in your own biases. When you’re looking at a more outcome-focussed view of technical areas, you’ll likely be much more agnostic to how the result is achieved. This leads to increased trust in your team members, and empowers them to take ownership over the details of their work.&lt;/p&gt;
&lt;p&gt;That said, even in areas where you’re no longer the technical authority, it’s wise to keep engaged with broad trends. This will allow you to have meaningful discussions with the people who have taken on the responsibility for this area. Even being able to ask the right questions will help the team to come to conclusions on big decisions, such as what technology to explore, or when to build vs buy. Showing humility and a willingness to learn from your team is one of the most effective ways to stay engaged with areas that you’re no longer working on directly. This approach has the added benefit of developing team members’ communication and teaching skills.&lt;/p&gt;
&lt;h3 id=&quot;looking-beyond-engineering&quot;&gt;Looking beyond engineering&lt;/h3&gt;
&lt;p&gt;As you shift time away from technical detail, it’s important to broaden your knowledge outside your fledgling team. When you’re the sole coder and your entire organisation is a handful of people, it’s easy to stay current with other areas of work simply through osmosis. As growth occurs across the organisation, you’ll need to be more intentional about maintaining an awareness of the work and goals of other teams. Part of your responsibility as a leader is to act as an effective conduit between your team and the rest of the organisation, broadcasting information both internally and externally (while avoiding becoming a communication bottleneck!) If you don’t spend time learning about other aspects of the organisation’s work, you won’t be able to accurately represent needs and interests that impact on your team. This learning will also allow you to identify opportunities where your team can contribute more broadly to the success of the organisation.&lt;/p&gt;
&lt;p&gt;Related to, but distinct from, understanding other teams’ work, is understanding the growth levers of your organisation. For revenue-driven organisations, this means understanding what aspects of the organisation can be changed to increase revenue. For example, an online sales business will have levers around conversion rate, customer re-purchase rate, customer volume, order value and so forth. Having clear knowledge of these levers will help to inform your decision-making as a leader, and also make it much easier to understand the motivations behind other decisions in the organisation. They can be helpful to identify areas for opportunity, and also to predict likely challenges that will arise as the organisation grows. Your conversion rate may be high when targeting highly-engaged early adopters, but will that conversion rate be sustainable as you address a larger market? It may seem obvious to know the basics of how your organisation fulfils its goals, but it can be surprisingly easy when you’ve been in the weeds of software development to lose sight of these levers. Staying in touch with them as the organisation grows is a key part of your own leadership growth.&lt;/p&gt;
&lt;h2 id=&quot;invest-in-your-relationships&quot;&gt;Invest in your relationships&lt;/h2&gt;
&lt;p&gt;A critical aspect of self-growth in a growing organisation is making sure that you invest in your relationships. If you haven&apos;t built trust and good communication with your organisation&apos;s leadership, your peers and your team, then you won&apos;t be able to take advantage of your own skills and knowledge to effect change.&lt;/p&gt;
&lt;p&gt;The first step in this process is simply to make sure that avenues of communication exist. Hopefully you work in an organisation where the concept of 1:1s is already established, but if not, start establishing it. Ask your manager (if you have one in your role - if not, it may be useful to find a mentor) for a regular catch-up, and schedule catch-ups with your direct reports. You may also find it valuable to schedule 1:1 time with other leaders in the organisation.&lt;/p&gt;
&lt;h3 id=&quot;team-relationships&quot;&gt;Team relationships&lt;/h3&gt;
&lt;p&gt;From my experience, 1:1s with your team are one of the most valuable ways you can spend your time as a manager and leader. There’s a plethora of great advice out there about how to run effective 1:1s, and there are many valid approaches. My philosophy around 1:1s has always been that they’re a time that belongs to your team, and as such each 1:1 should be driven by what works for that team member. Maybe they prefer to bring an agenda of topics they’d like to discuss, or maybe they prefer you to drive the conversation. Sometimes they might want to dive deep into an aspect of their work and get your advice, and other times they might just want to vent and get something off their chest. There’s definitely not enough space here to cover the details of a good 1:1, but some key outcomes that I look for from these conversations are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Team members feel listened to and valued&lt;/strong&gt; - this is priority number one. The strength of your relationship with your team rests on mutual trust and respect, and listening and understanding their priorities will build this trust.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team members have a chance to give and receive feedback&lt;/strong&gt; - whether this is feedback from them on the direction of a project, or feedback from you on their development, having space for feedback is very important.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You and your team member have a shared understanding of goals&lt;/strong&gt; - if there’s important information you need to communicate to your team, 1:1s are a great place to check understanding of that info. Likewise, if a team member has their own professional goals, they can use this space to keep you informed of them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’ll revisit the topic of 1:1s at another time, but remember - 1:1s shouldn’t be the only source of your relationship with your team members. Building authentic relationships with your team, through both formal and informal means, is a big part of effective leadership.&lt;/p&gt;
&lt;h3 id=&quot;manager-relationships&quot;&gt;Manager relationships&lt;/h3&gt;
&lt;p&gt;Alongside your relationship with your direct reports, cultivating your relationship with your manager will help you to grow alongside your organisation. As an engineering leader in a small organisation, it’s likely that you’ll be reporting directly to the CEO or founder. You may even be a founder, in which case you’ll have a slightly different dynamic to navigate.&lt;/p&gt;
&lt;p&gt;In the first case, your manager is the person ultimately responsible for the success of the organisation. They’ll have a lot on their plate, and as a result will likely expect you to operate with a lot of autonomy. In the context of this dynamic, having a good understanding of their priorities and concerns will make your life much easier. You’ll be able to anticipate work that will come your way, and align your goals with theirs. Listening and asking the right questions will help you to get this information, as well as increasing your manager’s trust that you have a clear understanding of their needs.&lt;/p&gt;
&lt;p&gt;Non-technical founders or CEOs will also want to be able to hand off technical problems and challenges to you, with the understanding that these will be dealt with. This means you also need to get comfortable with telling your manager what you need to succeed. While it might feel easier to just agree to a project, then go away and figure it out, being clear and up-front about the constraints and required resources will benefit your manager much more. They’ll be able to make an informed decision based on your expertise. This doesn’t mean being able to instantly estimate work, or accurately predict the exact cost of a project. Rather, it means taking the time to work out what the implications of a piece of work are, and bringing those clearly to the table. If a poorly-scoped project is going to take 6 months of your team’s time, then your manager would almost certainly know that information before starting, rather than waiting until you’re 3 months in to find out.&lt;/p&gt;
&lt;p&gt;Don’t forget to ask for feedback from your manager. While some leaders my find it easy to give feedback, others will err on the side of &lt;a href=&quot;https://www.radicalcandor.com/faq/what-is-ruinous-empathy/&quot;&gt;politeness and conflict aversion&lt;/a&gt;, robbing you of an opportunity to learn and improve. You can help to avoid this process by articulating when and how you like to receive feedback. This will make it easier for your manager, and ensure you get the guidance you need to grow.&lt;/p&gt;
&lt;p&gt;A final point on your relationship with your manager - as a founder of a small organisation, there’s no guarantee that they themselves have management training or experience. They could be learning as they go, and experiencing organisational growth for the first time. You can help to get the best out of your relationship by clearly articulating your needs, and providing them with advice and resources on how best to manage you. Chances are, they’ll really appreciate it.&lt;/p&gt;
&lt;h3 id=&quot;peer-relationships&quot;&gt;Peer relationships&lt;/h3&gt;
&lt;p&gt;As mentioned above, as organisations grow, your knowledge of the work and challenges of other teams becomes less readily available. One way to combat this is to invest in your relationships with other leaders in your organisation. Understanding what they’re trying to achieve, and making sure that they understand your goals, will allow you to work in alignment. A lack of relationships between different areas of an organisation is often the cause of wasted resources, working at cross purposes, or internal politicking.&lt;/p&gt;
&lt;p&gt;While it may not be necessary or feasible for you to have 1:1s with all other leaders in the organisation, a regular catch-up with anyone whose team’s work frequently overlaps with yours can be valuable. This will allow you to find shared solutions to common challenges, identify areas where you can support each other’s work, and build a shared narrative to present to the rest of the organisation.&lt;/p&gt;
&lt;p&gt;Not only do peer relationships offer important organisational benefits, they can also benefit you professionally. Having an opportunity to learn from people who are going through a similar growth trajectory to you is really helpful. If you’re able to put in place simple ways to share this learning, such as a leadership lunch-and-learn or a book club, you’ll be able to pool your shared knowledge to the benefit of all.&lt;/p&gt;
&lt;p&gt;Treating your &lt;a href=&quot;https://lethain.com/first-team/&quot;&gt;peers as your team&lt;/a&gt; may feel unnatural at first, especially if you are deeply invested in the growth and success of your own team. However, it’s definitely worth it in terms of helping you, and the organisation, to grow together.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;We’ve covered a lot of ground here, so let’s recap. In order to grow as a leader alongside our fledgling organisation, you’ll need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Develop relevant skills by treating management and leadership as &lt;strong&gt;distinct, learnable disciplines&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Broaden your knowledge of &lt;strong&gt;systems&lt;/strong&gt;, &lt;strong&gt;processes&lt;/strong&gt; and &lt;strong&gt;trends&lt;/strong&gt;, as well as your knowledge of the &lt;strong&gt;work of other teams&lt;/strong&gt; and the &lt;strong&gt;key levers of your organisation&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Invest in your relationships with your &lt;strong&gt;team&lt;/strong&gt;, &lt;strong&gt;manager&lt;/strong&gt; and &lt;strong&gt;peers.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, growth isn’t all about gaining. Next time, we’ll cover some of the things you’ll need to let go of as you grow in your role as a leader.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This article is part of a series I’m writing on scaling an engineering team from zero - look out for further posts on the same topic.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want support and advice on growing as an engineering leader? I’m currently available for consulting on the areas discussed above - see &lt;a href=&quot;https://www.jw.pe/blog/consulting-launch-2022/&quot;&gt;my consulting launch post&lt;/a&gt; for details. If you have thoughts or questions about the above, feel free to get in touch with me on &lt;a href=&quot;https://www.twitter.com/jwpe&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://climatejustice.social/web/@jwpe&quot;&gt;Mastadon&lt;/a&gt;, or by email.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[I'm Launching A Consultancy]]></title><description><![CDATA[I'm starting to offer consulting services in engineering management and software development. Here are my goals. ]]></description><link>https://www.jw.pe/blog/consulting-launch-2022/</link><guid isPermaLink="false">https://www.jw.pe/blog/consulting-launch-2022/</guid><pubDate>Tue, 01 Nov 2022 14:17:00 GMT</pubDate><content:encoded>&lt;p&gt;Today is 1st November, 2022, which feels like as good a day as any to begin my experiment with consulting. I&apos;m framing this next step in my career as an experiment because I want to approach it with openness and curiosity, as well as applying an evidence-driven approach to my goals.&lt;/p&gt;
&lt;p&gt;My career in the world of software engineering started with consulting and contracting. After making the switch to full-time employment a decade ago, returning to the world of consulting feels like coming full-circle. In that time, I&apos;ve worked for a &lt;a href=&quot;/about&quot;&gt;variety of great organisations&lt;/a&gt;, and learned an enormous amount. I&apos;m excited to share some of that knowledge and experience with organisations whose values are in line with my own.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;I&apos;m launching a software engineering management consultancy, focussing on three areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🌅 Engineering team management, leadership, and coaching&lt;/li&gt;
&lt;li&gt;✅ Preparing for investment&lt;/li&gt;
&lt;li&gt;⚙️ Hands-on software engineering&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to work with me, please drop me an email at &lt;code&gt;work at jw.pe&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-i-offer&quot;&gt;What I offer&lt;/h2&gt;
&lt;p&gt;When planning my consulting offering, I spent some time reflecting on the unique value that I can bring. I&apos;ve chosen three key areas where I believe my experience and skills can make a real difference.&lt;/p&gt;
&lt;h3 id=&quot;-engineering-team-management-leadership-and-coaching&quot;&gt;🌅 Engineering team management, leadership, and coaching&lt;/h3&gt;
&lt;p&gt;Having led two successive technology teams over the last five years, engineering leadership is where I believe I can offer the most value, and where I get the most enjoyment from my work. I can help your organisation to build and retain an effective engineering team, improve your engineering processes, and coach or mentor developing engineering leaders. My approach focusses on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Leadership centred on &lt;strong&gt;empathy&lt;/strong&gt; and &lt;strong&gt;consensus-building&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Creating systems and processess to &lt;strong&gt;empower the team&lt;/strong&gt; to drive its own development and growth.&lt;/li&gt;
&lt;li&gt;Building team structures that are appropriate for the level of maturity of the organisation, while laying the groundwork for future expansion.&lt;/li&gt;
&lt;li&gt;Establishing a working environment where team members &lt;strong&gt;understand their responsibilities&lt;/strong&gt; and the &lt;strong&gt;value of their work&lt;/strong&gt;, without unnecessary stress or pressure.&lt;/li&gt;
&lt;li&gt;Making sure that communication between key stakeholders and the engineering team is &lt;strong&gt;transparent and effective&lt;/strong&gt;, with clear shared goals and metrics.&lt;/li&gt;
&lt;li&gt;Agreeing shared values, behaviours and standards to &lt;strong&gt;clarify expectations&lt;/strong&gt; for all team members.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My experience is primarily working with engineering teams from early-stage start-ups to Series B scale-ups, with an engineering / product headcount between 1-20 people. I am happy to advise on processes for larger teams, but my experience will be less relevant.&lt;/p&gt;
&lt;h3 id=&quot;-preparing-for-investment&quot;&gt;✅ Preparing for investment&lt;/h3&gt;
&lt;p&gt;I&apos;ve been through several investment rounds in previous roles, and have helped to prepare organisations to successfully secure those investments.&lt;/p&gt;
&lt;p&gt;I&apos;ve worked extensively on &lt;strong&gt;pitch decks&lt;/strong&gt;, providing a trustworthy technical voice to investors. I would be happy to review investor materials and provide unbiased feedback, or work with you to craft the best story for the technology team in your organisation.&lt;/p&gt;
&lt;p&gt;As deals move forward, I&apos;ve prepared &lt;strong&gt;due diligence reports&lt;/strong&gt; that have provided investors with confidence in the technical strength of the organisation. There&apos;s no substitute for having an empowered and well-run engineering team, but communicating these strengths effectively is very important. Given the current challenging investment environment for technology businesses, being adequately prepared for due diligence is crucial. I can help your organisation to get ready for the due diligence process by evaluating your technical infrastructure, team, and processes, then producing an assessment report with actionable recommendations. Alternatively, I can assist your team with an ongoing due diligence process by providing technical insight and hands-on support.&lt;/p&gt;
&lt;h3 id=&quot;️-hands-on-software-engineering&quot;&gt;⚙️ Hands-on software engineering&lt;/h3&gt;
&lt;p&gt;I&apos;ve spent the majority of the last 10 years working hands-on with code. My areas of expertise are &lt;strong&gt;back-end web development&lt;/strong&gt; and &lt;strong&gt;infrastructure&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Rather than presenting an exhaustive list of technologies, two examples of web stacks I&apos;ve worked with extensively are:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stack 1&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Front-end&lt;/em&gt;: React, Redux, TypeScript&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Back-end&lt;/em&gt;: NodeJS, Express, TypeScript&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Data stores&lt;/em&gt;: PostgreSQL, S3&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Infrastructure&lt;/em&gt;: AWS, CircleCI, Terraform, Docker&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Stack 2&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Front-end&lt;/em&gt;: Angular, JavaScript&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Back-end&lt;/em&gt;: Python, Django, Celery&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Data stores&lt;/em&gt;: PostgreSQL, S3, Redis, Kafka&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Infrastructure&lt;/em&gt;: AWS, CircleCI, Docker&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many of my skills are cross-functional and I&apos;m always happy to learn new technologies, although this may depend on the duration of our work together.&lt;/p&gt;
&lt;p&gt;I&apos;ve worked to scale systems from pre-launch to heavy production usage, and will always try to choose an implementation to fit the level of maturity of your product, balancing the need for extensibility with avoiding overengineering.&lt;/p&gt;
&lt;p&gt;I pride myself on communicating effectively with both technical and non-technical team members, as well as producing high-quality, robust and well-documented code.&lt;/p&gt;
&lt;h2 id=&quot;goals&quot;&gt;Goals&lt;/h2&gt;
&lt;p&gt;With my experiment-driven approach in mind, I&apos;ve set myself some goals for this new chapter, as well as considering metrics that I can use to measure my success.&lt;/p&gt;
&lt;h3 id=&quot;️-create-sustainable-revenue&quot;&gt;♻️ Create sustainable revenue&lt;/h3&gt;
&lt;p&gt;A key goal for the coming months is to prove that consulting is a viable way for me to earn an income. It&apos;s critical to me that I can do this in a sustainable way. With this in mind, I&apos;m aiming to work a 4-day week, giving myself time to balance delivering on my work goals with enjoying life outside of work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Reach an average revenue of £5000 / month&lt;/em&gt; - As an initial income goal, this will prove the success of my experiment by allowing me to comfortably sustain my lifestyle. I&apos;m confident that the value I bring will allow me to reach this goal in a fraction of my available schedule. Once I&apos;ve proven the sustainability of the model I will set a new revenue goal, but for me the long-term aim is to reach a threshold that allows me to enjoy my life, not to earn as much as possible. &lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;Please don&apos;t misinterpret this metric by trying to use it to calculate my day rate! This will only lead to disappointment.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Maintain a 4-day work schedule&lt;/em&gt; - Experience has shown me that I can deliver timely, high-quality work in a 4-day schedule. I want to have a healthy balance between my work and other important aspects of my life.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-help-organisations-that-align-with-my-values&quot;&gt;🌱 Help organisations that align with my values&lt;/h3&gt;
&lt;p&gt;It&apos;s important to me that I work with organisations that I believe are doing something positive in the world, so I want to achieve my revenue goal without compromising on my principles.&lt;/p&gt;
&lt;p&gt;This is a bit of a tough goal to measure, since my assessment of an organisation as &apos;good&apos; is completely subjective, but I&apos;d want to be able to make a credible argument to friends and family for how each organisation I work with is benefitting the world and fits with my morals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;100% of clients pass the &apos;credible argument for good&apos; test&lt;/em&gt; - See above.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-build-a-reputation-for-excellence&quot;&gt;✨ Build a reputation for excellence&lt;/h3&gt;
&lt;p&gt;I want people and organisations to want to work with me. I believe that I&apos;ve left a good impression on my previous colleagues, and I&apos;d like to continue to do so with my clients.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;100% of clients would like to work with me again&lt;/em&gt; - I want to build positive relationships with anyone I&apos;m helping, and leave them satisfied that my work brought them value.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;X% of new work comes from client recommendations&lt;/em&gt; - Not sure what a good number to put against this is, but ideally I want to get to a place where word-of-mouth recommendations are driving a good chunk of my inbound work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;*&lt;/p&gt;
&lt;p&gt;I aim to be transparent about my progress towards these goals, both to hold myself accountable and because I think it&apos;s interesting. I really enjoy reading about other people developing their businesses in the open, so it seems reasonable to try to take the same approach.&lt;/p&gt;
&lt;h2 id=&quot;why-now&quot;&gt;Why now?&lt;/h2&gt;
&lt;p&gt;My career in software has taken me in some interesting directions so far - from travel to politics, from coding to managing to leading. I&apos;ve had the privilege to work in a variety of roles that have allowed me to better understand the landscape of technology organisations, and to discover my own strengths and topics that I&apos;m passionate about. I&apos;m sure that there&apos;s some nostalgia talking, but it&apos;s always felt to me like the next step on my journey was clear - when one chapter finished, the next one quickly began. Now, for the first time I can remember, that next step isn&apos;t so obvious, so I have to make my own way.&lt;/p&gt;
&lt;p&gt;It feels scary to put yourself out there and trade solely on your own merit, which has held me back from launching this experiment over the last month that I&apos;ve been seriously thinking about it. However, I&apos;ve always liked the idea of working for myself, and with the expertise I&apos;ve developed, I feel confident enough in my abilities to give it a try.&lt;/p&gt;
&lt;h2 id=&quot;work-with-me&quot;&gt;Work with me&lt;/h2&gt;
&lt;p&gt;If you like the sound of what I&apos;m setting out to do, and you think I could add value to your organisation, please don&apos;t hesitate to get in touch to outline how we could work together. You can drop me an email at &lt;code&gt;work at jw.pe&lt;/code&gt;, or send me a message through another medium if we&apos;re already in touch.&lt;/p&gt;
&lt;p&gt;I&apos;ll likely be spending at least some of this month on setup and promotion, so my short-term availability may be limited until December. That said, if you have a small project where I can help you immediately, I&apos;d love to hear about it. I&apos;m currently discussing some larger projects in the new year, so if you have something sizeable in mind, the sooner you can let me know about it, the better.&lt;/p&gt;
&lt;p&gt;I look forward to hearing from you!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Lockdown Walks Beyond Bristol]]></title><description><![CDATA[One of the few highlights of my 2020 was walking. Since I've lived in Bristol, I've wanted to make more of an effort to explore the beautiful local countryside.]]></description><link>https://www.jw.pe/blog/bristol-walks/</link><guid isPermaLink="false">https://www.jw.pe/blog/bristol-walks/</guid><pubDate>Wed, 13 Jan 2021 11:02:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the few highlights of my 2020 was walking. Since I&apos;ve lived in Bristol, I&apos;ve wanted to make more of an effort to explore the beautiful local countryside. This was always something that fell by the wayside in favour of easier, lazier activities. With the onset of the pandemic, though, options for things to do outside the house dwindled.&lt;/p&gt;
&lt;p&gt;Fortunately, during the first lockdown my girlfriend bought an excellent book - &lt;a href=&quot;https://www.prscshop.co.uk/products/beyond-bristol-24-country-walks-by-robin-tetlow&quot;&gt;Beyond Bristol&lt;/a&gt; by Robin Tetlow. This little book has made a huge difference to our lockdown lifestyle. Full to the brim with carefully-researched walks through the fields, woods and villages of Gloucestershire and Somerset, it&apos;s a great primer for walkers who want to explore the area around Bristol. In my experience, instructions in walking guides can be frustratingly vague or require lots of prior knowledge. This isn&apos;t the case at all with Robin&apos;s directions. Each step in each walk we&apos;ve been on so far has been clear, concise and unambiguous. I can&apos;t recommend it enough. It&apos;s clear from the creative routes and little details in the guide just how well the author knows the land around Bristol, and how much of his time and passion he&apos;s put into exploring and cataloguing it for the benefit of other walkers.&lt;/p&gt;
&lt;p&gt;Most weekends, when the rules allow it, we&apos;ve driven out of Bristol - the walks generally start around half an hour&apos;s drive away from the city&lt;sup&gt;&lt;a href=&quot;#ref-1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; - parked in a little village, and set off for a few hours of leisurely walking. Given the sheer amount of time we&apos;re all spending at home these days, being out in the fresh air and hills of the Mendips or the Cotswolds is a very revitalising experience. No screens, no video calls; just chatting, climbing over stiles and engaging with the world outside.&lt;/p&gt;
&lt;p&gt;There are still plenty of walks in the book that we&apos;ve yet to try, but some we&apos;ve done multiple times. Some of my favourites are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A walk from Dursley (of Cam and Dursley fame) over some impressive hilltops following the Cotswold Way, and back along the edge of the forest.&lt;/li&gt;
&lt;li&gt;A short (by the book&apos;s standard) walk from Hawkesbury Upton, passing through one valley in particular that seemed like something out of a Miyazaki film.&lt;/li&gt;
&lt;li&gt;A climb up Crook Peak on the south edge of the Mendips, with expansive views over the Bristol Channel and down to north Devon.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;m looking forward to more of these walks, and if by some miracle we manage to do them all, there&apos;s a &lt;a href=&quot;https://redcliffepress.co.uk/product/beyond-bristol-2-24-more-country-walks/&quot;&gt;second book&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I think this period of uncertainty and crisis has really highlighted to me the importance of doing things that are good for the soul, and walking definitely fulfils that for me. I might write in a bit more detail about some of the individual walks I&apos;ve done over the last year, but I wanted to publish this book recommendation to encourage anyone who&apos;s looking for a place to start. I&apos;d be interested to hear about any other good walks in the Bristol area.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a name=&quot;ref-1&quot;&gt;1.&lt;/a&gt; I haven&apos;t tried to get to any of the walks using public transport yet - I&apos;m sure that some of them, especially those starting in larger towns, should be accessible by bus or train. Smaller villages might be a bit more of a challenge. If you were feeling particularly dedicated, you could cycle to the start of some of the walks, but this would make for a long day!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Day 4: OAuth Flows]]></title><description><![CDATA[On Friday I finished off the initial implementation of obtaining an OAuth token from GitHub.]]></description><link>https://www.jw.pe/blog/day-4-oauth-flows/</link><guid isPermaLink="false">https://www.jw.pe/blog/day-4-oauth-flows/</guid><pubDate>Mon, 08 Aug 2016 15:24:36 GMT</pubDate><content:encoded>&lt;p&gt;On Friday I finished off the initial implementation of obtaining an OAuth token from GitHub. Their documentation makes it pretty straightforward, but for anyone unfamiliar with the OAuth2 flow, it goes a little something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The application generates an authentication URL for GitHub based on several parameters including the &lt;code&gt;CLIENT_ID&lt;/code&gt; (obtained from the &lt;a href=&quot;https://github.com/settings/applications/new&quot;&gt;OAuth application on GitHub&apos;s side&lt;/a&gt;)  and  a &lt;code&gt;redirect_url&lt;/code&gt; that users will be sent back to after authorizing.&lt;/li&gt;
&lt;li&gt;The user follows this URL which takes them to an authorization page on GitHub, where they can choose to authorize the application to use the scopes  (essentially permissions) that it has requested. More on scopes in a moment.&lt;/li&gt;
&lt;li&gt;If the user authorizes the application, they are redirected back to the previously supplied &lt;code&gt;redirect_url&lt;/code&gt;. GitHub passes a temporary code with this redirect call, which can temporarily be used to request a full access token for the user.&lt;/li&gt;
&lt;li&gt;Behind the scenes, the application makes a POST request to GitHub, with more client information and the temporary code. In return it receives the OAuth access token.&lt;/li&gt;
&lt;li&gt;The application can now use the access token to make authenticated calls on behalf of the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Steps 1 to 4 can be implemented in two fairly simple Django views. Let&apos;s go through the code for the initial generation of the authorization URL first. &lt;strong&gt;Note:&lt;/strong&gt; the application code is now &lt;a href=&quot;https://github.com/Jwpe/alexandria-server&quot;&gt;on GitHub&lt;/a&gt; so you can follow along there!&lt;/p&gt;
&lt;h2 id=&quot;generating-an-authorization-url&quot;&gt;Generating an authorization URL&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# models.py&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; models
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contrib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;postgres&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fields &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; ArrayField
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;utils &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; timezone

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; uuid

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OauthToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;
    Stores Ouath Credentials for contacting github.
    &quot;&quot;&quot;&lt;/span&gt;
    token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CharField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;max_length&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; blank&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; null&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    scopes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ArrayField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;base_field&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CharField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;max_length&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CharField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;max_length&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    timestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DateTimeField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;default&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;timezone&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;now&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; uuid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uuid4&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;OauthToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;kwargs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is our database model model to hold data about the OAuth token.  The token itself will live in the &lt;code&gt;token&lt;/code&gt; field, and the scopes that the token has access to will live in a fancy Postgres &lt;code&gt;ArrayField&lt;/code&gt;. The &lt;code&gt;state&lt;/code&gt; field allows us to preserve state between generating the authorization URL and receiving the callback - after this it is unused. Finally, we have a timestamp because if there&apos;s one thing I&apos;ve learned from working with Django for years, it&apos;s that everything is easier when your models are timestamped.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# views.py&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;conf &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; settings
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; django&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shortcuts &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; get_object_or_404&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reverse

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; json
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; requests
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; rest_framework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;views &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; APIView
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; rest_framework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Response

&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;models &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; OauthToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; User

OAUTH_URL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/login/oauth/&quot;&lt;/span&gt;
API_URL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.github.com/&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GitHubAuthorize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;APIView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# build an empty OauthToken&lt;/span&gt;
        desired_scopes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;user:email&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;write:repo_hook&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;public_repo&apos;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

        token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OauthToken&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scopes&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;desired_scopes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# construct oauth URL for GitHub&lt;/span&gt;
        oauth_url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_get_oauth_url&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;oauth_url&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; oauth_url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;data&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_get_oauth_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        redirect_uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;build_absolute_uri&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            reverse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;oauth:callback&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;state&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;client_id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GITHUB_CLIENT_ID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;scope&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scopes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&apos;redirect_uri&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; redirect_uri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OAUTH_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;authorize&quot;&lt;/span&gt;

        oauth_url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_add_params&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; oauth_url

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_add_params&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; uri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        uri_with_params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; uri &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;?&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&amp;amp;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;{0}={1}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;k&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; v&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; k&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; v &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; uri_with_params&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our view is a subclass of DRF&apos;s &lt;code&gt;APIView&lt;/code&gt; - this allows us to easily generate JSON responses and do a bunch of other awesome things like have pluggable authentication and permissions.  In the main &lt;code&gt;get&lt;/code&gt; method, we build a list of desired scopes to request from GitHub. We then make an empty &lt;code&gt;OauthToken&lt;/code&gt;, with a randomly generated &lt;code&gt;state&lt;/code&gt; field, to make sure that we can check that any callbacks we receive were actually initiated on our site. When we receive a callback, we will check for the existence of an &lt;code&gt;OauthToken&lt;/code&gt; object with the same state passed in the callback. If one exists, we can be confident that it&apos;s a real authorization response, not a spoofed call from another site.&lt;/p&gt;
&lt;p&gt;Our  &lt;code&gt;_get_oauth_url&lt;/code&gt; method just adds this state,  our application&apos;s client ID, the desired scopes and our redirect URI as URL parameters to the base GitHub OAuth URL, &lt;code&gt;https://github.com/login/oauth/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since the application will eventually have a separate client-side app, rather than redirecting the user directly to the authorization URL, we&apos;re returning it in a JSON response. The client application will then use this to redirect the user, but for the purposes of testing we can just get the resulting URL and plug it into our browser to authorize our application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3-eu-west-1.amazonaws.com/jwpevans/images/2016-08-08-144435_821x567_scrot.png&quot; alt=&quot;authorize screen&quot;&gt;&lt;/p&gt;
&lt;p&gt;Success!&lt;/p&gt;
&lt;h2 id=&quot;recieving-the-oauth-callback-and-obtaining-an-access-token&quot;&gt;Recieving the OAuth callback and obtaining an access token&lt;/h2&gt;
&lt;p&gt;Once the user has authorized our GitHub app, we need to listen for a callback from GitHub and exchange the temporary code we receive for a full access token.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#models.py&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;
    A basic user model that is used for authentication.
    &quot;&quot;&quot;&lt;/span&gt;

    email &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EmailField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unique&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    github_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IntegerField&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; models&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ForeignKey&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;OauthToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This model will hold basic user information. It would be totally fine to keep the user and OAuth token in a single model - I believe TravisCI does this - but I chose this implementation in order not to have to create empty user models in order to verify the legitimacy of a callback based on the &lt;code&gt;state&lt;/code&gt; field as described above. A more mature implementation would probably handle this &lt;code&gt;state&lt;/code&gt; flow separately, using expiring keys in Redis or something similar, and only create a unified token-user model once the callback had been confirmed valid. This is always something we can build later if we so desire.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# views.py&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GitHubCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;APIView&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query_params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;state&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        code &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query_params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;code&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;# Check that this is a legitimate callback for a request made from the site&lt;/span&gt;
        token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_object_or_404&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;OauthToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_get_access_token&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;except&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HTTPError &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_generate_error_response&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scopes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;scope&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;split&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;,&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;access_token&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_get_or_create_user&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;data&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&apos;user_email&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string&quot;&gt;&apos;access_token&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;token
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;errors&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;status&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;403&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;detail&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;User has not authorized the correct scopes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From our callback, we get the state and the code out of the URL parameters, and look for the &lt;code&gt;OauthToken&lt;/code&gt; instance corresponding to the state to make sure it&apos;s the real deal. Once we find it, we exchange the &lt;code&gt;code&lt;/code&gt; for the &lt;code&gt;access_token&lt;/code&gt;. Let&apos;s look at that in more detail:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_get_access_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

     uri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; OAUTH_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;access_token&quot;&lt;/span&gt;
     params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token string&quot;&gt;&apos;code&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token string&quot;&gt;&apos;client_id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GITHUB_CLIENT_ID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token string&quot;&gt;&apos;client_secret&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; settings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GITHUB_CLIENT_SECRET&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
     headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Accept&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

     response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raise_for_status&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

     &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This function is very simple: we send the &lt;code&gt;code&lt;/code&gt; to GitHub along with our application&apos;s client ID and secret, and get back a response including the access token and the scopes that the user has authorized.&lt;/p&gt;
&lt;p&gt;It&apos;s probably a good idea to check at this point that the user-authorized scopes are the ones we requested - without those we can&apos;t do the things the service needs to do.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scopes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;scope&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;split&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;,&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;access_token&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Currently, we&apos;ll just fail if the scopes are incorrect. In future, we could warn the user or redirect them back to the authorization page. If the scopes are right, we save the token object with the access token we got back. Finally, since we successfully have a token,  we create a user to store GitHub-specific data like email and ID.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_get_or_create_user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    user_url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; API_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;user&apos;&lt;/span&gt;

    headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Authorization&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;token {}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; User&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;email&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; github_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        old_token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;token
        user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; token
        user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        old_token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delete&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;except&lt;/span&gt; User&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DoesNotExist&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; User&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;objects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            email&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;email&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; github_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; user&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here we&apos;re calling GitHub&apos;s user API to  get details about the authorized user. If the user has been through the authorization flow before - e.g. if our application&apos;s authentication has expired because they haven&apos;t visited the site for a long time - we don&apos;t want to recreate a new user. In this case we&apos;ll just delete the old token and replace it with the new one. If they&apos;re new to the flow, we&apos;ll make a user object for them and attach the OAuth token.&lt;/p&gt;
&lt;p&gt;Later on we&apos;ll use this user for our own authentication purposes - we want to remember that the user has authorized with GitHub so they don&apos;t have to go through the full flow every time they visit the site.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&apos;s next?&lt;/h2&gt;
&lt;p&gt;In the current implementation, we&apos;re just spitting out the user&apos;s email and access token at the end of the callback function, to prove that authorization has been successful. In reality, though, we want to generate our own authentication token for the user that they can use to take actions on our service, and probably redirect them to some client application with this token as a header. We&apos;ll use JWTs for our authentication, so tune in next time for a discussion of generating JWTs and making authenticated calls with them!&lt;/p&gt;
&lt;p&gt;As always, if you have any questions or criticism, hit up the comments!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Day 2-3: Back to Django]]></title><description><![CDATA[The last couple of days have been relatively productive. Thanks to GitHub's excellent API documentation, I've got to the stage where I can reliably authenticate a user with my application via Oauth, and use their key to pull basic user info such as their private email address and GitHub ID.]]></description><link>https://www.jw.pe/blog/day-2-3-back-to-django/</link><guid isPermaLink="false">https://www.jw.pe/blog/day-2-3-back-to-django/</guid><pubDate>Fri, 05 Aug 2016 12:09:49 GMT</pubDate><content:encoded>&lt;p&gt;The last couple of days have been relatively productive. Thanks to GitHub&apos;s excellent &lt;a href=&quot;https://developer.github.com/guides/basics-of-authentication/&quot;&gt;API documentation&lt;/a&gt;, I&apos;ve got to the stage where I can reliably authenticate a user with my application via Oauth, and use their key to pull basic user info such as their private email address and GitHub ID.  In this post I&apos;ll talk briefly about the setup of the application and some of the design choices I&apos;ve made so far.&lt;/p&gt;
&lt;p&gt;I&apos;ve written the server element of the project using Django 1.10. I went with Django mainly because of my familiarity with the ecosystem and the ease of setting up REST endpoints with &lt;a href=&quot;http://www.django-rest-framework.org/&quot;&gt;Django REST Framework&lt;/a&gt; (DRF), which is definitely something I&apos;ll want to do further down the line.&lt;/p&gt;
&lt;h2 id=&quot;layout&quot;&gt;Layout&lt;/h2&gt;
&lt;p&gt;The project layout is slightly different from the standard created by Django&apos;s &lt;code&gt;startproject&lt;/code&gt; command. For simplicity&apos;s sake, I prefer to put &lt;code&gt;settings.py&lt;/code&gt;, &lt;code&gt;wsgi.py&lt;/code&gt; etc in the root directory of the Django application, rather than having them in a separate, named folder. As a result, my tree looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
├── alexandria_server
│   ├── permissions
│   │   ├── migrations
│   │   │   └── 0001_initial.py
│   │   ├── models.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── docker-compose.yml
├── Dockerfile
├── manage.py
└── requirements.txt&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All of the configuration and deployment code lives at the project root, along with &lt;code&gt;manage.py&lt;/code&gt;. The application root (&lt;code&gt;alexandria_server&lt;/code&gt;) contains settings, base URL configuration and WSGI info, as well as any app directories.&lt;/p&gt;
&lt;h2 id=&quot;configuration&quot;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;It was an interesting experience trying to see how few of Django&apos;s built-in modules were necessary for it to work. I&apos;ve built Django projects before without many of the &lt;code&gt;contrib&lt;/code&gt; apps, but I think this was the first time that I&apos;d completely removed the &lt;code&gt;auth&lt;/code&gt; app. Since the only login will be through GitHub initially, the user model in the application can be very basic and thus doesn&apos;t require the password hashing and other helpful features that Django auth provides.&lt;/p&gt;
&lt;p&gt;Here&apos;s my extremely minimal &lt;code&gt;settings.py&lt;/code&gt; file currently. Initially, DRF was pretty unhappy with my removal of the &lt;code&gt;auth&lt;/code&gt; app, as it tries to assign an &lt;code&gt;AnonymousUser&lt;/code&gt; object to the request on an unauthenticated call. However, the &lt;code&gt;UNAUTHENTICATED_USER&lt;/code&gt;  setting can be changed to another object, or &lt;code&gt;None&lt;/code&gt;, which circumvents this issue.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token triple-quoted-string string&quot;&gt;&quot;&quot;&quot;
Django settings for alexandria_server project.

Generated by &apos;django-admin startproject&apos; using Django 1.10.
&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; os
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; dj_database_url

&lt;span class=&quot;token comment&quot;&gt;# Build paths inside the project like this: os.path.join(BASE_DIR, ...)&lt;/span&gt;
BASE_DIR &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dirname&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;abspath&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__file__&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

SECRET_KEY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;SECRET_KEY&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;DEBUG&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    DEBUG &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    DEBUG &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;

GITHUB_CLIENT_ID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;GITHUB_CLIENT_ID&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
GITHUB_CLIENT_SECRET &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;GITHUB_CLIENT_SECRET&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

ALLOWED_HOSTS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# Application definition&lt;/span&gt;

INSTALLED_APPS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Core&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.contrib.contenttypes&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.contrib.messages&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Third-party&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;rest_framework&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Internal&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;alexandria_server.permissions&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

MIDDLEWARE &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.middleware.security.SecurityMiddleware&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.middleware.common.CommonMiddleware&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.middleware.csrf.CsrfViewMiddleware&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;django.middleware.clickjacking.XFrameOptionsMiddleware&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

ROOT_URLCONF &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;alexandria_server.urls&apos;&lt;/span&gt;

WSGI_APPLICATION &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;alexandria_server.wsgi.application&apos;&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# Database&lt;/span&gt;
DATABASES &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;default&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dj_database_url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environ&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;DATABASE_URL&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Internationalization&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# https://docs.djangoproject.com/en/1.10/topics/i18n/&lt;/span&gt;

LANGUAGE_CODE &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;en-us&apos;&lt;/span&gt;

TIME_ZONE &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;UTC&apos;&lt;/span&gt;

USE_I18N &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;

USE_L10N &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;

USE_TZ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# DRF&lt;/span&gt;
REST_FRAMEWORK &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;DEFAULT_AUTHENTICATION_CLASSES&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;DEFAULT_PERMISSION_CLASSES&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;DEFAULT_RENDERER_CLASSES&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;rest_framework.renderers.JSONRenderer&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;UNAUTHENTICATED_USER&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Only a couple of interesting things worth pointing out here. Firstly, as you can see, most of the default Django apps are gone. I&apos;m not entirely sure if I need &lt;code&gt;contenttypes&lt;/code&gt; or &lt;code&gt;messages&lt;/code&gt; either, so those may disappear.&lt;/p&gt;
&lt;p&gt;I&apos;m using Kenneth Reitz&apos;s &lt;a href=&quot;https://github.com/kennethreitz/dj-database-url&quot;&gt;&lt;code&gt;dj-database-url&lt;/code&gt;&lt;/a&gt; to easily populate the configuration for the default database. I like this approach because it makes it trivially easy to set your DB locally, or in the cloud, simply  by changing the &lt;code&gt;DATABASE_URL&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;p&gt;Finally, I&apos;m going for the classic &lt;a href=&quot;http://12factor.net/&quot;&gt;12 factor app&lt;/a&gt; approach to settings, so all variables and secrets are pulled in from environment variables. I prefer this tactic to the alternative of having a &lt;code&gt;settings.py&lt;/code&gt; file per environment, perhaps not checked in to source control. I feel like it gives more flexibility but also more consistency between environments.&lt;/p&gt;
&lt;h2 id=&quot;database&quot;&gt;Database&lt;/h2&gt;
&lt;p&gt;My go-to database choice for Django has always been Postgres. They play together very nicely, and Postgres has a &lt;a href=&quot;https://docs.djangoproject.com/en/1.10/ref/contrib/postgres/&quot;&gt;host of awesome features which are fully supported in Django&lt;/a&gt; that no other database has. It&apos;s also extremely easy, thanks to Docker, to run a Postgres database locally in a container. Here&apos;s the setup in my &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;9.5&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; POSTGRES_PASSWORD=some_password
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; POSTGRES_USER=some_user
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It&apos;s amazing to be able to spin up a local database with only 5 lines of configuration code.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In a separate post, I&apos;ll talk about the Oauth flow in more detail. If you have any questions or comments about project layout, or configuring a basic Django setup, let me know in the comments.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Open Source Adventures, Day 1: Ideas and Skeletons]]></title><description><![CDATA[Today I began work on my project in earnest. It always helps me to visualize my goals if I can draw them out, and I still find pen and paper to be the best medium for rapidly exploring ideas.]]></description><link>https://www.jw.pe/blog/open-source-adventures-day-1-ideas-and-skeletons/</link><guid isPermaLink="false">https://www.jw.pe/blog/open-source-adventures-day-1-ideas-and-skeletons/</guid><pubDate>Tue, 02 Aug 2016 22:43:38 GMT</pubDate><content:encoded>&lt;p&gt;Today I began work on my project in earnest. It always helps me to visualize my goals if I can draw them out, and I still find pen and paper to be the best medium for rapidly exploring ideas. Since I&apos;m at a very early stage, the notes that I made were pretty high level, but helped me to get an idea of the main moving parts I&apos;d want for the app.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3-eu-west-1.amazonaws.com/jwpevans/images/notes.jpg&quot; alt=&quot;Notes from Day 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;Some basic requirements for the app to work, as shown in the list on the right:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authenticate with GitHub.&lt;/strong&gt; The goal for the project is to automatically build documentation whenever a relevant repository is pushed to, much like continuous integration tools like &lt;a href=&quot;https://travis-ci.org/&quot;&gt;Travis&lt;/a&gt; or &lt;a href=&quot;https://circleci.com/&quot;&gt;Circle&lt;/a&gt;. This means that being linked to GH is pretty much a no-brainer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parse Markdown and convert it to HTML.&lt;/strong&gt; Ideally in the long term I&apos;d like the tool to support multiple documentation formats but Markdown is ubiquitous and easy to parse.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build a tree of static files and push them to a CDN (probably S3).&lt;/strong&gt; Ultimately the docs you view should just be static files for simplicity and speed of loading. At some point they will probably be accessed through a web app with authentication so that only registered users can view them, but this can come later.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There will have to be some boilerplate around this core logic, especially on the authentication/permissions front for interacting with the GitHub API. However, those three bullet points encompass the main purpose of the project. My aim is to build an MVP which covers these bases, and then expand as and when I&apos;ve ascertained the usefulness of the tool.&lt;/p&gt;
&lt;p&gt;My current idea is to separate the &apos;worker&apos; unit - the part of the application that listens for webhooks, retrieves raw documentation, parses the Markdown and spits out HTML - from the &apos;authentication&apos; unit, which will handle storage of user tokens, repo references, serving a REST API etc. I&apos;m hoping with this approach I&apos;ll be able to make the worker element stateless and easily horizontally scalable, since I imagine building and uploading the compiled documentation will be the time bottleneck in the app flow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;With my ideas slightly more in order, I started building an application skeleton that I could iterate on. I often find that the hardest part of a project is writing the first line of code, and breaking that barrier by laying simple groundwork is very helpful. It makes it easy to start building experimental views, run unit tests, and all the good stuff that allows rapid development. From a psychological perspective, the hurdle of starting is overcome and you can start focussing on the more interesting and challenging parts of the problem at hand.&lt;/p&gt;
&lt;p&gt;I&apos;m using Docker to run the application at the moment, since I&apos;m familiar with it and it&apos;s portable. Here&apos;s my bare-bones &lt;code&gt;Dockerfile&lt;/code&gt; for Python-based projects:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;dockerfile&quot;&gt;&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; python:3.5&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ADD&lt;/span&gt; requirements.txt /code/&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WORKDIR&lt;/span&gt; /code/&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; pip install -r requirements.txt&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ADD&lt;/span&gt; . /code/&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CMD&lt;/span&gt; python run.py&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I generally use &lt;code&gt;docker-compose&lt;/code&gt; to run locally as it makes everything so easy. Whether or not this will stay in through the final product, I don&apos;t know, but it makes running locally and in production fairly seamless, so there&apos;s a good chance.&lt;/p&gt;
&lt;p&gt;Tomorrow I plan to focus on getting the basics of authenticating and GitHub token storage down, so I can start playing around with the &lt;a href=&quot;https://developer.github.com/v3/repos/hooks/&quot;&gt;webhook API&lt;/a&gt; using my own credentials. I&apos;ll hopefully post an update showing some actual code or results at that point, but no promises!&lt;/p&gt;
&lt;p&gt;Let me know in the comments or via email if you have any initial suggestions or questions about the project!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Open Source Adventures, Day 0]]></title><description><![CDATA[Today is August 1st. Now that I have been back in Bristol for a little while, it's time to knuckle down on a project that I've been thinking about in some form or another for the last few months.]]></description><link>https://www.jw.pe/blog/open-source-adventures-day-0/</link><guid isPermaLink="false">https://www.jw.pe/blog/open-source-adventures-day-0/</guid><pubDate>Mon, 01 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today is August 1st. Now that I have been &lt;a href=&quot;http://www.jw.pe/blog/post/back-in-bristol/&quot;&gt;back in Bristol&lt;/a&gt; for a little while, it&apos;s time to knuckle down on a project that I&apos;ve been thinking about in some form or another for the last few months.&lt;/p&gt;
&lt;p&gt;I don&apos;t have many details yet because the ideas are still all in my head. However, I have a pretty good idea of the problem that I want to solve, so here&apos;s the summary:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As a software engineer working on a team, you want to write documentation to accompany your code. If your project involves multiple moving parts (for example a microservice architecture), it can be hard to keep documentation consistent and accessible across the different repositories that comprise the overall application. On one hand, you want to write your documentation alongside your code so that it stays up to date with changes to APIs. On the other hand, you don&apos;t want to have to trawl through 50 services&apos; GitHub repositories to find the documentation on the one endpoint you care about.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I want to build a tool that allows developers to write documentation alongside code, but read documentation in a single, presentable, easily accessible way. In other words, a documentation tool that allows &lt;strong&gt;distributed writing and centralised reading&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Open source is amazing, and I have built my career on the shoulders of great developers who make the open source packages I use every day. For this reason, among others, I want to build this project in the open. If teams find it useful, and want to self-host the tool, I will be delighted. If I can provide a paid service for those people who don&apos;t want to run it themselves, in the style of &lt;a href=&quot;https://www.getsentry.com/welcome/&quot;&gt;Sentry&lt;/a&gt; or &lt;a href=&quot;https://www.nginx.com/&quot;&gt;Nginx&lt;/a&gt;, all the better.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I want to make sure that I stay motivated and hold myself accountable while I&apos;m working on this project, hence the announcement. I intend to post regular updates on my progress for anyone who is interested, and to make sure that I work consistently towards a goal.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in more details, please let me know in the comments or via email. As soon as I have something tangible to show, I&apos;ll add links to a GitHub repository for anyone who wants to contribute!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Quantifying Sufjan Stevens with the Genius API and NLTK]]></title><description><![CDATA[I am an avid Sufjan Stevens fan. I really enjoy his peaceful, somewhat melancholy style of music. As a person who is not religious myself, one of the things that I find interesting about his songs is that religious - specifically Christian - themes seem to appear quite broadly across his oeuvre.]]></description><link>https://www.jw.pe/blog/quantifying-sufjan-stevens-with-the-genius-api-and-nltk/</link><guid isPermaLink="false">https://www.jw.pe/blog/quantifying-sufjan-stevens-with-the-genius-api-and-nltk/</guid><pubDate>Fri, 22 Jul 2016 18:45:06 GMT</pubDate><content:encoded>&lt;p&gt;I am an avid Sufjan Stevens fan. I really enjoy his peaceful, somewhat melancholy style of music. As a person who is not religious myself, one of the things that I find interesting about his songs is that religious - specifically Christian - themes seem to appear quite broadly across his oeuvre. A song will often have a superficial, literal interpretation, and potentially another allegorical interpretation with religious connotations. Stevens has discussed his faith, and how it influences his music, in several interviews. Nonetheless, discussions rage online as to whether his songs should be viewed through a lens of religion or simply taken at face value.&lt;/p&gt;
&lt;p&gt;I was thinking about this yesterday while listening to his song about the elusive, possibly extinct ivory-billed woodpecker, &apos;Lord God Bird&apos;:&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/6mN6x4awlDs&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;It made me wonder how often Sufjan Stevens actually references God in his work. Is he talking more, or less, about religion as time goes on?  Since I have been itching to do some lighthearted Python coding, I decided I&apos;d try to find out using &lt;a href=&quot;http://www.genius.com&quot;&gt;www.genius.com&lt;/a&gt; as a lyrics resource. Fortunately, Genius has a decent &lt;a href=&quot;https://docs.genius.com/&quot;&gt;REST API&lt;/a&gt; which can be accessed using an OAuth token. For experiments like mine with no users, it&apos;s possible to generate a client token after having &lt;a href=&quot;https://genius.com/api-clients&quot;&gt;registered an application&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Firstly, I needed to get a complete list of Sufjan songs to work with.  Using &lt;code&gt;requests&lt;/code&gt;, it was fairly easy to throw together a minimal API client that accessed the artist songs endpoint at &lt;code&gt;api.genius.com/artist/&amp;#x3C;id&gt;/songs&lt;/code&gt;. This endpoint only returns 20 results by default, so it was necessary to page through until I&apos;d made a list of all songs by the artist.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; requests

CLIENT_ACCESS_TOKEN &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;some_token&quot;&lt;/span&gt;

BASE_URI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://api.genius.com&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;_get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;BASE_URI&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Bearer {}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CLIENT_ACCESS_TOKEN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; headers&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        headers&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Authorization&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; token
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; headers&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raise_for_status&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get_artist_songs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artist_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    current_page &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    next_page &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;
    songs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; next_page&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;artists/{}/songs/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;artist_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;page&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; current_page&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; _get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        page_songs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;response&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;songs&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; page_songs&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            songs &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; page_songs
            current_page &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            next_page &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;False&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; songs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is a fairly naive implementation - it&apos;ll keep paging through until it gets to the first empty page of results - but it worked fine for my purposes. I chose to separate out the request logic because I wanted to implement some other API calls using the same approach.&lt;/p&gt;
&lt;p&gt;Running the &lt;code&gt;get_artist_songs&lt;/code&gt; function with &lt;code&gt;artist_id=958&lt;/code&gt; got me a list of songs by  Sufjan Stevens. I filtered out any that didn&apos;t list him as the primary artist:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;songs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; get_artist_songs&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;958&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
songs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;song &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; song &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; songs &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; song&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;primary_artist&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;958&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This left me with 284 Sufjan songs. I didn&apos;t do any filtering beyond this, so it&apos;s possible that the data set contains duplicates - I don&apos;t know how strict Genius is about removing multiple instances of the same song.&lt;/p&gt;
&lt;p&gt;Now that I had my song list, I needed to find the lyrics for each song. Unfortunately, this wasn&apos;t a field that was present in the &lt;code&gt;song&lt;/code&gt; objects I&apos;d got back from the API. However, I had the URL of the song itself on Genius. I used &lt;a href=&quot;https://www.crummy.com/software/BeautifulSoup/bs4/doc/&quot;&gt;BeautifulSoup&lt;/a&gt;, another of my favourite Python libraries, to grab the song lyrics directly from the HTML of the page... or that&apos;s what I would have done if it were permitted by the Genius Terms of Service.  To be honest, since Genius are &lt;a href=&quot;http://genius.com/a/news-genius&quot;&gt;shamelessly annotating other people&apos;s web content&lt;/a&gt; I&apos;m sure they&apos;ll be cool with this. Here&apos;s what that code might potentially look like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; requests
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; bs4 &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; BeautifulSoup

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scrape_lyrics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text

    soup &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BeautifulSoup&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;html.parser&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    lyrics &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; soup&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;lyrics&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    lyrics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;script&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decompose&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    lyrics_text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; / &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;lyric &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; lyric &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; lyrics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stripped_strings&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; lyrics_text&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;BeautifulSoup makes things so simple. It&apos;s easy to find the &lt;code&gt;lyrics&lt;/code&gt; tag in the HTML, and then get all child strings within the tag using &lt;code&gt;lyrics.stripped_strings&lt;/code&gt;. &lt;code&gt;stripped_strings&lt;/code&gt; just removes extraneous whitespace, but otherwise behaves like the more commonly-used &lt;code&gt;strings&lt;/code&gt;. I joined the strings together with &lt;code&gt;/&lt;/code&gt; for aesthetic purposes, but it&apos;s really not necessary.&lt;/p&gt;
&lt;p&gt;Using this function, I was able to retrieve the lyrics for all 284 songs on my list.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; song &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; songs&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; song&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;url&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    lyrics &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; scrape_lyrics&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    song&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;lyrics&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lyrics&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Armed with this trove of data, I could start doing some rudimentary analysis. My initial idea was to use the release dates of songs to investigate whether Sufjan had become more, or less, godly over time. However, only 34 of the 284 songs had release date information available through the Genius API, so that idea was quickly nipped in the bud. If I&apos;m feeling motivated at some point, I may revisit this and combine the data with another information source to get a better picture of God references per year.&lt;/p&gt;
&lt;p&gt;What I can tell you, however, is which of Sufjan&apos;s songs feature the most references to God. I chose the words &apos;God&apos;, &apos;Lord&apos;, &apos;Jesus&apos; and &apos;Christ&apos; as explicit references - if I missed any, please let me know.&lt;/p&gt;
&lt;p&gt;First, let&apos;s look at the lyrics in aggregate:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; nltk

stop_words &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nltk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;corpus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stopwords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;words&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;english&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
stop_words&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;update&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;,&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&quot;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&apos;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;?&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;!&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;(&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;[&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;]&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 

all_lyrics &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;song&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;lyrics&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; song &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; songs&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

all_tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nltk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;word_tokenize&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;all_lyrics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
all_tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; token &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; all_tokens &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; stop_words&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
fdist &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nltk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FreqDist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;all_tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here I am bringing NLTK to bear, admittedly at a very rudimentary level. I tokenized the lyrics, splitting them up into individual words, and then built a frequency distribution from the tokens. I removed stop words and punctuation to make the results less noisy.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;fdist&lt;/code&gt; object has a couple of fun options. Looking directly at the words we chose above, we can see how often Sufjan talks about God in all of his songs:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;god_words &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;christ&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;jesus&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;lord&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;god&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
god_count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;fdist&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;word&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; word &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; god_words&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the whole body of lyrics I was analyzing, direct references to God appeared a whopping 187 times in 69 distinct songs. In the absence of a comparison with a non-Christian artist, I don&apos;t want to draw any direct conclusions from this, but I think it&apos;s fair to say that Sufjan revisits this theme a lot.&lt;/p&gt;
&lt;p&gt;We can also see what other things Stevens talks about a lot in his songs by plotting the most common 50 words in the frequency distribution.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;fdist&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;plot&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://s3-eu-west-1.amazonaws.com/jwpevans/images/test3.png&quot; alt=&quot;Word frequency in Sufjan Stevens songs&quot;&gt;&lt;/p&gt;
&lt;p&gt;Unsurprisingly, &apos;love&apos; is the most common noun across all songs. Perhaps more surprisingly, &apos;Christmas&apos; is extremely common too - that is, until you know that Sufjan Stevens has released two &lt;a href=&quot;https://en.wikipedia.org/wiki/Songs_for_Christmas_(Sufjan_Stevens_album)&quot;&gt;Christmas&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Silver_%26_Gold_(Sufjan_Stevens_album)&quot;&gt;albums&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can go into a bit more detail, and look at the songs that feature God the most frequently:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;god_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; song &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; songs&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nltk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;word_tokenize&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;song&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;lyrics&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; token &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; tokens &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; stop_words&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    fdist &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nltk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FreqDist&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    god_count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;fdist&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;word&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; word &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; god_words&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    god_list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;append&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;song&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;title&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; god_count&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# filter out songs with no reference to god&lt;/span&gt;
god_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;song &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; song &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; god_list &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; song&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# sort by number of references&lt;/span&gt;
god_list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;god_list&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;lambda&lt;/span&gt; tup&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;tup&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reverse&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here then, are the top 10 Sufjan Stevens songs by Godliness:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Come On! Let&apos;s Boogey to the Elf Dance&lt;/li&gt;
&lt;li&gt;Away In A Manger&lt;/li&gt;
&lt;li&gt;Holy Holy, Etc.&lt;/li&gt;
&lt;li&gt;Get Real Get Right&lt;/li&gt;
&lt;li&gt;Oh God, Where Are You Now? (In Pickerel Lake? Pigeon? Marquette? Mackinaw?)&lt;/li&gt;
&lt;li&gt;Seven Swans&lt;/li&gt;
&lt;li&gt;Hark! The Herald Angels Sing!&lt;/li&gt;
&lt;li&gt;Ding-a-ling-a-ring-a-ling&lt;/li&gt;
&lt;li&gt;Once in Royal David&apos;s City&lt;/li&gt;
&lt;li&gt;The Transfiguration&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Aside from Christmas songs, the album that appears the most is Seven Swans, with two tracks  (&apos;Seven Swans&apos; and &apos;The Transfiguration&apos;) featuring in the top 10. This makes sense, as the album is &lt;a href=&quot;http://pitchfork.com/reviews/albums/7512-seven-swans/&quot;&gt;acknowledged as one of the more overtly religious&lt;/a&gt; of his works.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope you&apos;ve enjoyed this little adventure into data collection and analysis using Python, with a distinct Sufjan Stevens theme. Thanks go to the singer himself, as well as Genius for providing the data for this post.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Back in Bristol]]></title><description><![CDATA[It's been a long time since I've written anything on this blog, and I have a lot of ground to cover.]]></description><link>https://www.jw.pe/blog/back-in-bristol/</link><guid isPermaLink="false">https://www.jw.pe/blog/back-in-bristol/</guid><pubDate>Mon, 11 Jul 2016 15:18:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s been a long time since I&apos;ve written anything on this blog, and I have a lot of ground to cover. Since I last updated, I&apos;ve spent 18 months in America, worked on some amazing projects and learned an enormous amount. I&apos;m hoping to talk a bit more about  some of the technical concepts I&apos;ve had the chance to explore in subsequent posts. However, for the moment, I just want to address my short-term goals and current situation.&lt;/p&gt;
&lt;p&gt;I&apos;ve moved back to Bristol - perhaps my favourite city in the UK - and will be working on my own software projects.  I am also interested in doing more technical writing; I&apos;ll aim to put out content on this blog, as well as on &lt;a href=&quot;https://twitter.com/jwpe&quot;&gt;Twitter&lt;/a&gt;, on a more regular schedule.&lt;/p&gt;
&lt;p&gt;It would be great to become more involved again with the Bristol tech scene. I am particularly interested in discussing the following topics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;systems architecture&lt;/li&gt;
&lt;li&gt;Docker, and container orchestration/devops&lt;/li&gt;
&lt;li&gt;web development using Django/Flask and Python&lt;/li&gt;
&lt;li&gt;continuous integration and deployment&lt;/li&gt;
&lt;li&gt;JavaScript and ES2015&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you run a meetup group, are working on a project that covers one of these areas, or just want to get coffee and chat, please let me know!&lt;/p&gt;
&lt;p&gt;I am also considering taking on some short-term consulting/contract projects, so I&apos;m happy to discuss potential opportunities if they are relevant to my experience and interests.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Making a Mockery of Python]]></title><description><![CDATA[Today we will be talking about mocking. No, not the joke at someone else's expense kind. Mocking is a technique to temporarily replace real parts of our code with fake, simpler parts, so that we can check that the rest of the parts are working as we intend.]]></description><link>https://www.jw.pe/blog/making-a-mockery-of-python/</link><guid isPermaLink="false">https://www.jw.pe/blog/making-a-mockery-of-python/</guid><pubDate>Wed, 10 Dec 2014 15:11:53 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This post was originally published on the &lt;a href=&quot;http://engineroom.trackmaven.com/blog/making-a-mockery-of-python/&quot;&gt;TrackMaven
engineering blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Today we will be talking about mocking. No, not the joke at someone else&apos;s expense kind. Mocking is a technique to temporarily replace &lt;strong&gt;real&lt;/strong&gt; parts of our code with &lt;strong&gt;fake&lt;/strong&gt;, simpler parts, so that we can check that the rest of the parts are working as we intend. Here we&apos;ll consider some simple use cases for mocking in Python tests, and try to show how this extremely useful technique can make us better at testing.&lt;/p&gt;
&lt;h2 id=&quot;why-do-we-mock&quot;&gt;Why do we mock?&lt;/h2&gt;
&lt;p&gt;When we carry out unit testing, our goal is to test a small section of code - for example a function or class method - in isolation. In other words, we should only be testing the code that is contained in said function. If we rely on calls to other pieces of code in our test, then we find ourselves in an unfortunate situation where changes to the nested code can break the test, despite the code of the function being tested remaining the same. This is best illustrated by example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# function.py&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add_and_multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    addition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; y
    multiple &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; multiply&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;addition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; y

&lt;span class=&quot;token comment&quot;&gt;# test.py&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; unittest


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyTestCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unittest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestCase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_add_and_multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
        y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;

        addition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; multiple &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; add_and_multiply&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; addition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    unittest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python test.py
&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;
----------------------------------------------------------------------
Ran &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.001s

OK&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the simple case above, we have a function that adds and multiplies two numbers, and returns both the sum and the multiple. The &lt;code&gt;add_and_multiply&lt;/code&gt; function calls a second function, &lt;code&gt;multiply&lt;/code&gt; to perform the multiplication operation.&lt;/p&gt;
&lt;p&gt;Suppose we decided that we wanted to dispense with &apos;traditional&apos; mathematics, and redefine our multiply function to always add three to the numbers being multiplied.&lt;/p&gt;
&lt;p&gt;Our new &apos;multiplication&apos; function looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we encounter a problem. Our test code hasn&apos;t changed. The function we are supposedly testing hasn&apos;t changed. However, the &lt;code&gt;test_add_and_multiply&lt;/code&gt; test will now fail:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python test.py
F
&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;
FAIL: test_add_and_multiply &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__main__.MyTestCase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
----------------------------------------------------------------------
Traceback &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;most recent call last&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;:
  File &lt;span class=&quot;token string&quot;&gt;&quot;test.py&quot;&lt;/span&gt;, line &lt;span class=&quot;token number&quot;&gt;13&lt;/span&gt;, &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; test_add_and_multiply
    self.assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;, multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
AssertionError: &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;

----------------------------------------------------------------------
Ran &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;.001s

FAILED &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;failures&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The issue here is that our original test was not a &lt;em&gt;true&lt;/em&gt; unit test. Despite intending only to test our outer function, we are implicitly testing the inner function as well, since our desired result depends on its behaviour. This may seem like a pointless distinction in the simple case above, but in a situation where we are testing a complex piece of logic - for example, a Django view function that calls various different inner functions based on certain conditionals - it becomes more important to separate the testing of the view logic from the results of the function calls.&lt;/p&gt;
&lt;p&gt;There are two ways to solve this problem. We either ignore it, call our unit test an integration test and move on, or we can turn to &lt;strong&gt;mocking&lt;/strong&gt;. The disadvantage of the first course of action is that an integration test only tells us something is broken somewhere along the line of function calls - it makes it much harder to identify where the issue lies. This is not to say that integration tests aren&apos;t useful, because they are. However, unit tests and integration tests solve different problems, and should be used in tandem. So if we want to be good testers, we choose the alternative: the &lt;code&gt;mock&lt;/code&gt; library.&lt;/p&gt;
&lt;h2 id=&quot;what-is-mock&quot;&gt;What is mock?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;mock&lt;/code&gt; is a Python package so awesome, it was added to the standard library in Python 3. For those of us peasants still toiling in the UnicodeError-strewn fields of Python 2.x, you can install it through pip:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pip &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;.1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are many different ways to use &lt;code&gt;mock&lt;/code&gt;. We can use it to monkey-patch functions, create fake objects, or even as a context manager. All of these implementations serve one overall purpose - replacing parts of our code with replicas that we can use to a) gather information and b) return contrived responses.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mock&lt;/code&gt;&apos;s &lt;a href=&quot;http://www.voidspace.org.uk/python/mock/&quot;&gt;documentation&lt;/a&gt; can be quite dense, and finding information on a particular use-case can be tricky. Here, we&apos;ll take a look at a common scenario - replacing a nested function to check its inputs and outputs.&lt;/p&gt;
&lt;h2 id=&quot;we-will-mock-you&quot;&gt;We will mock you&lt;/h2&gt;
&lt;p&gt;Let&apos;s rewrite our unit test, using the power of mock. Then we&apos;ll discuss what&apos;s happening, and why it is useful from the perspective of testing.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# test.py&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; mock
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; unittest


&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyTestCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unittest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestCase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;patch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;multiply&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_add_and_multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock_multiply&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

        x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
        y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;

        mock_multiply&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;return_value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;

        addition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; multiple &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; add_and_multiply&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        mock_multiply&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assert_called_once_with&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; addition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assertEqual&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    unittest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;main&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At this point, we can change the multiply function to do whatever we want - it could return the multiple plus three, return None, or return your &lt;a href=&quot;https://www.youtube.com/watch?v=q-yxOFIkgxU&amp;#x26;t=1m15s&quot;&gt;favourite line from Monty Python and the Holy Grail&lt;/a&gt; - and our test above will still pass. This is because we are &lt;strong&gt;mocking&lt;/strong&gt; the multiply function. In true unit test fashion, we don&apos;t care about what happens inside the multiply function; from the perspective of our &lt;code&gt;add_and_multiply&lt;/code&gt; test, we only care that &lt;code&gt;multiply&lt;/code&gt; was called with the right arguments. We assume (hopefully, correctly) that what is going on &lt;em&gt;inside&lt;/em&gt; &lt;code&gt;multiply&lt;/code&gt; is itself being tested by another unit test.&lt;/p&gt;
&lt;h2 id=&quot;what-just-happened&quot;&gt;What just happened?&lt;/h2&gt;
&lt;p&gt;The syntax used above may look confusing at first. Let&apos;s consider the relevant lines more closely:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token decorator annotation punctuation&quot;&gt;@mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;patch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;multiply&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test_add_and_multiply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock_multiply&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We&apos;ve used the &lt;code&gt;mock.patch&lt;/code&gt; decorator to &lt;strong&gt;replace&lt;/strong&gt; &lt;code&gt;multiply&lt;/code&gt; with a mock object. We then insert this into our test by passing it as an argument, which we&apos;ve called &lt;code&gt;mock_multiply&lt;/code&gt;. Within the context of the test, any call to &lt;code&gt;multiply&lt;/code&gt; will be redirected to our &lt;code&gt;mock_multiply&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;Cries of terror - &quot;How can we be replacing a function with an object!?&quot; Don&apos;t worry! This is Python, so functions &lt;strong&gt;are&lt;/strong&gt; objects. Normally, when we call &lt;code&gt;multiply()&lt;/code&gt;, we are using the &lt;code&gt;__call__&lt;/code&gt; method of the &lt;code&gt;multiply&lt;/code&gt; function object. With our mock in place, however, our &lt;code&gt;multiply()&lt;/code&gt; call instead calls the &lt;code&gt;__call__&lt;/code&gt; method of our mock object.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;mock_multiply&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;return_value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In order to get our mock function to return anything, we need to specify the &lt;code&gt;return_value&lt;/code&gt; attribute. This tells our mock object what to give back when it is called.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;python&quot;&gt;&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;addition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; multiple &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; add_and_multiply&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

mock_multiply&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assert_called_once_with&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the test, we then called our outer function, &lt;code&gt;add_and_multiply&lt;/code&gt;. This will call our nested &lt;code&gt;multiply&lt;/code&gt; function, and if we&apos;ve mocked it correctly, the call will be received by our mock object instead. To check that this has happened, we can rely on a smart feature of mock objects - they store any arguments that they were called with. The &lt;code&gt;assert_called_once_with&lt;/code&gt; method of the mock object is a nice shortcut to check, as the name suggests, if the object was called once with a specific set of arguments. If it was, we are happy and the test passes. If it wasn&apos;t, &lt;code&gt;assert_called_once_with&lt;/code&gt; will let us know by raising an &lt;code&gt;AssertionError&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-have-we-achieved&quot;&gt;What have we achieved?&lt;/h2&gt;
&lt;p&gt;Well, quite a lot actually. Firstly, we have &lt;strong&gt;isolated&lt;/strong&gt; the functionality of &lt;code&gt;add_and_multiply&lt;/code&gt; from the functionality of &lt;code&gt;multiply&lt;/code&gt; by mocking the nested function. This means that our unit test is only testing logic specifically inside &lt;code&gt;add_and_multiply&lt;/code&gt;. Only changes to the code of &lt;code&gt;add_and_multiply&lt;/code&gt; will affect the success or failure of the test.&lt;/p&gt;
&lt;p&gt;Secondly, we can now control the outputs of our nested function to make sure our outer function handles different cases. For example, our &lt;code&gt;add_and_multiply&lt;/code&gt; function might have conditional logic based on the result of &lt;code&gt;multiply&lt;/code&gt;: say, we only want to return a value if the multiple is greater than 10. We could easily test that our logic works by generating contrived outputs from &lt;code&gt;multiply&lt;/code&gt; to mimic the case where the multiple is less than 10, and the case where the multiple is greater. This feature of mock objects is great for testing control flow.&lt;/p&gt;
&lt;p&gt;Finally, we can now make sure that our mocked function is being called the correct number of times, with the correct arguments. Since our mock object is sitting where our &lt;code&gt;multiply&lt;/code&gt; function normally sits, we know that any calls made to it would normally go to &lt;code&gt;multiply&lt;/code&gt;. When testing a complex function, it is extremely reassuring to make sure that each step is being called correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The example given above only scratches the surface of what &lt;code&gt;mock&lt;/code&gt; can do. In an upcoming post, we&apos;ll look at some more in-depth examples of using the library, as well as identifying some pitfalls to avoid. Meanwhile, questions are welcome in the comments, or on Twitter!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want support and advice on growing as an engineering leader? I’m currently available for consulting - see &lt;a href=&quot;https://www.jw.pe/blog/consulting-launch-2022/&quot;&gt;my consulting launch post&lt;/a&gt; for details. If you have thoughts or questions about the above, feel free to get in touch with me on &lt;a href=&quot;https://www.twitter.com/jwpe&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://climatejustice.social/web/@jwpe&quot;&gt;Mastadon&lt;/a&gt;, or by email.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using Markdown with Django 1.7]]></title><description><![CDATA[Markdown is great. As a formatting language, I find it to be pretty much unparalleled for speed and ease of syntax. Not having to think overly hard about style allows you to focus purely on the quality of the content.]]></description><link>https://www.jw.pe/blog/using-markdown-django-17/</link><guid isPermaLink="false">https://www.jw.pe/blog/using-markdown-django-17/</guid><pubDate>Fri, 09 May 2014 03:26:17 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://whatismarkdown.com/&quot;&gt;Markdown&lt;/a&gt; is great. As a formatting language, I find it to be pretty much unparalleled for speed and ease of syntax. Not having to think overly hard about style allows you to focus purely on the quality of the content.&lt;/p&gt;
&lt;p&gt;At work, we use GitHub extensively, which means that Markdown is a standard. Issues, pull requests and comments all accept the syntax, so it feels very familiar. Given a choice, I now prefer to do all of my writing in Markdown. Using &lt;a href=&quot;http://www.jw.pe/blog/post/5-tools-made-site/&quot;&gt;Draft&lt;/a&gt; makes this very convenient - I can write in Markdown and output my writing to HTML. However, this has always felt like a bit of an extra step to me. What I really wanted to be able to do was just dump my Markdown into my blog and have it render as HTML.&lt;/p&gt;
&lt;p&gt;One way to do this would be to use a static site generator like &lt;a href=&quot;http://blog.getpelican.com/&quot;&gt;Pelican&lt;/a&gt; or &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;. However, my site is a Django app, and I want to keep it that way. Unfortunately, Django deprecated their built-in support for Markdown rendering in 1.5. Fortunately, there is an easy way around this!&lt;/p&gt;
&lt;h2 id=&quot;using-markdown-with-django-15&quot;&gt;Using Markdown with Django 1.5+&lt;/h2&gt;
&lt;p&gt;The Python &lt;a href=&quot;http://pythonhosted.org/Markdown/&quot;&gt;markdown&lt;/a&gt; package is the secret to adding Markdown support back into our Django templates. In order to make it as easy as possible to render Markdown, we want to create a &lt;a href=&quot;https://docs.djangoproject.com/en/dev/howto/custom-template-tags/&quot;&gt;custom template filter&lt;/a&gt;. It will look a little something like this:&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/Jwpe/fad38fd47679ff57f0d4.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;In order to render content written in Markdown on a page, we can just add the filter to the relevant context variable. Don&apos;t forget to load the file containing the template tag!&lt;/p&gt;
&lt;p&gt;Et voila - now we can write our content as Markdown, throw it into a text field on a Django form, and see it rendered as HTML on our display page. Easy! So easy, in fact, that this post itself was written in Markdown and put straight into my blog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: For the security-minded, it&apos;s also possible to change the &lt;a href=&quot;http://pythonhosted.org/Markdown/reference.html#safe_mode&quot;&gt;escape behaviour&lt;/a&gt; of the &lt;code&gt;markdown&lt;/code&gt; function to disallow raw HTML from being passed through. This way, we can expose a Markdown form to our users without worrying that they will inject malicious code into our pages! In the example above, I&apos;ve used &lt;code&gt;safe_mode = &apos;escape&apos;&lt;/code&gt; which automatically escapes raw HTML to a safe equivalent.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want support and advice on growing as an engineering leader? I’m currently available for consulting on the areas discussed above - see &lt;a href=&quot;https://www.jw.pe/blog/consulting-launch-2022/&quot;&gt;my consulting launch post&lt;/a&gt; for details. If you have thoughts or questions about the above, feel free to get in touch with me on &lt;a href=&quot;https://www.twitter.com/jwpe&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://climatejustice.social/web/@jwpe&quot;&gt;Mastadon&lt;/a&gt;, or by email.&lt;/em&gt;&lt;/p&gt;</content:encoded></item></channel></rss>