September 4th, 2020

An Evening With AppEngine And Python3

About a month ago, I received an email with the subject, “Configure alternative cost management method(s) for App Engine projects by July 24, 2021.” After skimming it, I decided to star it in Gmail and move on with life; maybe the COVID-19 global pandemic would be over before then and this task would rise above #534,245,770,103 on my list of things to do. However, a little over a week ago I came across a blog post by a another software engineer, Dear Google Cloud: Your Deprecation Policy Is Killing You. And I realized it described (albeit a bit melodramatically) my situation, so I read the email one evening after everyone else in the house was asleep.

Since the shelter-in-place order went into effect over 5 months ago, I had adopted an exercise schedule where every other night I would go on a 5k run after 10pm so as to minimize the likelihood of encountering other people. I tried running with a mask on, but between that and the warmer temperature during the day, just couldn’t do it for any reasonable amount of time at my desired exertion level. However, given the air quality situation due to the late August California wildfire situation, I haven’t been able to keep to that schedule. So one night while trapped inside, I figured I’d look into configuring the alternative cost management method.

The email listed four mechanisms:

Of the three AppEngine apps I had, two could be disabled manually as one of them is not in use and the other one belonged to a Google Cloud Project that didn’t actually use AppEngine but for some reason had a default AppEngine app. The third one was legitimate, my C O L O R F L O W game.

I created that app over 9 years ago; I can’t remember why, maybe it’s because I was working at Google and AppEngine was getting close to becoming an officially supported product. Regardless, I only had to make two changes to it since. The first was to move from the Master/Slave Datastore to the High Replication Datastore (HRD). IIRC this was one of those required migrations where failing to do so by the deadline would result in the disabling of one’s app. I actually had to go through the migration process for an application at work that I volunteered time to so I remember this going pretty smoothly, though still annoying as it had only been a year since I created the application and I had hoped the promise of “platform as a service” (Paas) would insulate me from things like that.

The second time I had to make change was to move to a new AppEngine SDK and Python 2.7 (as opposed to Python 2.x). This was almost exactly three years ago, and by that time I had my act together enough to have posted my code to GitHub; based on the commits (1, 2) it looked pretty painless, though likely required digging through some documentation.

With disabling not an option, that left specifying the maximum number of instances or creating budget alerts. Alerts? Ain’t nobody got time for that. When I checked the documentation I noticed this in regard to the max_instances parameter:

Important: If you use appcfg from the App Engine SDK for Python 2 to deploy, you cannot use this parameter in your app.yaml. Instead, set the parameter as described in Setting Autoscaling Parameters in the API Explorer, or by using the App Engine Admin API.

That reminded me it’s quite a bit past the sunset date for Python 2, January 1, 2020. It was only a matter of time until I received another email notifying me of another migration date, might as well try to get that done now as well. It turns out, according to the migration guide I hit the trifecta and had work to do in order to:

  1. Address compatibility issues between Python 2 and Python 3.
  2. Deal with the fact there are no bundled App Engine services in the Python 3 runtime.
  3. Use a different web framework to route requests.

The compatibility issues were fairly trivial, a few iteritems() -> items(), wrapping invocations of map() in list() and using float division / and integer division // where appropriate. The lack of bundled App Engine services meant I’d have to migrate to Cloud NDB (and perhaps will have a future date migrating to Datastore Mode Client Libraries). However, first I’d have to migrate from DB to NDB…I should have known once I started pulling on this thread I’d discover it was turtles all the way down.

Fortunately since I wasn’t doing anything fancy, there weren’t many changes to go from DB to NDB and the change from there to Cloud NDB was just to use a different module import (until migrating to a different web framework, more on that later). I hadn’t written tests when creating the solver or wrapping it in a webapp. Having not made any changes in several years and working from a new laptop, I didn’t even have an AppEngine development environment setup, so it was off to read more documentation (I wasn’t willing to deploy my updated code and hope for the best).

It turns out there is a Datastore Emulator which would allow me to test the app locally with full persistence capabilities. After installing a few more gcloud and Java libraries and SDKs, I fired up the app and was reminded that the web framework I was using, webapp2, was not supported in AppEngine + Python3. So I needed to complete step #3 before being able to test things.

My choices were Django and Flask. A number of years ago I was asked to rescue an application that used Django. Given I already had the persistence layer taken care of via NDB and the number and complexity of the endpoints were dirt simple, Django seemed like overkill. I had never used Flask but after a quick scan of the documentation, it seemed like it had a template engine and a way to route requests which were the two main bits of functionality I needed. And sure enough, changing my webapp.RequestHandler classes to functions an decorating them @app.route and changing template.render(template, parameters) to render_template(template, parameters) was just about all I needed to do. all of a sudden I had an app running and raising access PERMISSION_DENIED errors, how exciting.

After some digging, it turned out I hand’t granted Cloud Datastore permissions to the colorflow-hrd@appspot.gserviceaccount.com. Once that was setup, I could play the game in my local environment and do everything except test the post to Facebook functionality (likely a domain/security issue).

The deploy to AppEngine went smoothly and everything (including the post to Facebook functionality) appeared to be working properly. I pushed a single commit to GitHub and called it a night. All in all, it took just under 5 hours. Add that to the couple of hours I spent doing the previous upgrades and it probably averages to under 1 hour per year spent on maintenance and $0 to run. Had I instead built the app using a typical LAMP stack on my webhost, I probably wouldn’t have had to do any maintenance since the PHP/MySQL software gets automatically updated and I don’t think the APIs I would have used would have changed or been deprecated, but one can never be sure.

April 26th, 2020

COVID-19 pyttsx3 Chilblains

Unless one has been living on Antarctica for the last few months, life has probably been affected by COVID-19 in some way, shape or form. It has been almost 7 weeks since the company I work for issued a recommended work from home (WFH) notice and I have not been into the office since. The communications went from voluntary WFH to recommended to strongly recommended to mandatory over a 16 day period.

At the very beginning, it seemed a little odd that I had stopped going into work while my daughter was still going to kindergarten. I tried to rationalize and explain to her that riding public transit and sharing an office space with thousands of other people who were possibly travelling all over the world, when it wasn’t really necessary, was more likely to spread the disease and endanger society as a whole as compare to her spending time with a few hundred kids and some adults at school. In other words, it wasn’t that it was more important for me not to get sick than her, but that if I were sick and didn’t know it, I might be exposing many more people, more quickly, than she would if she were sick. Based on this photo I happened to take 2 weeks earlier on my morning commute, there might be some truth to that. She does go to a public school, but the classrooms are not quite this overcrowded.

February 25, 2020 morning BART commute.

Nevertheless it felt bad and not 100% correct to try and explain this to her; and while we are fortunate enough to be in a position where we could have just taken her out of school without penalty, she wanted to go to school. Also part of the reason we live where we do is the belief that people in charge of the school district, health services, city and county government are knowledgeable and rational people who will “do the right thing”. So it was somewhat of a relief when 3 days after I started WFH the school district announced that Friday would be the last day of school for the next 3 weeks (one of which was spring break).

The first week was pretty rough, she already had a distaste for video chats even before the pandemic; so getting her to participate and deal with the technical difficulties of everyone trying to get setup with distance learning lead to quite a few more meltdowns than normal. We tried to emulate some of her school day to provide a semblance of normalcy e.g. she wrote out her standard Tuesday schedule.

I was probably only reaching about 60-80% of usual productivity on work matters; and that included staying up late to catch up on things while everyone else was asleep. There were plenty of other stressors e.g. attempting to get groceries delivered and then having to clean/sanitize them etc. None of this is unique nor is it comparable to the hardships that many other people are going through but life had certainly changed abruptly in significant (to us) ways.

Week two was better and then things changed for week 3. Since it was technically spring break we didn’t do any of the homeschooling and she could wear footie pajamas all day (we enforced having to get dressed in the morning for “school”). I was still playing catch-up at night with work but at this point I felt like it usually brought me close to 100% normal productivity. What I didn’t realize is that I was slowly injuring myself in a strange way. It wasn’t carpal tunnel or anything like that, though my WFH setup is less than ideal.

The monitor is 13 years old, my in-laws gave it to me several years ago after they got a new computer because they weren’t sure how to properly dispose of it. I had procrastinated taking it to the computer recycling center, so lucky me. The laptop is company owned. As someone who at varying points over the last 25 years has had as many as 5 different physical computers running in their apartment/loft/house at any given time and also ran their own firewall, nat, web, dns, mail, ftp, raid-5 etc. servers, it’s somewhat surprising that the only computer I own (not counting phones or an old chromebook I got from work) turned 8 years old a month ago. To be fair, I purposely configured it on the high end as a desktop replacement when I purchased it, planning for it to last at least 4 years e.g. Intel Quad Core i7 2.2GHz CPU w/6MB L3 cache, 4GB 1333Mhz DDR3 RAM, 16.4in 1080p screen, HDMI, Blu-Ray/DVD+RW, NVidia GeForce 520M w/512MB dedicated video RAM etc. And towards the end of 2018 I did upgrade to a SSD hard drive, but even with that and the end of Moore’s Law, it feels a little ridiculous to be typing this on a laptop from 2012.

I remember during an architecture course in college that focused on 3D animation (as opposed to CAD), someone from Industrial Light and Magic came to speak to us and mentioned he didn’t even own a computer (this would have been 1999). At the time I thought that was completely absurd given my room looked like this.

But now, I’m basically that guy who spoke to us (though I’m also likely 5-10 years older than he was at the time). I don’t have a desk at home, I spend 8-10hrs/day 5 days a week fairly intensely focused on making computers do things and while I’m still interested in continuing to educate myself about technology, I rarely practice it outside of work.

Some of that is due to a belief that we should not be exposing our daughter to too much technology too quickly and therefore want to engage her while playing with a basketball or dolls or Lego or a guitar etc. Though as part of the homeschooling, we asked our daughter to come up with electives, and I figured since she had started to have an interest in reading and doing arithmetic on her own, it might be worth trying to teach her how to code. She was open to the suggestion since she likes to type gibberish in a text editor and has seen me working from time to time asking “what are you coding now?” or “are you fixing a bug?” I had read Seymour Papert’s Mindstorms shortly after our daughter was born and read a little about a programming language geared towards kids called Scratch. I knew parents who’s kids had fun with Scratch and others who were uninterested. And there were those who’s kids liked Python and others who had rejected every language and attempt at interesting them in programming and computation. I had also reflected from time to time on my experience teaching 8-16 year olds HTML/CSSJavaScript and Macromedia Flash at a summer camp (also more than 20 years ago) as well as my own upbringing in BASIC.

In the mid-1980’s we had an Atari 800XL at home and then my middle school had TRS-80 Model III’s (10 years old at the time). I still remember this feeling like magic:

10 PRINT “HELLO JAMES!”
20 GOTO 10

Graphics seemed out of reach at the time, I could spend an hour typing in code from a magazine that I didn’t comprehend just to see an orange and green jack-o-lantern appear on screen. Another one I remember was typing in a bunch of code that rendered a green fractal fern. I learned about variables and conditionals, but not functions (that I remember) and certainly not the association coding/programming had with mathematics. It wasn’t until several years later, I taught myself a little VisualBasic (first project was writing a Celsius/Fahrenheit/Kelvin converter with a GUI) did I really start to see the power of the computer as a tool.

By the time I was teaching at a summer camp, I had more formal education under my belt and it was much easier to make visuals via code. With most kids only going to the camp for a week during the day time, and mixing in all the other camp activities, I probably had 15-20hrs of time with any given group to make an impression. So I decided to spend most of the time teaching them how to programatically draw and animate lines and shapes. There was much document.body vs document.layers in JavaScript juxtaposed with looping keyframes in Flash.

However, that still seemed like a bit too much for a 6 year old. One of the things we’ve been thankful for is that she got to have a birthday party before everything shut down. Some classmates have had to celebrate their birthdays over a video chat or having their neighbors setup a bear hunt. Her age is a big deal too e.g. when playing Monopoly, “oh I rolled a 6 because I’m 6 years old.” So I thought, maybe an age calculator would capture her attention in a similar way to how the temperature converter caught mine. But I also wanted to tap into whatever it was that made me so excited to see my “Hello James” printed repeatedly across the screen. However, given she has grown up swiping and tapping on touchscreens while talking to our Alexa and Google Mini, printing characters to a screen likely wouldn’t create the same sense of astonishment. But what if I could make the computer “speak” to her or better yet, have her instruct the computer what to “speak”?

A program that asked, “What year were you born?” and then “What month were you born?” and then if necessary, “What day were you born?” would be able to correctly state how old someone was (ignoring the detail of time of day since every birthday when I tell her she’s not N+1 years old yet due to her birth time being after her bedtime it just makes her roll her eyes). It would provide an opportunity to introduce concepts like input, output, variables, numbers, strings, conditionals, functions, errors and provide an opportunity for the computer to “speak”.

Without much trouble I could probably write this program in C, C#, C++, Java, JavaScript, Perl or Python. I ended up choosing Python because it seemed like it would be the simplest to teach. I also found the pyttsx3 library which seemed like a dirt simple way to get the computer to “speak”. My plan was to work through a bit of the program with her each day that she wanted to write code as part of her elective activities and then magically flip the “speaking” switch.

The basic premise was to have a series of print and input functions e.g

print("What is your name?")
name = input()
print(f"Hello {name}! What year were you born?")

interspersed with the various calculations. That would exercise her reading and typing and then the magic would be converting the print functions to a custom print_and_say function which would “speak” everything while also printing it to the screen as before. This worked out pretty well, though I am not sure she really grasped any of the concepts I had initially hoped to convey. Mainly she enjoyed having the computer say her name along with poop and pee at various speeds. The full program appears below.

import pyttsx3
import calendar
from datetime import date, datetime

def month_to_number(month):
  for i, m in enumerate(calendar.month_name):
    if m.startswith(month.lower()):
      return i
  raise ValueError(f"Could not convert {month} to a number.")

def print_and_say(message, speech_engine):
  print(message)
  speech_engine.say(message)
  speech_engine.runAndWait()

def main():
  speech_engine = pyttsx3.init()
  speech_engine.setProperty("rate", 165)
  speech_engine.setProperty("voice",
                            speech_engine.getProperty("voices")[1].id)

  print_and_say("helo, wut is your name?", speech_engine)
  name = input()
  print_and_say(f"nise to meet you, {name}! wut yeer wr you born?",
                speech_engine)
  year = int(input())
  now = datetime.now().date()
  years_old = int((now - date(year, 12, 31)).days / 365.2425)
  print_and_say(f"I think you are at least {years_old}. "
                f"what munth wer you born?", speech_engine)
  munth = input()
  month_number = month_to_number(munth)
  print_and_say(f"{munth} is month number {month_number}!", speech_engine)
  day = 1
  if month_number == now.month:
    #srhfhfhfhrgrgtgrhrhhvrvghfghtgrgsurfhgrhgryfhrgyhfgyygdyurgrreyutfyhfb657474656550r968767686urthfhfhfbfgfgfgfgtggtgffhftrgftrytyrrtr65tftbybrrgtvhgfhfgdbjhfhdgfygdrf
    print_and_say(f"Oh, I guess I also need to know what day you were born,"
                  f" can you please tell me?", speech_engine)
    day = int(input())
#hjudhhfhhghghgfghghghgyfhfhjgjgugfufufufugghhyghgyyughbvbhgygvtyg
  elif month_number == 12:
    pass
  else:
    month_number += 1
    
  years_old = int((now - date(year, month_number, day)).days / 365.2425)
  print_and_say(f"Ahh, now I am sure you are {years_old} years old!",
                speech_engine)
  speech_engine.stop()

if __name__ == "__main__":
  main()
#hgbtttytuybvrhutbgrghftvytyttvtytyvttytvtythttvhvbvfhfdjhnvmbkhlotpyorhejfhvbvogjghthghghgghtggghjgjghghgghhjhvtbyvhrbtvhryrghftggytr

The text-to-speech engine was actually pretty good at pronouncing her estimated words e.g. nise vs nice. I hadn’t thought to teach her what a comment was but when she was getting frustrated with having to write certain words, I showed her how she could use the # character to tell the computer to ignore it and I think that turned out to be her favorite part of programming. Upon further reflection, probably not a bad thing as most code would actually benefit from some well placed comments.

Maybe you’re wondering where the chilblains comes into play. During the first 3 weeks of WFH I was often barefoot (fully dressed otherwise) and as mentioned working late. My feet started to hurt as if I had blisters. I tried using bandaids to reduce friction but it didn’t help and it got to the point where I could not go jogging and even walking the dog was a struggle. After much reading I self-diagnosed chilblains and started wearing socks and shoes inside the house. I also applied petroleum jelly to the sensitive parts of the skin and within a couple of days there was a significant improvement, after a week I was able to go jogging again with only mild discomfort and a few days after that all the pain was gone.

Finally, about a week after that my wife came across an article about covid toes; so who knows, maybe I was infected and otherwise asymptomatic. Or maybe I just had one of the silliest self-inflicted WFH injuries.

May 19th, 2019

Bike to Work Day

When I was 11 years old, I biked for work; delivering ~20 papers along a ~1 mile route with a fairly steep hill. Alas, none of my jobs post-college have really been bike-able, given nearly all of them have been in San Francisco; and there isn’t a bike lane all the way across the Bay Bridge (yet).

The one exception was a period of time in 2004-2005 when the main office of CafePress was in San Leandro (they moved to San Mateo ~10 months after I started). Some days I would bike 1.7 miles to the West Oakland BART station and then 2.3 miles to the office from the San Leandro BART station, but that doesn’t really count as “biking to work”. It turns out I could have biked the whole way if I’d been willing to ride on some not-so-safe streets and/or known about the dirt/gravel trails. I partially blame the lack of Google Maps cycling directions at the time (in fact Google Maps had only recently launched and it was an accessory to getting me in trouble with HR, but that’s a story for another time). Plotting a route from where I used to live to the former CafePress office indicates it’s a little over 12 miles and an hour each way; so not necessarily something I would have done often, but certainly doable.

The last significant distance I’d traveled on a bike in one shot was almost 3.5 years ago when I biked 7 miles to the (then) terminus of the bike path on the eastern span of the Bay Bridge.

The old eastern span was still in the process of being disassembled.
Two “bridges to nowhere” side by side…
The view of Treasure Island from the previous terminus of the bike path.
An artsy shot of the new eastern tower.

An officemate who goes on 20+ mile bike rides for fun on the weekends mentioned Bike to Work Day was coming up, on a day she was schedule to work in Mountain View. She had signed up for a group ride and I should do it too. I looked at the map, it would be a little over 53 miles door-to-door, one way and I’d have to leave my house around 4:30a to meet the group at Lake Merritt in Oakland at 5a. Along the entire route, there was very little elevation gain other than the climb over the Dumbarton Bridge. I had been exercising regularly, my bike was probably in reasonable shape. I was also planning on working from Mountain View that day and it turned out another co-worker would be doing a ride from San Francisco. I’d thought about doing this in the past, a former co-worker and his girlfriend were currently on a 18,000 mile bike ride from Alaska to Argentina, what excuse did I have not to go?

Well, maybe the fact that the last time I biked more than 7 miles in one go was nearly 18 years ago in 2001? I had planned on doing the California AIDS Ride, 575 miles from San Francisco to Los Angeles, but the startup I was working at was falling apart, and with it went my matching sponsor. I documented a few of my training rides, but did not end up participating.

29 mile Briones Reservoir Loop (1300+ foot elevation gain)
35 miles on Hwy 1, north of Bodega Bay (~2000 foot elevation gain)
59 miles on and around Mt. Diablo (2700+ foot elevation gain)

So yeah, one time in my life I had biked 53+ miles in one go, I had no time to “train” for this ride, I’m significantly older and likely in worse shape; hmm, that’s more than one reason not to do this. Nevertheless, I bought a couple of spare bike inner-tubes and a portable pump, tuned up my bike as well as I knew how, rode it around a couple of blocks the night before and set my alarm for 3:45a.

The morning of the ride I had a big bowl of cereal for breakfast, packed my bike supplies, 2 liters of water, some granola bars, a change of clothes and my laptop into a backpack, threw on my reflective vest, helmet and bike lights and at 4:25a I was off. At 4:40a I realized I had forgotten my cell phone, but if I turned around to go back and get it I would not be able to meet up with the group (in hindsight this was likely wrong as people were late and if I sent a message as soon as I got my phone, that I too was going to be late, they’d have waited). Instead, I was the second one to arrive at the meeting point at 4:50a and already had my first facepalm moment to share with others (so yes this means no more pictures in this post, no Strava route times etc. you’ll just have to trust me).

My office mate had decided not to go after getting sick over the previous weekend and a couple of other people also cancelled, but we still had a good sized group of 9. Some people seemed fairly serious about cycling i.e. padded cycling shorts, clip in shoes, hydraulic or disk brakes, hydraulic shifters, aluminum frames etc. Others, like me, had flat pedals and sneakers, steel frames, non-cycling clothes etc. I even had part of a child seat support attached to my frame because it’s way too much trouble to take that part off (I of course didn’t have the seat itself attached). The group leader mentioned he planned on staying on dirt/gravel trails as much as possible to keep us closer to the bay and away from the cars, that sounded good to me. He followed that up with, “I usually do this route in about 2h 45min, but happy to go faster or slower”. Yikes, I had presumed this would be a 4hr+ ride given the amount of gravel/dirt trail that was planned. I think this is roughly the route we took: https://goo.gl/maps/GH8bQcxt16QyiAB76. It’s not the shortest, but it stays as close to the bay and out of traffic as possible.

We left at 5:10a and had our first flat tire within 20 minutes, hadn’t even made it to Alameda yet. I hadn’t invested in a water bottle cage so the timing worked out well to have a drink and a snack. We probably made it another 60 minutes before we had a second flat and at almost the same time a rider fell (minor cuts on the hands). At that point we had made it past the Oakland Airport and the person leading the ride said he had to go on without us since he needed to be showered and changed for a client meeting at 9a.

We proceed without incident to the toll plaza of the San Mateo Bridge. The ride along the bay had been quite nice, the skies were overcast which meant no view of the sunrise, but also comfortably cool weather. We met a woman on a mountain bike (enjoys spending weekends in the Santa Cruz mountains, Joaquin Miller Park etc.) She had started the day in Castro Valley and joined our group as she was on her way towards an office on Hwy 84 just before the Dumbarton Bridge; and that was how we were going to cross the bay. We suffered our final flat tire along the way, and after she left our group, made our way to the bridge.

By this point I’d biked over 35 miles, and even though it had been mostly flat, much of it was pretty bumpy and my body was starting to feel a little sore. Perfect timing for the first significant headwinds and incline. It was a great feeling to be moving faster than all the traffic on the northern side of the bridge (heading east/west) but it totally sucked to have the bike path on the southern side with oncoming traffic speeding along at 50mph+ (west/east). Every time a truck or bus rocketed by it was a face full of exhaust and a force trying to push me back down the bridge.

Once down, the rest of the ride went smoothly, no flat tires, no falls, and we rolled into work just after 9:30a. While the soreness in my arms and legs was gone by the next day, it wasn’t until a few days later that I could sit comfortably on my bike again.