{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Wiwa",
  "description": "The Ramblings of Wiwa",
  "home_page_url": "https://lukewiwa.com/",
  "feed_url": "https://lukewiwa.com/feed/blog/feed.json",
  "authors": [
    {
      "name": "Luke Wiwatowski"
    }
  ],
  "items": [
    {
      "id": "https://lukewiwa.com/blog/tern_quick_haul_one_year_review/",
      "url": "https://lukewiwa.com/blog/tern_quick_haul_one_year_review/",
      "title": "Tern Quick Haul One Year Review",
      "content_html": "<p>With a growing family the idea of buying a second car mostly for school drop off and local errands felt like bringing a gun to a knife fight. Financially and in terms of the overall space and hassle a second car would occupy in our lives. In essence we didn't really need two cars, we sort of needed a car and a half. I'd been hearing good things about cargo bikes and their potential as a family vehicle. So we took a punt on buying one. It's been in our possession for over a year now and has done over 3000km in that time. In short it's been a fantastic investment for us on several axes: financial, fitness, health and overall wellbeing.</p>\n<!--more-->\n<p>The cargo bike in question is a Tern Quick Haul P9. We came to the decision about buying this bike after utilising the Canberra E-Bike Library. This service allows you to borrow e-bikes over several weeks for a small fee. This allowed us to see how it would actually fit into our daily lives and really show the shortcomings of each style of cargo bike. Needless to say there are many styles, long-john or Bakfiets, Long Tail, Mid Tail, Trike and some other more exotic styles. In the end we tried a bunch and came to the conclusion that a mid tail would suit us best as we didn't need to haul two kids and it rode very much like a normal bike. There was pretty much no question that Tern is the front runner brand when it comes to long and mid tail cargo bikes. Along with using the excellent Bosch motor system they used largely standard parts and had a wide array of accessories that remains unmatched by any other vendor.</p>\n<h1>Positives</h1>\n<p>For the most part the cargo bike has been a resounding success. It has delayed our need for a second car and has provided me and my family an alternative for getting around town. Additionally it's fun! I can't describe how nice it is to chuck the kids on the back and just whizz around town. The motor makes light work of any hills or headwinds and we always get parking right out front of wherever we are going. Within certain bounds it's more convenient than getting in the car. The kids love it too, they are much more likely to enjoy hanging out on the bike than being shoved into the car. We can ride out for a picnic with all the blankets and snacks without breaking a sweat. I've hauled my kids bikes on it!</p>\n<p>Surprisingly even the cold winters here in Canberra have not been a huge deterrence. As long as I have the right clothing, good coverage over my hands, ears and toes, then I'm fairly fine riding in the cold. The kids are perfectly fine too as long as I throw a heatpack in the back with them.</p>\n<p>Even without kid duties I'm often using it to pickup groceries or takeaway. I commute from time to time with it, I carry tools and accessories from the local hardware store. There is something quite nice about riding and not worrying about how much I can carry, the answer is always &quot;a lot!&quot;. Just the carrying capacity changes the way you think about riding.</p>\n<p>The range on this machine is fantastic. I commute 20km each way to work with the motor set to &quot;turbo&quot; and I get home with battery to spare. I neither need nor want a spare battery, this is plenty. If I need to go further than this I'm probably going to use the car or jump on public transport anyway. Anywhere that takes more than an hour commute is probably too annoying for me to do unless we're desperate.</p>\n<p>The running costs are near zero. I do pay for insurance on the bike, and I get it professionally serviced from time to time since I don't have the tools to work on the electrical side of things. But those costs are comparatively quite reasonable and not something I want to skimp on.</p>\n<p>The bottom line is that I and my family ride more. I'm fitter and my family are more likely to ride, not just the cargo bike but our regular bikes too.</p>\n<h1>Upgrades</h1>\n<p>The groupset that came with the bike, a shimano alivio set, was frankly garbage. It would not shift reliably at all. I tweaked it so much to try and get it working but I suspect the cable run was just too long and windy. I eventually replaced it with a groupset from a Chinese manufacturer called L-Twoo. It was cheap but effective. With that installed it was night and day. The shifting since has been flawless.</p>\n<p>I replaced the seat post with a cheap dropper post with a trigger built in to the seat. This has made switching between me and my wife riding a breeze. I highly recommend this upgrade if you are sharing a bike with someone even slightly different in height to you.</p>\n<h1>Downsides</h1>\n<p>Every spring the magpies come out to swoop and it's annoying and discouraging for the kids. Luckily I'm usually the target so i've had to attach a tall flag and add a brim to my helmet to distract the maggies. In the same vein when the weather really turns wet it can be a miserable experience riding in the rain. I will say it's usually not that bad, I'll often times not regret getting out. But when it really comes down you're just going to get wet.</p>\n<p>Additionally for any reasonable use case you will need to invest in some accessories. It's pretty much non negotiable that a front basket and extra rear storage need to be added at purchase time or very soon after. Carrying kids will up the cost again. The trick here is to frame the costs not as an extra expense, but as a trade off to not buying another car. None of these accessories cost that much when you compare to even the running costs of a car.</p>\n<h1>Sprint Finish</h1>\n<p>I can't gush enough about how good it has been, to the point where I've convinced at least one friend to join the club. I'm on the fence as to whether we will remain a one car family but the bike seems to have already paid for itself compared to the opportunity cost of purchasing a car. It's fun, my family are riding more and the maintenance costs are minimal.</p>\n",
      "date_published": "2025-12-03T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/end_multi_arch_manylinux2014_builds/",
      "url": "https://lukewiwa.com/blog/end_multi_arch_manylinux2014_builds/",
      "title": "End of lukewiwa/manylinux2014 Container Image Builds",
      "content_html": "<p>I previously created a <a href=\"https://github.com/lukewiwa/manylinux2014\">github repo</a> to amalgamate the different architectural builds of <code>manylinux2014</code> into a multi architectural manifest. With manylinux2014 <a href=\"https://github.com/pypa/manylinux/pull/1755\">gaining multiarchitectural image support</a> I'm going to stop the schedule for these builds.</p>\n",
      "date_published": "2025-03-24T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/aws_cloudfront_and_api_gateway_with_django/",
      "url": "https://lukewiwa.com/blog/aws_cloudfront_and_api_gateway_with_django/",
      "title": "AWS cloudfront and API Gateway with Django",
      "content_html": "<p>If you are hosting a web app on AWS you should be putting a Cloudfront instance in front of it. There are a few advantages but at the end of the day it's cheaper, allows for global caching at the edge and provides a fairly straight forward way to obtain SSL certificates. In my own hosting of apps I will more often than not use API gateway in front of Django too as this gives you some nice tools such as rate limiting and whatnot. But it seems really annoying and difficult to get it all working with a Django app given Django's security model around the allowed hosts.</p>\n<!--more-->\n<p>I had a few goes at this and thought it might be fairly straight forward but it turns out AWS needs it's own host headers for integration purposes if you are using cloudfront and API Gateway. As such we need to do a little bit of faff to get the host header over to Django.</p>\n<p>Firstly on the application side we need to tell Django to use the <code>x-forwarded-host</code> header for host checks in the <code>ALLOWED_HOSTS</code> setting. This is necessary if we are integrating into API Gateway or Lambda URLs since the host header is expected to be the domain of the cloudfront distribution.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">ALLOWED_HOSTS</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> [</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"yourdomain.com\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">]</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">USE_X_FORWARDED_HOST</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> True</span></span></code></pre>\n<p>Secondly we need to actually forward the host through using the <code>x-forwarded-host</code> header. Cloudfront has no native setting to do this for us so we need to use a cloudfront function. It's not particularly complicated but does introduce another resource to manage.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">function</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> handler</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">event</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> request</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> event.request;</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  request.headers[</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"x-forwarded-host\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">] </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { value: request.headers.host.value };</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> request;</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n<p>Thirdly we need to connect to our integration. Below is a CDK representation of what that would look like. It's important to note that there are a number of native <a href=\"https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront_origins-readme.html\">cloudfront CDK integrations</a> in to various AWS services. In my particular case I'm using API Gateway HTTP Endpoints which are somewhat simpler than the old REST Endpoints but you do have to do a little bit of work yourself.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-typescript\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> forwardHostHeader</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cloudfront.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">Function</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  this</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">  \"ForwardHeaderCfFunction\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    code: cloudfront.FunctionCode.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">fromFile</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">      // Filename of the above cloudfront function code</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      filePath: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"./lib/forwardHostFunction.js\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    runtime: cloudfront.FunctionRuntime.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">JS_2_0</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> cachePolicy</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cloudfront.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">CachePolicy</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">this</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"CachePolicy\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  cookieBehavior: cloudfront.CacheCookieBehavior.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">all</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  queryStringBehavior: cloudfront.CacheQueryStringBehavior.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">all</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">});</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> distribution</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cloudfront.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">Distribution</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  this</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">  \"CloudfrontDistribution\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    domainNames: [</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"your-domain-name.com\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">],</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    defaultBehavior: {</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">      // This could be a load balancer or an API gateway</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">      // or any number of AWS services.</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      origin: </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">new</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> origins.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">HttpOrigin</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        `internalAWSdomain.com`</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      ),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      functionAssociations: [</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">          function: forwardHostHeader,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">          eventType: cloudfront.FunctionEventType.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">VIEWER_REQUEST</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      ]</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      cachePolicy,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      originRequestPolicy:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        cloudfront.OriginRequestPolicy.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">ALL_VIEWER_EXCEPT_HOST_HEADER</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      allowedMethods: cloudfront.AllowedMethods.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">ALLOW_ALL</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      viewerProtocolPolicy:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        cloudfront.ViewerProtocolPolicy.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">REDIRECT_TO_HTTPS</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    },</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">);</span></span></code></pre>\n<p>In terms of the cloudfront distribution itself there are some extra configurations that we need to add on the defaults to make our Django application work as expected.</p>\n<ul>\n<li>We need to allow all http methods. By default cloudfront only allows GET and OPTIONS, any writable http requests need to be explicitly allowed.</li>\n<li>We need to be aware of the caching strategy. By default the AWS predefined CACHE OPTIMIZED strategy doesn't take into account cookies or query strings. In your typical django project we'll need both included to address user accounts and list views among other things.</li>\n<li>We need to avoid forwarding the original host header through to API Gateway. This will break integrations and basically get a permission denied error so we use the <code>ALL_VIEWER_EXCEPT_HOST_HEADER</code> managed policy.</li>\n</ul>\n<p>With all this in place the host header should flow from cloudfront all the way through to our application using the <code>x-forwarded-host</code> header and we should be able to set the <code>ALLOWED_HOSTS</code> Django setting to our original URL.</p>\n<p>Annoyingly I feel like none of this is particularly clear in the AWS docs, the only reference I could find to it was in the <a href=\"https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html#managed-origin-request-policy-all-viewer-except-host-header\">managed origin request policies page</a> which calls out specifically API Gateway and Lambda function URLs as requiring this header.</p>\n",
      "date_published": "2025-02-19T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/django_on_aws_for_chump_change/",
      "url": "https://lukewiwa.com/blog/django_on_aws_for_chump_change/",
      "title": "Django on AWS for Chump Change",
      "content_html": "<p>Below are the resources to my PyConAU 2024 talk &quot;Django on AWS for Chump Change&quot;</p>\n<!--more-->\n<ul>\n<li><a href=\"https://docs.google.com/presentation/d/1BXcYuaGFwHEirEcrNzsS98QNLGBaVpiNN-1sWQqnspI/edit?usp=sharing\">slides</a></li>\n<li><a href=\"https://github.com/lukewiwa/deploy-django-on-aws-for-chump-change\">infrastructure CDK repo</a></li>\n<li><a href=\"https://docs.aws.amazon.com/cdk/v2/guide/hello_world.html\">Offical CDK starter guide</a></li>\n<li><a href=\"https://github.com/awslabs/aws-lambda-web-adapter\">AWS Lambda Web Adaptor</a></li>\n<li><a href=\"https://github.com/lukewiwa/django_sqlite_object_storage\">Django Sqlite Object Storage github</a></li>\n<li><a href=\"https://pypi.org/project/django-sqlite-object-storage/\">Django Sqlite Object Storage PyPI</a></li>\n</ul>\n",
      "date_published": "2024-11-21T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/pocket_notebook/",
      "url": "https://lukewiwa.com/blog/pocket_notebook/",
      "title": "Pocket Notebook",
      "content_html": "<p>I've tried a lot of productivity software over the years. I seem to churn through apps and systems fairly regularly. I suspect part of this is because I just need a change from time to time. My brain requires a reset and adopting a new system provides that demarcation line and some fresh energy. Lately though I've moved to a very analogue system for personal note taking.</p>\n<!--more-->\n<p>Since the beginning of this year I've started carrying a pocket notebook and a pen. Part of this motivation was some youtube video that popped up in my feed with a good story to tell about the concept. Another part was that I decided I needed to find a way to be less Phone Dad around my kids. Finally with my wallet becoming lighter as more cards become digital I had a little more space to carry something on my person everyday.</p>\n<p>There is a plethora of personal notebook systems out there and you can tie yourself in knots trying to adhere to them. For me I began this with the clear goal that my notebook is strictly formatless and freeform. I must not be precious about it because that would cause me to write less if it wasn't &quot;perfect&quot; or within the system. The notebook is throwaway, a mental scratchpad and very much ephemeral. It can be torn to shreds and I'll just buy a new one and continue on. It had to be small enough to throw in any pocket, it must also be as reachable as my phone. Hopefully the goal was to reach for it first before my phone.</p>\n<p>With all that in mind I bought the cheapest A5 notebook I could find and started writing in it. Obviously the first couple of weeks were high energy and I was writing multiple pages a day. As I settled in to it more a rough &quot;system&quot; emerged from my daily scribbles. I would write the date, a rough agenda of the day as I knew it and a rough &quot;upcoming&quot; list of events I should keep in mind for the coming days. After that it was a mix of personal journal entries, todo lists, scribbles and doodles, rough notes and a portable art book for my daughter.</p>\n<p>As of today I've gone through roughly 10 of these notebooks and I suspect this might continue for some time. I wouldn't say on the whole it's been absolutely life changing but it has made a difference at the margins. I have something else to occupy my time with in those small moments where I would reflexively reach for my phone. I now am a slightly better example to my children with me reaching for my phone less, when my daughter is bored I hand her my notebook and she happily doodles some lovely art. It's a much more engaging experience to take a quick note during a conversation when it's written down on paper rather than in a phone. People notice. There are numerous studies saying that writing something down makes memories stick better than typing it in a device. I have found this to be true.</p>\n<p>One thing that I found very interesting as time went on is that an online todo list lasts forever and you will always add items at a greater rate than you will complete them. The ephemerality of the notebook means that some items never get done. They live on a previous page and at some point you forget to move it to today's agenda. A natural attrition occurs where these tasks end up less important or don't need to be done at all. I am really enjoying this phenomenon. It feels much more natural in my busy life than doing a &quot;weekly review&quot; or having to consciously clean up tasks.</p>\n<p>There are some downsides. I still lean on technology for reminders and calendar events. This is fine, the notebook isn't a system in and of itself but is part of a larger system that helps me manage my life. One of the most annoying things though is that I have run into unreliable pens. This has really annoyed me more than I thought it would! I'm happy with cheap notebooks but I definitely have found out that I need better quality pens. Sometimes carrying a notebook and pen is annoying in a way that a wallet isn't. Sometimes I forget my notebook!</p>\n<p>Another thing worth noting is that I am effectively paying roughly $5 per month to keep this habit going. I have no issues with this personally but it is funny on reflection that I probably would think twice about paying that much for a personal software subscription that does largely the same thing. In any case it's a cost I'm willing to bear at this point because it does give me upsides. I also enjoy looking out for my next notebook. I've tried a variety and I mostly land on moleskins with a dotted grid since they're easy to acquire but I've found some nice ones from various book stores.</p>\n<p>I've had this blog post down as a task for some time now transferring it from notebook to notebook and I guess now I can tick that item off, physically. It will feel good.</p>\n",
      "date_published": "2024-09-28T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/debug_django_management_commands_in_vscode/",
      "url": "https://lukewiwa.com/blog/debug_django_management_commands_in_vscode/",
      "title": "Debug Django Management Commands In VSCode",
      "content_html": "<p>Adding break points to a django management command in Visual Studio Code is one of those tasks that I’ve been wanting to achieve for a while but never really got around to investigating. I generally fell back on print statements which has been <em>Good Enough</em>.</p>\n<!--more-->\n<p>VSCode does give you some helpful templates for debugging commands when setting them up. It already has a pretty workable Django server solution but nothing specifically in the way of management commands. However the entry for <a href=\"https://code.visualstudio.com/docs/python/debugging#_set-configuration-options\">“Python File With Arguments”</a> looked interesting and that seemed like a good stepping stone into what I wanted.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-json\"><span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">{</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"version\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"0.2.0\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"configurations\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    {</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"name\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Python Debugger: Current File with Arguments\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"type\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"debugpy\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"request\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"launch\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"program\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"${file}\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"console\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"integratedTerminal\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"args\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"${command:pickArgs}\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  ]</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n<p>This is pretty close. What we want to do is tweak the program entry so that it follows the standard <code>./manage.py &lt;filename&gt; [args]</code> format that Django requires. For the arguments we can keep the args entry in the configuration as is. The tricky part is getting the currently open file without the extension. VSCode already has this as a predefined variable for this <code>${fileBasenameNoExtension}</code>. So with this we can put together a debug configuration that should allow us to add breakpoints to a management command and run that file with all the niceties of VSCode debugging. Here’s the full configuration.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-json\"><span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">{</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"version\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"0.2.0\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"configurations\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    {</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"name\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Python Debugger: Django Management Command\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"type\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"debugpy\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"request\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"launch\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">      // Make sure the path to `manage.py` is correct for your project</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"program\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"${workspaceFolder}/backend/manage.py ${fileBasenameNoExtension}\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"console\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"integratedTerminal\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"args\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"${command:pickArgs}\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  ]</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n<p>This way we can set break points inside a management command file and run the currently open file from the debugging options. You’ll also have options to add arbitrary arguments too.</p>\n",
      "date_published": "2024-04-29T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/finish_work_on_aws_lambda_python_sqlite/",
      "url": "https://lukewiwa.com/blog/finish_work_on_aws_lambda_python_sqlite/",
      "title": "Finishing Work On Docker Image aws-lambda-python-sqlite",
      "content_html": "<p>AWS now has <a href=\"https://gallery.ecr.aws/lambda/python\">preview support for python 3.12 in their lambda docker images</a>. They are built on the new Amazon Linux 2023 base images and as such stop the need for my hacky image <a href=\"https://hub.docker.com/r/lukewiwa/aws-lambda-python-sqlite\"><code>aws-lambda-python-sqlite</code></a>.</p>\n<!--more-->\n<p>The reason I built the image <code>aws-lambda-python-sqlite</code> was because the base image didn't support a version of sqlite high enough for Django. Happily the new base images have a more up to date version and this means support for python 3.12 in <code>aws-lambda-python-sqlite</code> will not come. It's been a fun little project and I'll keep the build pipeline going for a while yet but there will be no new python version support.</p>\n",
      "date_published": "2023-11-07T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/update_aws_lambda_python_sqlite_container_image/",
      "url": "https://lukewiwa.com/blog/update_aws_lambda_python_sqlite_container_image/",
      "title": "Added Python 3.11 to aws-lambda-python-sqlite container",
      "content_html": "<p>I went ahead and updated the build scripts for my <a href=\"https://lukewiwa.com/blog/aws_lambda_and_django_with_sqlite\">custom AWS lambda container</a>. Now there is Python 3.11 available with all its goodies.</p>\n<!--more-->\n<p>From memory this python version is much more performant with a few more really nice things that I can't remember off the top of my head.</p>\n",
      "date_published": "2023-07-25T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/multi_arch_manylinux2014/",
      "url": "https://lukewiwa.com/blog/multi_arch_manylinux2014/",
      "title": "manylinux2014 Multi Architecture Build",
      "content_html": "<p><a href=\"https://github.com/pypa/manylinux\"><code>manylinux</code></a> is a linux distro created to provide the widest support possible for python packages. The folks at the Python Packaging Authority (PyPA) provide convenient docker images of this distro but due to the scale and issues of providing builds across multiple architectures the images are distributed under different tags for different architectures. I created a <a href=\"https://github.com/lukewiwa/manylinux2014\">repo github action</a> to aggregate these images into a multi architecture build using the <a href=\"https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/\">docker manifest command</a>.</p>\n<!--more-->\n<p>This allows me to mindlessly pull <a href=\"https://hub.docker.com/r/lukewiwa/manylinux2014\"><code>lukewiwa/manylinux2014</code></a> and let docker sort out the architectural details. This was fairly straight forward to do and I hope the PyPA team implement something like this (although I understand the issues they face). I've got this setup to update every month which feels like a relatively good update schedule without wasting CPU cycles.</p>\n",
      "date_published": "2023-01-08T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/django_migrate_middleware/",
      "url": "https://lukewiwa.com/blog/django_migrate_middleware/",
      "title": "Django Migrate Middleware",
      "content_html": "<p>I created a <a href=\"https://pypi.org/project/django-migrate-middleware/\">python package</a> that runs django migrations on every request. It seems silly but has been useful for creating <em>almost</em> stateless AWS Lambda functions using django.</p>\n<!--more-->\n<p>I've found using django in AWS Lambdas pretty useful for creating little websites that need a little server work and can't be fully static. Sometimes I want a little state and the power of django's database integration between a request and a response has proven really useful. Using an ephemeral sqlite database in the <code>/tmp</code> directory is really handy for stuff like this. As such this package allows each request to have a database available.</p>\n<p>An example of this pattern is implemented in my <a href=\"https://setfire.lukewiwa.com\">set fire to emoji</a> website. You can see <a href=\"https://github.com/lukewiwa/set-fire-to-emoji/blob/main/src/config/settings.py#L71\">in the source</a> where my package being used.</p>\n",
      "date_published": "2022-12-28T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/no_growth/",
      "url": "https://lukewiwa.com/blog/no_growth/",
      "title": "No Growth",
      "content_html": "<p>This end of year break my goal is to have no growth. No goals, no targets, no feeling guilty about not finishing that book or that side project.</p>\n<!--more-->\n<p>This year more than any other I feel like everyone around me has a heightened sense of exhaustion. It's palpable, people need the rest and I am no exception. It's been a big year for me personally and next year is likely to be just as full.</p>\n<p>As such I've set a personal goal this holiday period to allow myself to meander, to be slow, to not achieve any personal or professional goals. If I read a book it's because I felt like doing it in the moment. If I create a little side project it's for fun and if it withers on the vine it was meant to be. I'm giving myself permission to suck and be lazy. Perhaps I'm lucky and privileged to have this opportunity but I am taking it with open arms.</p>\n<p>Here's to a break in work and life. A delineation in the forward momentum of the universe. A rest.</p>\n",
      "date_published": "2022-12-23T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/never_use_a_virtual_env_in_a_container/",
      "url": "https://lukewiwa.com/blog/never_use_a_virtual_env_in_a_container/",
      "title": "Wiwa's Axiom for Python Virtual Environments In Containers",
      "content_html": "<p>Don't.</p>\n<!--more-->\n<p>Never use python virtual environments in a container. At least not directly. There is one argument that holds some water and that's using it as a base to copy from in a multi-stage build but on all honesty that's done quite neatly with a pattern like this:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-dockerfile\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ARG</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PYTHON_VERSION=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"3.9\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">FROM</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> python:${PYTHON_VERSION} </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">AS</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> builder</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ARG</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PYTHON_VERSION</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># use whatever method you like here.</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># I usually use poetry but let's use the common</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># primitive for demonstration purposes.</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">COPY</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> requirements.txt requirements.txt</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> python -m pip install -r requirements.txt</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">FROM</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> python:${PYTHON_VERSION}</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ARG</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PYTHON_VERSION</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># Copy files from build container</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">COPY</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> --from=builder /usr/local/lib/python${PYTHON_VERSION}/site-packages/ /usr/local/lib/python${PYTHON_VERSION}/site-packages/</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">COPY</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> --from=builder /usr/local/bin/ /usr/local/bin/</span></span></code></pre>\n<p>If we need to install &quot;standalone python executables&quot; then the best way is through <code>pipx</code>. A good example might be to install <code>cookiecutter</code>.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-dockerfile\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">FROM</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> python</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> python -m pip install pipx</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ENV</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PATH=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"/root/.local/bin:$PATH\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pipx install cookiecutter</span></span></code></pre>\n<p><code>pipx</code> technically does create a virtual environment for each executable but that's not something we need to worry about managing.</p>\n<p>Never use python virtual environments in a container.</p>\n",
      "date_published": "2022-11-27T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/dev_container_features/",
      "url": "https://lukewiwa.com/blog/dev_container_features/",
      "title": "Dev Container Features",
      "content_html": "<p>Dev containers have been a thing for a while now in different forms and it looks like Microsoft is really trying to push them forward as a standard rather than something siloed off in Visual Studio Code. One of their latest additions to the spec is a feature creatively named <a href=\"https://containers.dev/features\"><em>features</em></a>. I took the time to investigate them and <a href=\"https://github.com/lukewiwa/features\">write some of my own</a>.</p>\n<!--more-->\n<p>I've standardised my own development using dev containers for almost everything. It provides me with a consistent developer experience regardless of the machine and has been a huge boon for onboarding people onto big projects that require a lot of moving parts.</p>\n<p>One annoying point that I had with them though is that any development tool you would like in your environment you would have to install into your container and that led to awkward Dockerfiles with patterns like this:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-dockerfile\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">WORKDIR</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> /tmp/dev-setup</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ARG</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> HADOLINT_VERSION=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"v2.10.0\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ARG</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> HADOLINT_ARCH=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"arm64\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> if [ </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"$DEV_ENV\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> = </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"vscode\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ]; then \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    yum install --debuglevel=1 -y zsh vim git amazon-linux-extras \\</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">    #</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">    # Install docker</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    &#x26;&#x26; amazon-linux-extras install docker -y \\</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">    #</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">    # install zsh</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    &#x26;&#x26; sh -c </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> --unattended \\</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">    #</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">    # install hadolint</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    &#x26;&#x26; curl -sSL </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-Linux-arm64\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    --output /usr/bin/hadolint \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    &#x26;&#x26; chmod +x /usr/bin/hadolint \\</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">    #</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">    # Clean up</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    &#x26;&#x26; rm -rf /tmp/dev-setup &#x26;&#x26; yum clean all; \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    fi</span></span></code></pre>\n<p>Ugly as hell and mixes IDE logic with deployment logic. Dev container features allows us to abstract all this away and install an extra layer on top of what your &quot;production&quot; Dockerfile will be. It also allows us to have one bit of abstracted code and use it in enumerable projects. All the above can be replaced with this addition to the <code>devcontainer.json</code> file:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-json\"><span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"features\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"ghcr.io/devcontainers/features/docker-from-docker:1\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {},</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"ghcr.io/devcontainers/features/git:1\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {},</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"ghcr.io/guiyomh/features/vim:0\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {},</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"ghcr.io/devcontainers/features/common-utils:1\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {},</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"ghcr.io/dhoeric/features/hadolint:1\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {},</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n<p>I wrote a couple for myself and it's surprisingly straight forward to get started. There are nice <a href=\"https://github.com/devcontainers/feature-starter\">starter templates</a> which helped immensely in getting a basic idea into a repo. At it's very bare bones all you need is a <code>devcontainer-feature.json</code> file and an <code>install.sh</code> file. But the sky is the limit, there are a lot of powerful options such as setting an entrypoint or messing with the mounting points. See the <a href=\"https://github.com/devcontainers/features/tree/main/src/docker-from-docker\">docker-from-docker</a> feature for something that extensively used the available features.</p>\n<p>The first <a href=\"https://github.com/lukewiwa/features/tree/main/src/shellcheck\">feature I wrote</a> was a simple installation of the <a href=\"https://www.shellcheck.net/\"><code>shellcheck</code></a> tool. Additionally you can add an extension so naturally I added the excellent <a href=\"https://marketplace.visualstudio.com/items?itemName=timonwong.shellcheck\">vscode shellcheck extension</a>. Now for any project I want shellcheck for all I need to do is add a line to my features key in the <code>devcontainer.json</code> file and I'm off to the races.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-json\"><span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"features\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">  \"ghcr.io/lukewiwa/features/shellcheck:0\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {}</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n<p>The second <a href=\"https://github.com/lukewiwa/features/tree/main/src/wait-for-it\">feature</a> was adding the <a href=\"https://github.com/vishnubob/wait-for-it\"><code>wait-for-it</code></a> tool. This one required adding an entrypoint and a few options for what host and port to wait for.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-json\"><span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"features\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">    \"ghcr.io/lukewiwa/features/wait-for-it:0\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"host\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"postgres\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"port\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"5432\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      \"timeout\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"60\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n<p>The only downside is that because these features are build as layers on top of your existing dockerfile if you regularly make changes to your dockerfile you can expect some slow build times if you have a few features to load. All in all though I'm ok with this trade off if it abstracts away all this code from the main production logic of the project.</p>\n",
      "date_published": "2022-11-24T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/setup_mangum_in_aws_lambda_for_django/",
      "url": "https://lukewiwa.com/blog/setup_mangum_in_aws_lambda_for_django/",
      "title": "Setup Mangum in AWS Lambda for Django",
      "content_html": "<p>Here's a quick setup guide to get <a href=\"https://mangum.io/\">mangum</a> working as the request handler in a django project in AWS Lambda.</p>\n<!--more-->\n<p>The first step is to setup the handler function itself. The tutorial in the official docs outlines this process but I like to add a little extra processing to the handler for running select management commands. This could be abstracted out further than here but I find the migrate command enough for my needs. Add the following to <code>asgi.py</code>.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> django.core.asgi </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> get_asgi_application</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> django.core.management </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> call_command</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mangum </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Mangum</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mangum.types </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> LambdaContext, LambdaEvent</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">application </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> get_asgi_application()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> handler</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(event: LambdaEvent, context: LambdaContext):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> event.get(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"migrate\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        call_command(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"migrate\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">interactive</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">False</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    else</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Mangum(application, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">lifespan</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"off\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)(event, context)</span></span></code></pre>\n<p>The next step is to setup logging. It seems under default configs this goes missing when deployed so the below config tweaks the default django config just slightly so that mangum largely mirrors the output you would get running this on a standard asgi or wsgi framework. Add the following to your settings file (usually <code>settings.py</code>).</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">LOGGING</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">    \"version\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">1</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">    \"disable_existing_loggers\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">False</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">    \"formatters\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"django.server\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"()\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"django.utils.log.ServerFormatter\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"format\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">{server_time}</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">] </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">{message}</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"style\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"{\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    },</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">    \"handlers\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"django.server\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"level\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"INFO\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"class\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"logging.StreamHandler\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"formatter\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"django.server\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        },</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    },</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">    \"loggers\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"mangum\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"handlers\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: [</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"django.server\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">],</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"level\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"INFO\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">            \"propagate\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">False</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        },</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    },</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n",
      "date_published": "2022-11-07T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/australia_pommel_horse/",
      "url": "https://lukewiwa.com/blog/australia_pommel_horse/",
      "title": "Australia and Pommel Horse",
      "content_html": "<p>There is something so wrong and rigid about how we teach pommel horse in Australia.</p>\n<p>In contrast we seem to be so savvy and inventive when it comes to high bar or even floor. Our &quot;middle class&quot; routines are adequate and serviceable.</p>\n<!--more-->\n<p>Sure we had Prashanth and now Jesse but where are our all around stalwarts getting solid mid 13s?</p>\n<p>I'm telling on myself here. Quite obvious that pommel was my worst. Part of me reckons with a coach that thought a little more outside the box we could've gotten a lot more out of me on that apparatus.</p>\n<p>Last aimless thought. Whatever has worked for us on high bar needs to be bottled up somehow and applied to pommel.</p>\n<p>Some nations have achieved this. GB and Brazil are good recent examples.</p>\n",
      "date_published": "2022-11-03T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/wiwas_axiom_for_python_packaging/",
      "url": "https://lukewiwa.com/blog/wiwas_axiom_for_python_packaging/",
      "title": "Wiwa's Axion For Python Packaging",
      "content_html": "<p>Python packaging will be &quot;solved&quot; when a packaging and dependency manager that is written in a compiled language becomes mainstream.</p>\n<!--more-->\n<p>There are many efforts to fix packaging and dependency resolution on Python. PDM, Poetry, Pipenv and many other efforts have been made in this space. The fundamental flaw in my opinion as that they are all written in Python. Javascript is having a boon in tooling due in part to many of these tools being written in Go, Rust or some other language that can compile down to a static binary and releases itself from the hard dependency of the language it's built for.</p>\n<p>I look forward to the day that we see that same explosion for tools in Python.</p>\n",
      "date_published": "2022-10-26T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/aws_lambda_and_django_with_sqlite/",
      "url": "https://lukewiwa.com/blog/aws_lambda_and_django_with_sqlite/",
      "title": "AWS Lambda and Django With Sqlite",
      "content_html": "<p>Running Django on AWS Lambda has become fairly mainstream and there are many tools to assist with this goal. There is however a pain point which is admittedly not a conventional use case but a possible use case none the less. Django supports a version of sqlite that is above the current version supported on the underlying operating systems of the AWS python lambda implementation. If you're interested lambda functions run on Amazon Linux 1 and 2 which are based on CentOS 6 and 7 respectively.</p>\n<!--more-->\n<p>There are a few ways to get around this, all of them involving either building the source yourself or standing on the shoulders of <a href=\"https://github.com/FlipperPA/django-s3-sqlite/\">other people who already have</a>. Some people have created <a href=\"https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html\">lambda layers</a> to add the required libraries.</p>\n<p>With lambda now supporting container images I thought an easy thing to do would be to precompile sqlite inside the image using the build process in the <a href=\"https://github.com/coleifer/pysqlite3\">pysqlite3</a> repository. This repo formalises a build process for creating more up to date <code>sqlite3</code> python modules. Using the magic of <a href=\"https://docs.docker.com/develop/develop-images/multistage-build/\">multi-stage builds</a> we can build everything and only copy over the relevant output files. The final image won't even have the build tools which cuts down on image size. Here's an example:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-dockerfile\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ARG</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PYTHON_VERSION=3.9</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">FROM</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> public.ecr.aws/lambda/python:${PYTHON_VERSION} </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> build</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> yum groupinstall -y </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Development Tools\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> &#x26;&#x26; \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    yum install -y tcl</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">WORKDIR</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> /</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> git clone --depth 1 --branch master https://github.com/coleifer/pysqlite3.git</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> curl -sSL https://www.sqlite.org/src/tarball/sqlite.tar.gz?r=release \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    --output sqlite.tar.gz</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> tar xzf sqlite.tar.gz</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">WORKDIR</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> /sqlite</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  ./configure &#x26;&#x26; make sqlite3.c</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cp /sqlite/sqlite3.[ch] /pysqlite3/</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">WORKDIR</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> /pysqlite3</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> python setup.py build_static build</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">FROM</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> public.ecr.aws/lambda/python:${PYTHON_VERSION}</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ARG</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PYTHON_VERSION</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">COPY</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> --from=build /pysqlite3/build/lib.linux-*/pysqlite3/_sqlite3.*.so /var/lang/lib/python${PYTHON_VERSION}/lib-dynload/</span></span></code></pre>\n<p>You can see the source at <a href=\"https://github.com/lukewiwa/aws-lambda-python-sqlite\">github</a> and pull the final product from <a href=\"https://hub.docker.com/r/lukewiwa/aws-lambda-python-sqlite\">dockerhub</a>. I've set up monthly builds so hopefully things keep up to date for a while.</p>\n",
      "date_published": "2022-10-20T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/another_refresh/",
      "url": "https://lukewiwa.com/blog/another_refresh/",
      "title": "Another Fresh Lick Of Paint",
      "content_html": "<p>I finally found the time and space to do my semi regular migration to a new static site generator. I thought it was time to step away from Vue and try something new, partly out of curiosity and partly because I really like JSX as a templating language. And frankly I'm just not really up for the migration from Vue 2 to Vue 3.</p>\n<!--more-->\n<p>This site is now generated using <a href=\"https://lumeland.github.io/\">lume</a>. Lume is a static site generator built on top of the <a href=\"https://deno.land\">Deno</a> programming language. I've been somewhat enamoured with Deno when it came out. It's pretty fantastic to download a single binary and it's got everything I need for compilation, formatting and typechecking.</p>\n<p>I've used Deno for a few small scripts (which I think it excels at) and I wanted to get into a deeper project with it. Additionally I love JSX as a templating language and was wondering whether there was anything out there that would build static web sites using it. I mean particularly calling the <code>renderToStaticMarkup</code> method in react at build time and leaving just HTML files to serve. I wanted to steer clear of tools like Gatsby and Next.js, just pure HTML in the end.</p>\n<p>Well turns out lume really scratched that itch. It's a neat little framework and suits my needs ok. I do enjoy using TailwindCSS but found no clear way to install it using Deno so I ended up using TachyonsCSS which covers similar ground.</p>\n<p>I've also added <a href=\"https://newcss.net/\">new.css</a> for base styles because I'm lazy. It turns out it has a dark mode which I'm not in love with but I'll take it and hopefully finally learn the particulars of detecting dark mode preferences.</p>\n<p>So enjoy the different colour scheme, point out the inconsistency in styles and subscribe to my <a href=\"https://lukewiwa.com/feed/blog/rss.xml\">rss</a> which I'm sure I borked somehow.</p>\n",
      "date_published": "2022-02-19T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/notification_service_aws/",
      "url": "https://lukewiwa.com/blog/notification_service_aws/",
      "title": "Personal Notifications Using AWS",
      "content_html": "<p>I've found quite a few uses for recurring and scheduled scripts in my personal life. In days gone by I would have run these in a cron job on a server somewhere but as I've gotten into the weeds of AWS infrastructure I've leant on some AWS services more for these little personal conveniences. My preference nowadays is to use AWS Lambda as my first point of call and integrate other services as I need them. I'm very confident that AWS won't be deprecating their Lambda platform and they connect well to Cloudwatch, meaning I don't have to worry about creating a logging solution. Additionally now that I'm pretty confident with the AWS CDK for spinning up infrastructure it's a breeze to connect other services to assist with any other needs. Today I'll go through one example which provided a flexible and <strong>cheap</strong> solution to my problem.</p>\n<!--more-->\n<p>It can be quite difficult to find a good sports massage service and I've recently been very lucky to find a fantastic one. Of course they are booked out months in advance, which doesn't bother me too much but sometimes after a big training session I'll need something worked on sooner rather than later. Appointments do pop up from time to time due to cancellations but I didn't really want to keep checking their website throughout the day. So it made sense to automate the process somewhat.</p>\n<p>Firstly the code for the lambda. Fairly easy to test locally (minus the SNS dispatch), I've abridged a few things here but you get the idea. Basically it performs a GET request to the API endpoint for appointments available, if the response array has anything in it send an SMS message to all subscribers.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-typescript\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> axios </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"axios\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { DateTime } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"luxon\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { PublishCommand, SNSClient } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"@aws-sdk/client-sns\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { URL, URLSearchParams } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"url\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { EventBridgeHandler } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"aws-lambda\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">interface</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MonthYearProps</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">  month</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">  year</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">  days</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">?:</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">[];</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">interface</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ResponseAppointment</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">  day</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> number</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">  day_parts</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> |</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 2</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> |</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 3</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> region</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> process.env.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">AWS_REGION</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> TopicArn</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> process.env.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">TOPIC_ARN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> apiEndpointUrl</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> process.env.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">API_ENDPOINT_URL</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> nextMonths</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Array</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">2</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  .</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">fill</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(DateTime.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">now</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">().</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">setZone</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Australia/Canberra\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">))</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  .</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">map</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">((</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">date</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DateTime</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">index</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=></span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> date.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">plus</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">({ months: index }));</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> apiEndpoint</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ({ </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">month</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">year</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> }</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MonthYearProps</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=></span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> endpoint</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> URL</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(apiEndpointUrl);</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> searchParams</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> URLSearchParams</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    month: month.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    year: year.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  });</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  endpoint.search </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> searchParams.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> endpoint.href;</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">const</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> sendNotification</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> async</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ({ </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">month</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">year</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">days</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> }</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MonthYearProps</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=></span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> client</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> SNSClient</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">({ region });</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> dates</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> days.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">map</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">((</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">day</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=></span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> `${</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">day</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">}/${</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">month</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">}/${</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">year</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">}`</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">).</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">join</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\", \"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> message</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> `Appointments available: ${</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">dates</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">}`</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> command</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PublishCommand</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Subject: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Appointments available\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Message: message,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    TopicArn,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  });</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  await</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> client.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">send</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(command);</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">export</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> const</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> handler</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> EventBridgeHandler</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">&#x3C;</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">string</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">unknown</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">void</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">> </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> () </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=></span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  nextMonths.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">forEach</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">async</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ({ </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">month</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">year</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> }) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=></span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    try</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">      const</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">data</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> }</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">data</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ResponseAppointment</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> await</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> axios.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">get</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">        apiEndpoint</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">({ month, year })</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      );</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">      if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (data.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">length</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> !==</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> days</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> data.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">map</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(({ </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">day</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> }) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=></span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> day);</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        await</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> sendNotification</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">({ month, year, days });</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">else</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        console.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">log</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">`No appointments found on ${</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">month</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">}/${</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">year</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">}`</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">);</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">catch</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (error) {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      console.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">error</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(error);</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  });</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">};</span></span></code></pre>\n<p>Now let's build the infrastructure around it. I've pretty much standardised on AWS CDK for deployment since it does a great job of holding your hand and creating a lot of the interlinking IAM permissions for you. There's only a few things going on here.</p>\n<ol>\n<li>Create an SNS topic.</li>\n<li>Create a lambda function and point it to the above code, we need to wait for the SNS topic to finish being created since we're sending the SNS topic ARN to the lambda function in the form of an environment variable.</li>\n<li>Grant permissions using the convenience method <code>grantPublish</code>.</li>\n<li>Subscribe any emails (mine) to the topic.</li>\n<li>Schedule the lambda on a recurring basis. I chose once every hour during business hours to not spam either my own email or the booking website.</li>\n</ol>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-typescript\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  Stack,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  StackProps,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  aws_sns </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> sns,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  aws_lambda </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> lambda,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  aws_logs </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> logs,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  aws_sns_subscriptions </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> subscriptions,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  aws_events </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> events,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  aws_events_targets </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> targets,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">} </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"aws-cdk-lib\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { NodejsFunction } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"aws-cdk-lib/aws-lambda-nodejs\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> { Construct } </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"constructs\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> *</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> path </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"path\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">export</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> InfraStack</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> extends</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Stack</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">  constructor</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">scope</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Construct</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">id</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">:</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> string</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">props</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">?:</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> StackProps</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">    super</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(scope, id, props);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> emails</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> process.env.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">EMAILS</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">?.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">split</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\",\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">??</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> [];</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> topic</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> sns.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">Topic</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">this</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"WiwaNotificationsTopic\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      displayName: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Available Appointments\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    });</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> fn</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> NodejsFunction</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">this</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"WiwaNotificationsLambda\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      entry: path.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">join</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(__dirname, </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"..\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"..\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"src\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"index.ts\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      description: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Check booking site for open appointments\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      environment: {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        TOPIC_ARN: topic.topicArn,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      },</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      runtime: lambda.Runtime.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">NODEJS_14_X</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      logRetention: logs.RetentionDays.</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">ONE_WEEK</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    });</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    fn.node.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">addDependency</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(topic);</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    topic.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">grantPublish</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(fn);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    emails.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">forEach</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">((</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">email</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=></span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      topic.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">addSubscription</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">new</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> subscriptions.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">EmailSubscription</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(email));</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    });</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    const</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> rule</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> =</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> new</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> events.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">Rule</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">      this</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">      \"WiwaNotificationsScheduleRule\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      {</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        schedule: events.Schedule.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">cron</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">({ minute: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"24\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, hour: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"0-8,20-23\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> }),</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        description: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"Check every hour during the day\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    );</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    rule.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">addTarget</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">new</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> targets.</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">LambdaFunction</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(fn));</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">  }</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">}</span></span></code></pre>\n<p>And that's it! This now sends me open bookings which probably pop up once or twice a week. It's a nice little piece of functionality which lives well within the free tier limits of AWS.</p>\n",
      "date_published": "2022-02-11T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/guitar_tabs_fortune_teller/",
      "url": "https://lukewiwa.com/blog/guitar_tabs_fortune_teller/",
      "title": "Fortune Teller by Xavier Rudd - Guitar Tabs",
      "content_html": "<p>There are no good tabs for this track online so I finally figured it out and created them.</p>\n<!--more-->\n<iframe style=\"border-radius:12px\" src=\"https://open.spotify.com/embed/track/5CDEX5cmjHeYhkj7u9zcbo?utm_source=generator\" width=\"100%\" height=\"80\" frameBorder=\"0\" allowfullscreen=\"\" allow=\"autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture\"></iframe>\n<p>For the authentic key you can uptune 2 half steps or put a capo on the second fret.</p>\n<h2>Verse</h2>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>E|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>B|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>G|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>D|--------2h3p2p0-----|---------0h3-3--0h3-|</span></span>\n<span class=\"line\"><span>A|-----0h3-------0h3/5|----0h3p2-----------|</span></span>\n<span class=\"line\"><span>D|-0------------------|---------0h3-3--0h3-|</span></span></code></pre>\n<h2>Pre-Chorus</h2>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-text\"><span class=\"line\"><span>E|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>B|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>G|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>D|--------2h3---------|--------2h3---------|</span></span>\n<span class=\"line\"><span>A|-----0h3------0h3---|-----0h3------0h3---|</span></span>\n<span class=\"line\"><span>D|-0----------------3-|-0----------------3-|</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>E|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>B|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>G|--------------------|--------------------|</span></span>\n<span class=\"line\"><span>D|--------2h3---------|---5--3--2-3-2------|</span></span>\n<span class=\"line\"><span>A|-----0h3------0h3---|--------------------|</span></span>\n<span class=\"line\"><span>D|-0----------------3-|---5--3--2-3-2------|</span></span></code></pre>\n",
      "date_published": "2022-01-22T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/multiple_pythons_docker_image/",
      "url": "https://lukewiwa.com/blog/multiple_pythons_docker_image/",
      "title": "Docker Image With Multiple Python Installs",
      "content_html": "<p>Not sure whether this has been done before but I created a <a href=\"https://hub.docker.com/r/lukewiwa/pythons\">docker container with four (4) Python installs</a>. To get there I heavily abused <a href=\"https://docs.docker.com/develop/develop-images/multistage-build/#use-an-external-image-as-a-stage\">Docker multistage builds from external images</a>. You can check out the <a href=\"https://github.com/lukewiwa/pythons\">repo</a> for the gory details. Now I'm not insane there is a specific reason why I did this.</p>\n<!--more-->\n<p>I'm pretty all in on Visual Studio Code dev containers and I also like having sensible CI pipelines to catch things. I recently wrote a little library that I diligently tested against multiple python versions using <a href=\"https://tox.readthedocs.io/en/latest/index.html\">tox</a>. I like to be able to test and debug this locally and not rely on the pipeline specific syntax. So the challenge was to create an environment locally and in the pipeline where a single <code>tox</code> command would kick start all my tests. This seems to be the solution that satisfies this criteria.</p>\n<p>Here's an example using Github Actions:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">name</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">tests</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">on</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">push</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">jobs</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">  test</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">    runs-on</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">ubuntu-latest</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">    container</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">      image</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">lukewiwa/pythons:latest</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">    steps</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">      - </span><span style=\"color:#22863A;--shiki-dark:#85E89D\">name</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">tox</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">        run</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">tox</span></span></code></pre>\n<p>And one for bitbucket pipelines:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-yaml\"><span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">image</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">: </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">lukewiwa/pythons</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">pipelines</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">  default</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    - </span><span style=\"color:#22863A;--shiki-dark:#85E89D\">step</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#22863A;--shiki-dark:#85E89D\">        script</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            - </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">tox</span></span></code></pre>\n",
      "date_published": "2021-05-26T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/add_the_aws_cli_to_a_dockerfile/",
      "url": "https://lukewiwa.com/blog/add_the_aws_cli_to_a_dockerfile/",
      "title": "Add the AWS CLI to a Dockerfile",
      "content_html": "<p>Using the magic of multi-stage builds using <a href=\"https://docs.docker.com/develop/develop-images/multistage-build/#use-an-external-image-as-a-stage\">external images</a> and Amazon's official aws cli <a href=\"https://hub.docker.com/r/amazon/aws-cli\">docker image</a> it's actually pretty easy to add the AWS CLI to a huge amount of containers.</p>\n<!--more-->\n<p>I haven't tested this on every single docker image out there but for my needs this is a quick and easy way to add the AWS CLI. Configuration can be done using environment variables so no need for mucking around with config files.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-dockerfile\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">ARG</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> IMAGE</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">FROM</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ${IMAGE}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># Install AWS CLI</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">COPY</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> --from=amazon/aws-cli:latest /usr/local/aws-cli/ /usr/local/aws-cli/</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">RUN</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ln -s /usr/local/aws-cli/v2/current/bin/aws \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        /usr/local/bin/aws &#x26;&#x26; \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    ln -s /usr/local/aws-cli/v2/current/bin/aws_completer \\</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        /usr/local/bin/aws_completer</span></span></code></pre>\n",
      "date_published": "2021-04-25T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/wsl2_import_ubuntu_image/",
      "url": "https://lukewiwa.com/blog/wsl2_import_ubuntu_image/",
      "title": "Importing Rootfs Ubuntu Images into WSL2",
      "content_html": "<p>I've been generally impressed with the preview of Windows Subsystem for Linux 2 (WSL2) and it's integration between the windows desktop and the underlying linux kernel.</p>\n<!--more-->\n<p>I did notice on a <a href=\"https://www.youtube.com/watch?v=UCAid-NQwWU&amp;feature=youtu.be\">talk by one of the main devs</a> that you could manually import and export linux and container images. This seemed like a nice idea if you wanted to separate dev environments or perhaps have separate work or personal accounts. The demonstration showed exporting and importing docker containers and linux tarballs using the Cool New <code>wsl</code> command in powershell. My dilemma was that I had already setup one Ubuntu environment and wanted a clean one from scratch. So I did a little digging and managed to get one working with a few hiccups along the way.</p>\n<h1>1. Download The Latest Rootfs Image</h1>\n<p>The Ubuntu wiki has a very neat <a href=\"https://wiki.ubuntu.com/WSL#Installing_Ubuntu_on_WSL_via_rootfs\">guide</a> on how to import an image. Be sure to download the nightly image with the <code>-wsl.rootfs.tar.gz</code> suffix.</p>\n<h1>2. Import The Image</h1>\n<p>As in the Ubuntu wiki run the command below. I decided to keep the image location in my home directory, in a <code>distro</code> folder.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-shell\"><span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">wsl</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> --import</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> &#x3C;</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">DistributionNam</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">e</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">></span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> &#x3C;</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">InstallLocatio</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">n</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">></span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> &#x3C;</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">FileNam</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">e</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">></span></span></code></pre>\n<h1>3. Create A User</h1>\n<p>Drop into the wsl2 instance by running the name of the distribution as a command in powershell, or by using the excellent new Windows Terminal app. By default the only user in the image is the root user. While the stock standard Ubuntu image you install from the Microsoft Store prompts you to create a user at startup we'll have to do the prompting ourselves here. The <code>adduser</code> command should get you what you need. <strong>Be sure to add your new user to the sudo group</strong>.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-shell\"><span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">adduser</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> &#x3C;</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">usernam</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">e</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">></span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> --ingroup</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> sudo</span></span></code></pre>\n<h1>4. Change The Default User</h1>\n<p>Now that we have created the user we would like it to be the default user whenever you start a new shell. This is a little tricky and there's a <a href=\"https://github.com/Microsoft/WSL/issues/3974\">github issue</a> outlining this pain point and some possible work arounds. I went with the powershell option but pick your poison.</p>\n<p>And that's it, enjoy separating those concerns.</p>\n",
      "date_published": "2020-05-26T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/dark_wiwa_published/",
      "url": "https://lukewiwa.com/blog/dark_wiwa_published/",
      "title": "Dark Wiwa Published",
      "content_html": "<p>I've been using my self made <a href=\"https://lukewiwa.com/blog/new_text_editor_theme\">theme for Visual Studio Code</a> for some time now. With every new installation of vscode I've had to manually copy and paste the project directory into the proper extensions directory. Couple this with the fact that I am now using the excellent <a href=\"https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync\">Settings Sync</a> extension to synchronise my setup between machines this manual copypasta becomes tiresome.</p>\n<!--more-->\n<p><img src=\"https://lukewiwa.com/img/dark_wiwa.png\" alt=\"Dark Wiwa\"></p>\n<p>Well no more because I have now published that theme for all to enjoy and/or <a href=\"https://github.com/lukewiwa/dark_wiwa/issues/new\">criticise</a>. Check out the <a href=\"https://marketplace.visualstudio.com/items?itemName=lukewiwa.dark-wiwa\">Marketplace page</a> or if you like or install it from the command line:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">code</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> --install-extension</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> lukewiwa.dark-wiwa</span></span></code></pre>\n",
      "date_published": "2019-05-20T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/mygov_centrelink_name_change/",
      "url": "https://lukewiwa.com/blog/mygov_centrelink_name_change/",
      "title": "How To Link Your MyGov Account To Centrelink After A Name Change",
      "content_html": "<p>My wife and I are about to have a baby, as such my wife is applying for paid parental leave which is painfully done through the Centrelink system. We also got married in the intervening years between us being on Centrelink regularly and now, and she subsequently decided to take my last name after our marriage. This has caused a headache linking accounts in her MyGov account since the information doesn't match and the system refuses to move forward. We eventually found a solution after tearing our hair out. I thought I would leave it here for any other lost souls who need this guidance.</p>\n<!--more-->\n<p>Unlink all your accounts! This will allow you to link your Centrelink account. Yes you will have to relink your other accounts if you wish but this is probably a small price to pay versus being on hold with Centrelink for an interminably long amount of time.</p>\n",
      "date_published": "2019-02-10T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/ease_of_deployment/",
      "url": "https://lukewiwa.com/blog/ease_of_deployment/",
      "title": "JavaScript Remains The Best Deployment Target",
      "summary": "It is abundantly clear that targeting the web, even as a Single Page App with heavy JavaScript, hits that sweet spot of development time, availability and cost.",
      "content_html": "<p>I have finally got to the point where I can throw together a single purpose web app over the course of a weekend. I recently was in need of a quick utility that could translate the session times of the recent Gymnastics World Championships, being held in Doha, into my own timezone. I also thought this was a problem that more people probably had so I started thinking about the quickest, easiest and cheapest way to share that utility with others.</p>\n<p>Just by shear chance that I work mostly on Python and Javascript projects, and deciding I needed to brush up on my React skills I chose a Single Page App as my distribution method of choice. So I rolled up my sleeves and built a <a href=\"https://lukewiwa.com/dohagym2018\">web app that did just that</a>.</p>\n<p>Much as I've wanted to expand into learning a bit of Android development, on this occasion my lack of knowledge and time constraints pushed me towards the languages and platforms that I already know. What is abundantly clear though is that targeting the web, even as a Single Page App with heavy JavaScript, clearly hits that sweet spot of development time, availability and cost.</p>\n<h1>Advantages</h1>\n<h2>Development Time</h2>\n<p>Javascript is ugly as hell but with modern JavaScript libraries like React and Vue, among others, and their requisite cli commands for bootstrapping a boilerplate app, developing for the current web is pretty nice in the scheme of things. It's easy to iterate, there are plenty of resources out there to help with most issues and there is a metric tonne of helper libraries and components just a quick NPM install away. It truly is a matter of gluing a few pieces together.</p>\n<h2>Availability</h2>\n<p>Nothing has more reach than the Web at this point in time. No app store or packaged app is anywhere near as easy as to share as a URL with anybody and everybody. Additionally there is no wait times for app auditing or to register as a developer. There are dozens of services that will host your static files.</p>\n<h2>Cost</h2>\n<p>Not only are there dozens of static hosting services but the vast majority of them are free. No registration fees, plenty don't even ask for a credit card number. I was amazed at how simple it is to push a bunch of static files up to a service and grab a URL.</p>\n<h1>Disadvantages</h1>\n<h2>Monetisation</h2>\n<p>Charging for Web Apps, especially small, single purpose utility ones is notoriously hard to do. This is the strength of an App Store where the user is more conditioned to pay for an app (although I'm not so sure about that anymore). I didn't need to monetise my app but if you do care about pulling in a bit of spare change perhaps the current web isn't your best bet.</p>\n<h2>Further Building</h2>\n<p>If you ever get to the point where you need to start building more features on top of your app or you start needing a database to back it then you are looking at a bit of rebuilding and rethinking your strategy.</p>\n<h1>Conclusion</h1>\n<p>All in all I was pretty happy that I could build a utility that did exactly what I needed in pretty quick time, and that I could share with the world easily and freely.</p>\n",
      "date_published": "2018-11-10T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/pinocchio/",
      "url": "https://lukewiwa.com/blog/pinocchio/",
      "title": "Pinocchio",
      "content_html": "<p>My siblings and I, when growing up, had a VHS tape of Pinocchio which we would watch on occasion. The interesting thing about it was that this was a very particular adaptation, not the one made famous by Disney but a decidedly creepier and more disturbing version. It left such a mark on us that it became a frequent topic of conversation when meeting for family events as adults. We described it with such disdain that our friends and significant others seemed sceptical as to how bad it could be. It appeared no amount of googling could find this mythical portrayal, until now.</p>\n<!--more-->\n<p>The truth is that this Pinocchio adaptation was a BBC series originally airing in 1978. Produced by Barry Letts who famously produced many Doctor Who episodes and various other BBC productions. This version is a darker reading of original text. I found an obscure upload of the entire series of the show on DailyMotion and having rewatched (parts) of it I can definitely say my family's fears are fully founded.</p>\n<p><img src=\"https://lukewiwa.com/img/pinocchio.jpg\" alt=\"Look at this god damn nightmare\"></p>\n<p>You could not make a more unsettling version if you tried, the marionette of the title character is terrifying with jet black eyes, a shrill voice and body movements deep within the uncanny valley. The supporting characters are not much better with humans with elaborate face make up playing the fox and the cat, and a whole host of characters who are cruel and unforgiving. Interspersed musical numbers grate the ears and crude green screen techniques round out the package.</p>\n<p><img src=\"https://lukewiwa.com/img/pinocchio_cat_fox.jpg\" alt=\"More nightmares\"></p>\n<p>If you're curious enough I've linked to the offending videos <a href=\"http://www.dailymotion.com/video/x16gdo4\">here</a> and <a href=\"http://www.dailymotion.com/video/x16hhk0\">part two here</a> and you, dear reader, can confirm the absolute skin crawling nature of this imagining of Pinocchio.</p>\n",
      "date_published": "2018-04-18T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/a_good_year/",
      "url": "https://lukewiwa.com/blog/a_good_year/",
      "title": "A Good Year",
      "content_html": "<p>It's that time to take stock of another year in my life and I think it's worthy of a blog post. Professionally and personally a lot has happened and I'm hoping the next year can be very much like it.</p>\n<!--more-->\n<p>Starting with the personal, I cemented my partnership with my bestie Megan by finally tying the knot. I can definitively say we (she mostly) organised a damn good wedding and we had a ton of fun celebrating with the special people in our lives. I have no desire to have another one any time soon. On that note my peers and friends have had their own special moments including marriage and kids and I'm privileged to have been a small part of these.</p>\n<p>I've always been a late starter to professional work but this year was the one in which I took small steps into a larger world. A rocky start to the year saw my work experience in controls engineering not flourish into a career. For various reasons my internship was not seen valuable enough to move me into the team proper however I learnt a lot from the experience.</p>\n<p>My few months of work experience did see me fulfil the criteria for graduation and after ten long years chipping away I can finally call myself a graduate! Thanks to my gymnastics career this was a very slow burn which sometimes left me wondering whether I'd even finish but this year I finally have closure on this chapter of my life.</p>\n<p>Finally I was extremely lucky to get a graduate engineering role in civil engineering. I am excited for the future and I seem to be working with a pretty good group of people.</p>\n<p>All in all I'm hoping for more years like this one.</p>\n",
      "date_published": "2017-12-29T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/new_text_editor_theme/",
      "url": "https://lukewiwa.com/blog/new_text_editor_theme/",
      "title": "New Text Editor and Syntax Colour Scheme",
      "content_html": "<p>After trying a few different text editors out, from the standard of gedit, to sublime text and even attempts at nano, I've standardised on Visual Studio Code which seems to be a standout for a few reasons. I'm not exactly an advanced programmer but when I am punching in a few crummy lines of python or writing some markdown notes I've reached for VSCode time and again. It seems to be both extremely flexible, as well as fairly intuitive. The VSCode's extension ecosystem and the git interface is straight forward and easy to use.</p>\n<!--more-->\n<p>There was however one bug bear of mine which irritated me somewhat when using it. I love the <strong>Vibrant Ink</strong> theme and there is an extension available, however the available theme does not have markdown support and I spend a lot of my time punching in notes using the markdown syntax. As such I attempted to do some tweaking of the Vibrant Ink theme but I wasn't happy with the results. I decided to eventually start from scratch.</p>\n<p><img src=\"https://lukewiwa.com/img/dark_wiwa.png\" alt=\"Dark Wiwa\"></p>\n<p>I made use of Microsoft's <a href=\"http://yeoman.io/\">Yeoman</a> to generate a scaffold theme and then used a great <a href=\"https://github.com/Tyriar/vscode-theme-generator\">generator</a> to populate that scaffold with a basic colour scheme and added my own tweaks. My choice of colours ended up looking very festive. I've put the theme up on <a href=\"https://github.com/lukewiwa/dark_wiwa\">Github</a>.</p>\n",
      "date_published": "2017-12-06T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/automation_and_driving_performance/",
      "url": "https://lukewiwa.com/blog/automation_and_driving_performance/",
      "title": "Does Driving Performance Degrade Or Improve With Increased Automation",
      "content_html": "<p>During my studies I took a human factors and ergonomics course and found it quite interesting to see the implementation of human factors and how it relates to engineering in general. I was fascinated to study the psychology of how people in general accept certain designs. For better or worse here's an essay which I wrote answering the question &quot;Does Driving Performance Degrade Or Improve With Increased Automation&quot;.</p>\n<!--more-->\n<p>How is driving performance affected by the introduction of automated systems? It appears the answer is more complex than a simple more or less. There has been increased research into the automation of the tasks involved with driving (Cottrell &amp; Barton, 2013; Merat &amp; Lee, 2012). Coupled with this there is increased study into how these automation’s affect the driver of the vehicle and the safety and reliability of the car in general. Adoption of these automation systems is very much dependant on the willingness of the general driving population to use them (Cottrell &amp; Barton, 2013; Merat &amp; Lee, 2012) and how they interact with the system (Carsten, Lai, Barnard, Jamson, &amp; Merat, 2012; Larsson, 2012; Young &amp; Stanton, 2002; Muhrer, Reinprecht, &amp; Vollrath, 2012). In that sense human factors in driving automation is an important area of study. It can be argued that driving performance increases up to the point of the confines that the automated system works under, however as the system fails and control of the vehicle is handed over to the driver driving performance degrades to a point greater than manual operation.</p>\n<p>What is automation and how is it implemented? Driving and operating a vehicle is a complex task which involves many physical and cognitive tasks, often times concurrently (Carsten et al., 2012). Automation systems aim to control a selection of these tasks, or even sometimes all of these tasks, thereby relieving the driver of some cognitive load and increasing the driving performance of the vehicle. The primary tasks of driving can be broken down into longitudinal and latitudinal patterns (Carsten et al., 2012), which basically mean the throttling and steering respectively. As such there has been an effort to cut away some of these tasks and subtasks by using automated systems. These automated systems often look to take care of mundane or repetitive tasks which would allow the driver to concentrate on the higher level tasks associated with operating a vehicle (Larsson, 2012).</p>\n<p>A lower level automation system is the automation of a single task which can be coupled with warning systems. Such an example is the forward collision warning (FCW) and forward collision warning plus braking system (FCW+) (Muhrer et al., 2012). This system indicates to a driver that he/she should take action to avoid a forward collision, the plus braking component initiates the braking for the driver in the event of an imminent collision. This system also gives both visual and audio cues in an increasingly attention grabbing sequence to indicate the threat of collision. Additionally it can also be turned off by the driver allowing full manual control.</p>\n<p>Some higher level examples include the, already commercially available Active Cruise Control (ACC) and Active Braking (AB), automated lane keeping and automated highway driving (Larsson, 2012; Muhrer et al., 2012). These automation systems operate in different ways and have different visual and auditory cues depending on the task. In the case of ACC it is engaged by the driver of the vehicle and has a visual cue on the dashboard to indicate that the system is running. The system works in tandem with the driver rather than taking over an entire task. To deactivate there are several ways, the driver can simply hit the brakes, or turn it off at the control point or the system will disengage itself when outside its boundaries of use (Larsson, 2012).</p>\n<p>In some circumstances however there has been a focused push on automating the entire vehicle and taking the control away from the driver to the point of simply being an observer rather than an active participant in the operation of the vehicle, the so called ”driverless car” (Merat &amp; Lee, 2012). In this circumstance the driver does not have any role and the system makes all the decisions.</p>\n<p>Why does automation matter and what are the advantages? Automation from a high level is seen as an improvement to the current limitations of manual operation of a vehicle. The advantages include increased sensitivity to road and traffic conditions (Muhrer et al., 2012), more efficiency and better assistance and comfort to drivers, especially with low driving skills (Merat &amp; Lee, 2012). Automation is also proposed as a solution to reducing congestion by increasing road capacity (Merat &amp; Lee, 2012). In addition to these advantages automation systems allow the abstraction away of mundane or low level tasks allowing access to vehicle driving to a wider population. Ultimately the entire automation of the vehicle could possibly allow huge advantages to many people. These can include the accessibility to driving a large population that previously couldn’t. This includes the disabled, such as blind or physically incapable of driving, will have a level of mobility not previously accessible to them. For all these reasons automation is a sought after technology by car manufacturers and as such can require intense human factors study.</p>\n<p>Some more specific advantages can be seem in human factors studies. Within the limitations of the automation systems they are extremely effective and reduce risk on the road (Muhrer et al., 2012). Automated systems have distinct advantages to human drivers. Some include the inability of automated systems to get distracted, automated systems use sensors which is outside the scope of human senses and automated systems sense multidirectionally. Automated systems also are able to carry out several tasks at once which humans struggle at. In addition to this the reaction time of automated systems are well above that of the average driver (Muhrer et al., 2012). Additionally automated systems do not suffer from fatigue, meaning they stay much more resilient than regular drivers.</p>\n<p>What are the potential drawbacks of these automated systems? As automation increases there come some drawbacks which can affect the adoption and safety of these systems. One drawback is the edge cases and limitations of the system for which it was not designed for. A good example of this is for automatic throttling (ACC) the systems forward radar loses contact with the forward car due to a sharp bend in the road (Larsson, 2012). As the system starts to reach its operating limits the system has to make a decision as to when to fail and also how to fail and hand back control to the driver (Merat &amp; Lee, 2012). At the system limitations the system has to be intelligent enough to know when and how to do this otherwise it remains a bad system. Additionally automated systems suffer from another seemingly widespread drawback, that is that with increased automation the awareness of the driver drops and increases the reaction time, and therefore risk, in situations where the system fails and the driver has to take over operation of the vehicle (Cottrell &amp; Barton, 2013; Merat &amp; Lee, 2012; Young &amp; Stanton, 2002). Although countering the last point, if the driver is given more time and education with the system to learn its limitations and quirks, the reaction times and acceptance increases reducing the risk in the changeover of control (Larsson, 2012). This can be demonstrated by (Larsson, 2012) that as time and experience with the ACC increases the driver is more aware of the limitations and the situations in which ACC fails, such as bends and roundabouts, and therefore the awareness in these situations increases.</p>\n<p>There is also another argument that many of these automated systems can be disparate and piecemeal which can hinder education and acceptance of these automated systems (Merat &amp; Lee, 2012).</p>\n<p>Another interesting drawback is if the system is only a warning system than the drivers reaction time may decrease even with the warnings in place (Muhrer et al., 2012). This is particularly interesting as it indicates a seemingly unique issue of under automation. The system does not take over enough from the driver and hence puts the driver at a larger risk.</p>\n<p>On top of these drawbacks there is also the a specific human factor drawback of acceptance of the automation systems. As automation systems are introduced to a vehicle it necessitates an adoption by the driver. Acceptance and trust must be given by the driver to the system. This can prove difficult. Often the driver must learn new and different behaviours which creates a barrier to entry. Often times drivers can be slow to learn these new behaviours, sometimes to the point where drivers may give up and not use them (Larsson, 2012). Conversely when the driver learns the behaviours and limitations of an automation system then the acceptance and trust raises (Larsson, 2012). In this particular study as experience with the system increased so did the acceptance of the system.</p>\n<p>How can we interpret the results of human factors studies and determine how driving performance is affected? So we have automated systems that create a safer and better driving experience within the limitations of the system. But when the limitations are breached there remains a larger risk and loss of driver awareness and reaction time due to the inattention these systems allow (Cottrell &amp; Barton, 2013; Merat &amp; Lee, 2012). We also have a gap of education due to unfamiliarity with the system of the driver and the drivers knowledge of its limitations. However as this education gap is overcome the risk and awareness gap shrink as well (Larsson, 2012). It appears a gap of education is a barrier to entry for the adoption of these vehicle systems and we therefore have a vicious cycle of adoption and awareness of the system limitations. To keep driving performance at a desirable level there seem to be a few strategies in relation to these automated systems. The simplest strategy is to simply not automate the driving of a vehicle at all and keep all the tasks the responsibility of the driver (Muhrer et al., 2012). There are some advantages to this, in that the driver is always in control and always comfortable with the control he/she has of the vehicle. This allows for quicker awareness in emergency situations. Countering this though the more mundane tasks of regular driving can reduce the awareness of the driver to the tasks and subtasks needed for regular operation (Young &amp; Stanton, 2002). This results in higher stress overall but within the confines of an acceptable and sometimes optimal level of stress for the operation of a vehicle (Cottrell &amp; Barton, 2013).</p>\n<p>To the ultimate conclusion of full automation it seems we eliminate this gap of risk and inattention. If the fully automated car is capable and indeed possibly better than a driver at all the tasks needed for operating a vehicle than the elimination of the driver and thus the gap of inattention and risk driving performance should be completely improved. The only drawback to the driverless car is that acceptance is very low as drivers prefer to have the illusion of control (Merat &amp; Lee, 2012). But to counter this drivers enjoy a more integrated approach to rather than piecemeal implementations of various disparate automation systems (Merat &amp; Lee, 2012), therefore if the driverless car has an appealing automated experience than adoption may well remain high despite the removal of driver control.</p>\n<p>There are however some points to make about the research and testing currently available. Many of the studies are done within the confines of a simulated environment (Young &amp; Stanton, 2002; Muhrer et al., 2012; Carsten et al., 2012) which is a limitation of the studies. One which possibly must be put up with due to health and safety concerns of undertaking these tests on the open road. It is possible that participants in these tests behave differently in a simulator as opposed to how they act on the open road. One study was able to research drivers in the real world (Larsson, 2012) via a survey. However the sample size remains small with all the respondents electing to pay extra to have the automated system installed, thereby possibly introducing some confirmation bias into the study results. The final two papers (Cottrell &amp; Barton, 2013; Merat &amp; Lee, 2012) being meta analyses of various papers the conclusions drawn can be trustworthy provided the papers these analyses have drawn from are reliable. So it is clear that more than just these papers must be taken into account for a more rigorous understanding of drivers reaction to automation and how this affects performance on the road.</p>\n<p>The overall takeaway from these studies can be as follows. Under the management of an automated system driving performance improves. Sometimes it even improves significantly. However when the system fails or returns control to the driver there is a period of time where driving performance degrades to a point lower than with manual operation. After this period performance is restored. There seems to an antidote to this problem, with increased education and familiarity this gulf of performance can be overcome. The challenge is to implement more standardised systems which cultivate familiarity and keep the driver educated about these systems and their quirks and limitations.</p>\n<p>In conclusion using automation systems to take over driving tasks has many advantages and car manufacturers seem to be moving in the direction of offering these systems to drivers more and more. However as driving becomes more and more automated there tends to be a moving gap of acceptance and safety as drivers have to learn to operate and understand these systems and limitations. As such driving does improve with automation within the confines of the systems limitations. Outside of these limitations driving performance can degrade for a critical amount of time, but with education and awareness of system limitations this gap may be minimised. As such we tend to trend towards fully automated systems where the system is inherently more capable than the driver and hence driving performance improves with automation, but with a moving gap of driver-system interface.</p>\n<h2>References:</h2>\n<p>Carsten, O., Lai, F. C., Barnard, Y., Jamson, A. H., &amp; Merat, N. (2012). Control task substitution in semiautomated driving does it matter what aspects are automated? Human Factors: The Journal of the Human Factors and Ergonomics Society, 54(5), 747–761.</p>\n<p>Cottrell, N. D. &amp; Barton, B. K. (2013). The role of automation in reducing stress and negative affect while driving. Theoretical Issues in Ergonomics Science, 14 (1), 53– 68.</p>\n<p>Larsson, A. F. (2012). Driver usage and understanding of adaptive cruise control. Applied ergonomics, 43(3), 501–506.</p>\n<p>Merat, N. &amp; Lee, J. D. (2012). Preface to the special section on human factors and automation in vehicles designing highly automated vehicles with the driver in mind. Human Factors: The Journal of the Human Factors and Ergonomics Society, 54(5), 681–686.</p>\n<p>Muhrer, E., Reinprecht, K., &amp; Vollrath, M. (2012). Driving with a partially autonomous forward collision warning system how do drivers react? Human Factors: The Journal of the Human Factors and Ergonomics Society, 54 (5), 698–708.</p>\n<p>Young, M. S. &amp; Stanton, N. A. (2002). Malleable attentional resources theory: a new explanation for the effects of mental underload on performance. Human Factors: The Journal of the Human Factors and Ergonomics Society, 44 (3), 365–375.</p>\n",
      "date_published": "2017-08-18T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/toyplot/",
      "url": "https://lukewiwa.com/blog/toyplot/",
      "title": "Toyplot - A Simple Python Plotting Library",
      "content_html": "<p>My previous attempts at using python for plotting saw me use the massive and widely used library of <a href=\"https://matplotlib.org\">Matplotlib</a>. I managed to produce some workable plots which I exported to jpeg and used on one of my <a href=\"https://medium.com/@lukewiwa/the-2020-code-of-points-what-we-might-expect-28afdab4b095\">medium articles</a>. Although extremely customisable and functional I began the search for a plotting library with a nicer API that would export to more web friendly formats such as html and SVG.</p>\n<!--more-->\n<p>After some searching I stumbled upon <a href=\"http://toyplot.readthedocs.io\">toyplot</a> which I've been quite happy with. I went ahead and updated the plots from my medium article using the library and uploaded the results to where I've mirrored that medium article <a href=\"https://lukewiwa.com/blog/the_2020_code_predictions\">here</a>. The format options are various but I decided to export to an html/css/js format and simply copied and pasted into my static site generator.</p>\n<p>The API is quite straight forward, it boils down to a single <code>Canvas</code> object which you can then add different attributes to. It's a nice way to block together the things you want. It doesn't seem to have hard opinions about what plot you are building to begin with which for me is nice as it generalises my code across different graph types. You tend to always begin the same way. Here's a quick snippet of the heat map plots I produced with it.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pandas </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pd</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> toyplot</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> toyplot.html</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># initialise a pandas dataframe of the code of points</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">with</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> open</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(csv, </span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'r'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">) </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">as</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> f:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    data </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pd.read_csv(f)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># Produce heat map matrix. I chose Rings</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">app_name </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> 'Rings'</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">matrix </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pd.crosstab(</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    data[data.app</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">==</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">app_name].value,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    data.eg,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># Some variables to make the plot pretty</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">domain_min </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> matrix.values.min()</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">domain_max </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> matrix.values.max()</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">tlocator</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">toyplot.locator.Explicit(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">labels</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">list</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(matrix))</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">llocator</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">toyplot.locator.Explicit(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">labels</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">list</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(matrix.index))</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">width </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 500</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">height </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 600</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">label </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> app_name</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">tlabel </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"Element Group\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">llabel </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"Value\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">colormap </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> toyplot.color.brewer.map(</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">    \"Greens\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    reverse</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">True</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    domain_min</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">domain_min,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    domain_max</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">domain_max,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># initialise the Canvas object with variables above</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">app_canvas, app_table </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> toyplot.matrix(</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    (matrix, colormap),</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    tlocator</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">tlocator,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    llocator</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">llocator,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    width</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">width,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    height</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">height,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    label</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">label,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    tlabel</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">tlabel,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">    llabel</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">llabel,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># Clean up the Canvas, give it a name and render in html</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">app_table.body.grid.hlines[[</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">0</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">-</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">1</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">],</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">...</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">] </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"single\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">app_table.body.grid.vlines[</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">...</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,[</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">0</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">,</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">-</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">1</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">]] </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"single\"</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">plot_name </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">{}</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">.html\"</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.format(app_name)</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">toyplot.html.render(</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    app_canvas,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    plot_name,</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span></code></pre>\n<p>The output is a rather nice looking plot based on html, css and javascript. The result is below.</p>\n<p><img src=\"https://lukewiwa.com/graphs/sr.svg\" alt=\"cool graph\"></p>\n<p>The full code is available on <a href=\"https://github.com/lukewiwa/matrix_plots_code_of_points\">Github</a> for anybody interested.</p>\n",
      "date_published": "2017-07-12T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/the_2020_code_predictions/",
      "url": "https://lukewiwa.com/blog/the_2020_code_predictions/",
      "title": "The 2020 Code of Points: What we might expect",
      "content_html": "<p>The Code of Points for the 2020 Olympic cycle is official. The time has come to make some wild speculations about the consequences of these code changes.</p>\n<!--more-->\n<p>I decided to map the distribution of skills across value and element groups using a heatmap type graph. Not extremely complex but enough to get an idea of where skills are concentrated in certain values and element groups. The darker the colour, the more occurrences of that skill. To see more varied and, in my opinion, interesting routines I’m willing to speculate we need two things from these graphs:</p>\n<ol>\n<li>A concentrated distribution at and above values of C, since most routines will be made up by these skills.</li>\n<li>A more uniform distribution across element groups, since this will encourage variety with lots of skills to choose from.</li>\n</ol>\n<p><img src=\"https://lukewiwa.com/graphs/fx.svg\" alt=\"Floor Exercise\"></p>\n<p><img src=\"https://lukewiwa.com/graphs/ph.svg\" alt=\"Pommel Horse\"></p>\n<p><img src=\"https://lukewiwa.com/graphs/sr.svg\" alt=\"Rings\"></p>\n<p><img src=\"https://lukewiwa.com/graphs/vt.svg\" alt=\"Vault\"></p>\n<p><img src=\"https://lukewiwa.com/graphs/pb.svg\" alt=\"Parallel Bars\"></p>\n<p><img src=\"https://lukewiwa.com/graphs/hb.svg\" alt=\"High Bar\"></p>\n<p>My predictions looking at these charts are:</p>\n<ul>\n<li>Floor will be tend towards similarity as there are few skills to choose. Routines will trend towards the same skills and combinations in slightly varying order.</li>\n<li>Pommel and Rings routines may have some difference and flair with a good number of skills available to construct differing combinations. Although in saying that there will be a core set of skills which almost all routines will contain.</li>\n<li>Parallel Bars and Horizontal Bar will be where we see the most variety in routine construction with a large range of skills on offer over different element groups. Gymnasts will be able to play to their strengths more and routines will be more unique.</li>\n</ul>\n",
      "date_published": "2017-06-05T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/bottle_url/",
      "url": "https://lukewiwa.com/blog/bottle_url/",
      "title": "Bottle URL Redirect",
      "summary": "The little documented url method in bottle.py",
      "content_html": "<p>I've been making a little web app for my partner and I. I decided to use the <a href=\"https://bottlepy.org/docs/0.12/\">Bottle</a> framework as opposed to <a href=\"http://flask.pocoo.org/docs/0.12/\">Flask</a> since I've always liked the lightness of Bottle and my little app has no need for scaling.</p>\n<p>Flask and Bottle seem have much the same features but there's one pattern in particular that is done easily in Flask but wasn't obvious in Bottle. In many flask tutorials a simple redirect to a url, usually after an authentication seems easily done.</p>\n<p>In Flask it's basically implemented like this:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> flask </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> route, redirect, url_for</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">@route</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'/'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> root</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">():</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    return</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> 'home'</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">@route</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'/redirected'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> redirected</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">():</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> redirect(url_for(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'root'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">))</span></span></code></pre>\n<p>In Bottle this can be done with the little documented <code>url</code> method, however it works a little bit differently. The route needs to be named using a keyword argument, but then the rest is much the same.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code class=\"language-python\"><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> bottle </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> route, redirect, url</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">@route</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'/'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">, </span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">name</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'home'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> root</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">():</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    return</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> 'home'</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">@route</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'/redirected'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> redirected</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">():</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> redirect(url(</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">'home'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">))</span></span></code></pre>\n",
      "date_published": "2017-05-23T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/machine_readable_code/",
      "url": "https://lukewiwa.com/blog/machine_readable_code/",
      "title": "The Code Of Points Format",
      "content_html": "<p>For my own purposes I wanted to do some analysis of the Code of Points and I had little option but to scrape the important information from the current MAG Code of Points PDF file. I appreciate that the Code is now freely available online however if one wants to do any sort of analysis of the Code the PDF format is one of the more annoying formats to work with. As such I've made my scraping efforts available on <a href=\"https://github.com/lukewiwa/code_of_points_MAG_2020\">github</a> to anyone who would like to download it or make changes.</p>\n<!--more-->\n<p>On that note I truly think that the FIG needs to consider making the Code of Points available in a machine readable format. Nowadays more and more of the community is starting to make efforts moving gymnastics and the Code of Points into the digital age. Having the FIG provide that data in a digital friendly format is a great first step into allowing the community to engage more and create interesting things with the data. To be clear when I say machine readable format that can be done in any number of ways. Whether it's simply a csv file published in tandem with the pdf or a fully fledged public API I don't honestly care, as long as it can be consumed and manipulated quickly.</p>\n<p>In addition to this the way that most people will read the Code nowadays is on a digital device of some kind. I'd personally prefer a format which is responsive to the device you are reading it on. PDF does not lend itself well to this use case.</p>\n<p>A few fun things I've done with the data can be found at my <a href=\"https://lukewiwa.com/projects\">projects</a> page.</p>\n",
      "date_published": "2017-04-08T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/dumping_ground/",
      "url": "https://lukewiwa.com/blog/dumping_ground/",
      "title": "A Dumping Ground",
      "content_html": "<p>I have tried a few different iterations of personal websites and of online publishing in the past. Although I will still publish random thoughts on my favourite social media sites and longer thoughts tend to find their way to <a href=\"https://medium.com/@lukewiwa\">medium</a> this platform might be a nice dumping ground for a few things. Mainly I intend for this to become a landing page for some personal projects and also a better place to leave more personal or technical ramblings.</p>\n",
      "date_published": "2017-03-03T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/gymnastics_teams_suck/",
      "url": "https://lukewiwa.com/blog/gymnastics_teams_suck/",
      "title": "The Case Against Gymnastics Teams",
      "content_html": "<p>There has been plenty of debate about the new team format for the current cycle and, as usual, nobody is happy. There is of course one solution to this problem which has not been talked about, although it appears to perhaps to be the most logical. <strong>Abolish all gymnastics teams</strong>. Get rid of them. Really reform the system. This is a radical solution I’ll admit but let me outline some reasons as to why this option may actually be very appealing.</p>\n<!--more-->\n<ol>\n<li>\n<p>Gymnastics team events are essentially shoehorning an individual event into a team format. Gymnastics is not basketball, volleyball or football, it’s not a sport which requires two teams face each other. The sport can’t even pretend to be a team sport like relay events in swimming or athletics where individual athletes do their individual performance directly after each other. Stop pretending gymnastics is something that it’s not.</p>\n</li>\n<li>\n<p>We should keep in mind team event rules change all the time, even within the same competition (hello team final vs qualification). How are we to take the team competition seriously when the teams that make the team final do so under different rules compared to the ones they follow competing in the final. Team numbers have been diminishing from 7 members to 6 to 5 and now 4 in the next cycle. Some events, such as the European Games, have experimented with 3 member teams.  The logical conclusion is not hard to see in the distance. 1 member teams!</p>\n</li>\n<li>\n<p>We don’t see the best gymnasts make it to the Olympics currently. The current team qualification system is a large reason why this is not the case. We get gymnasts that fit the small teams of countries under the arbitrary rules that competition decides on. Sometimes even the best gymnasts in the country can miss out on selection for their own team due to strange team structures. Which brings me to my next point.</p>\n</li>\n<li>\n<p>Given this massive reform this would open up many spots at the Olympics and other events for those who are competitive and deserve to contest at these major competitions. Now I have no huge ideas on the specifics of how to pick the competitors but the numbers would open up room to explore radical new options rather than the tinkering around the edges we see now.</p>\n</li>\n</ol>\n<p>In essence the gymnastics community has been asking itself the wrong questions and we really need to take a hard look at ourselves and decide what we really want from gymnastics in the long term. Granted it takes two medals away from the Olympic Games events but if we really want to be honest with ourselves as a community we should stop beating around the bush and tackle this issue head on.</p>\n",
      "date_published": "2017-03-03T00:00:00.000Z"
    },
    {
      "id": "https://lukewiwa.com/blog/broken_code_review/",
      "url": "https://lukewiwa.com/blog/broken_code_review/",
      "title": "The “We Say You Do” Model Of Code Review Is Broken",
      "content_html": "<p>With the upcoming review and rehash of the Code of Points for the 2017–2020 cycle one thing is becoming increasingly clear, the FIG and requisite technical committees should do as students are told to do and show their working. The Code has been a living and morphing document since it’s inception. This needn’t change, however the way in which we go about modifications has to become a more data driven process.</p>\n<!--more-->\n<p>I have been attending athletes meetings at every World Championships since 2011 and I can say they it has been important and useful method to communicate ideas and feedback between the athletes and the FIG about many issues. The consultations last year concerning the new code changes, however, were underwhelming and it has taken me a long time to really unpack why that was. To be clear these are criticisms about the process of the Code review rather than criticisms of the final draft.</p>\n<h1>Show Me The Data</h1>\n<p>The biggest change proposed for the upcoming MAG code was the move from 10 to 8 counting skills. There were of course other changes put forward but for the purposes of this discussion I will focus on this issue. This was extensively discussed and <strong>the general consensus was that it would make gymnastics more forgiving on the body, it would possibly improve the overall quality of competitions (especially if gymnasts can discard that one problem skill) and raise the base level of gymnasts.</strong> There was one thing that really bugged me though and so I asked a question:</p>\n<blockquote>\n<p>Is there any evidence for these claims?</p>\n</blockquote>\n<p>The reaction was mostly shrugged shoulders. The FIG and the Men’s Technical Committee (MTC) have a responsibility to provide the best arguments for and against any Code modifications and on this occasion it didn’t feel like this was the case.</p>\n<p>A change from 10 to 8 counting skills is not without precedent, The WAG code has been operating with 8 counting skills this Olympic cycle. This should provide a great opportunity to do some real data analysis on some of these claims. Here are just some examples that I thought about off the top of my head that could help with justifying the arguments for Code changes either way.</p>\n<h2>Injury Frequency</h2>\n<p>I don’t know what data is collected by the FIG but I have heard they have made an effort recently in collating data for injuries during competition. A demonstration of injury frequency before and after the change for the WAG code could be an invaluable statistic. Now many factors need to line up to correctly obtain this data. The National medical teams need to be willing to participate, reporting of injuries needs to be consistent across the board and the FIG need to collate and analyse this data in a meaningful way. However if this is going to be an argument for modifications based on injury rates these kind of statistics are surely an integral part of that argument.</p>\n<h2>Average Execution Deductions</h2>\n<p>To justify whether less counting skills translates to an increase in skill quality we could take execution scores across various competitions and find the average execution deduction per skill before and after the WAG code changes. This eliminates the bias towards 8 counting skills having a higher execution score as would happen if the analysis were to be done per routine.</p>\n<p>A more basic measure would be to calculate the rate of falls per routine or per skill to see whether, on average, 8 counting skills correlates with less falls. These kind of statistics would, at the least, provide some evidence to support arguments concerning the quality of routines after the changes.</p>\n<h2>Average Participation Age</h2>\n<p>Has the average or median participant age increased for major competitions around the World? If longevity is an argument then this may be borne out in the statistics themselves. <em>There are problems, however with this as the minimum age restriction got bumped up to 18 for WAG recently</em> <strong>Update: I have been reminded this was only on the MAG side.</strong> Not an insurmountable problem however for statisticians.</p>\n<h2>Single Data Point Arguments</h2>\n<p>I’ve heard through the grapevine that a justification for keeping with 10 counting skills was put forward because of the unique situation at Glasgow World Championships where Uneven Bars had a four way tie for first place. The argument follows that this would not happen in a 10 counting skill system. Now this argument could easily be backed up with a little data analysis. Have we had a sudden increase in tie situations in WAG competitions since the introduction of 8 counting skills? This would be fairly easy to justify either way by simply checking back on major competitions and comparing the rate of tied scores before and after the change. <strong>One data point does not an argument make</strong>.</p>\n<h1>Conclusions</h1>\n<p>Without any analysis no real conclusions can be drawn. I’m sure there are many ways to manipulate the data. There are also many smarter people out there who could get more out of the data than I ever could and the FIG and MTC should be embracing these possibilities. I for one would be happy to stay with 10 counting skills or move to 8 if there was something, anything, concrete the FIG and MTC could hang their hat on but so far I am left underwhelmed. Who knows, perhaps some analysis has been done and I am ignorant of it, if so feel free to point me towards it. I really hope that I am wrong.</p>\n",
      "date_published": "2016-02-24T00:00:00.000Z"
    }
  ]
}