Last Few Days in Programming: Lots of Python


Been quite busy with Python, keeping me away from other pursuits, like video games. (Although the kids have been requesting Spelunky 2 whenever it’s time to hang out with them)

Extra Life Donation Tracker (eldonationtracker)

For my Extra Life Donation Tracker I pushed out a new release, v5.2.2. A user of my program (man, I never get tired of how awesome that is!!) had wholly anonymous donors which was causing an issue I thought I’d handled. But it turns out that the folks that run the Donor Drive API are a little inconsistent in how they handle that in the donor endpoint vs the donations endpoint. So I pushed that fix out and now things should be dandy for game day (about 2 weeks away!!)

Automating some Boring Stuff

In these COVID-19 times I have a problem – the YMCA where I’m a member has instituted signups for swimming. But you have to sign up EXACTLY 48 hours before you want to swim. Since I’m swimming every other day, that means that sign up time is when I’m swimming. For a while I would just wait until after my swim to sign up. But it’s a VERY popular time. So I started taking my phone to the pool to sign up. There are many negatives to this:

  1. It takes ~5 minutes or so with my phone and LTE connection (out of a 45 minute session which is already shorter than I’d normally spend in there)
  2. It uses data
  3. I risk dropping my phone into the pool or into a pool of water around the pool
  4. It means my phone is right there in my gym bag where someone could steal it (although that would give me a great excuse to buy a new one…)

So, while I was swimming today (best source of thoughts other than the shower), I realized I could probably use Selenium to automate this. I’ve never used it before, but I’d heard a lot about it. I knew that Al Sweigart talked about it in his book, Automate the Boring Stuff with Python. I bought a copy of the first edition, but I wanted to make sure I was up to date on the latest stuff so I went to that link I just shared where he has it available to read for free. He’s using the model Corey Doctorow used to use where it’s there for free, but you can also buy it and help him and the publisher. Also, he has a class on Udemy that covers the same topics. Anyway, I spent all morning (literally) digging around in my browser’s inspector mode to get all the data I needed to use it to automate the sign up. I believe I’ve got it working (I’d already signed up for my next swim session, so I had to pretend to sign up for another time, but you can only sign up for one time per day). I set up a cron job and what I’m going to do is let it sign me up and I’ll double-check (safety valve in case it doesn’t work). I’m not ready to share this code at this point – mostly because I’d prefer if it could keep working. However, it was a great experience in debugging and in how web scraping is just as annoying now as when I first learned about it somewhere around 15 years ago with O’Reilly books with titles like “Google Hacking” and “Flickr Hacking”.

raspigaragealert

As I mentioned in Switching up the hardware for the Garage IOT, I recently moved my Raspberry Pi-powered garage alert software from a Raspberry Pi 1B to a Raspberry Pi Zero W. The Raspberry Pi 1B is now in the office providing temperature and humidity data – quantifying just HOW HOT it is in here. This led me to have a renewed interest in the program. So I went ahead and created another config file in order to make it more generally usable to folks who aren’t me. Then I also created documentation. The documentation still needs a bit more work, but it could help others. Also, since it’s Hacktoberfest, someone made a PR for my code!! If this isn’t the first PR someone’s made against my code in a project in which they were co-authors, it’s at least one of the first. So that’s exciting!

Python Morsels

Finally, for this time period there was the most recent Python Morsels exercise. I fell a little behind with some other projects (and Spelunky 2) so my most recent assignment was to “create a ProxyDict class which offers an immutable dictionary-like interface that wraps around a given dictionary.” The first bonus was to add support for len, items, values, and get. The second bonus has to implement iteration and a nice repr string. The final bonus had to support equality.

At first I was a bit lost. I tried a naive solution where I just passed the keys of the dictionary I received in the __init__ method, but I got stuck on figuring out __getitem__. So then I thought I needed to use abstract base classes. I’d seen them in some book I read in the past few months. But I couldn’t remember what they were called. So I clicked on Trey’s first hint, which showed that I was right and reminded me of the term “abstract base class”. This was not a “gimme” for there was not a Dictionary in collections.abc. So after looking at the table in https://docs.python.org/3.8/library/collections.abc.html#module-collections.abc for a while, I thought Collection would give me a lot of what I wanted. But it was still missing a bit, so I looked at Mapping, which was probably the best thing to use because it was immutable and inherited from Collection. Unlike other problems in Python Morsels, this is a very, very esoteric part of Python, but it was interesting to learn how to implement an ABC; particularly the fact that it will let you know which dunder methods you’re missing when you try to create a class. Turns out that by doing this, I got bonuses 1 and 3 (and part of 2) for free! This was my code:

from collections.abc import Mapping


class ProxyDict(Mapping):

    def __init__(self, dictionary):
        self.proxy_dictionary = dictionary
        super().__init__()

    def __getitem__(self, item):
        return self.proxy_dictionary[item]

    def __iter__(self):
        return (key for key in self.proxy_dictionary.keys())

    def __len__(self):
        return len(self.proxy_dictionary)

As for the __repr__ method – I’m a pro at those at this point. I kept thinking there must be some way to cheat and use the one from the dictionary I was proxying, but I didn’t know how. So this was my code:

from collections.abc import Mapping


class ProxyDict(Mapping):

    def __init__(self, dictionary):
        self.proxy_dictionary = dictionary
        super().__init__()

    def __getitem__(self, item):
        return self.proxy_dictionary[item]

    def __iter__(self):
        return (key for key in self.proxy_dictionary.keys())

    def __len__(self):
        return len(self.proxy_dictionary)

    def __repr__(self):
        center_list = []
        for key, value in self.proxy_dictionary.items():
            if isinstance(key, int):
                center_list.append(f"{key}: '{value}'")
            else:
                center_list.append(f"'{key}': '{value}'")
        center = (', '.join(center_list))
        return "ProxyDict({"+center+"})"

I don’t think I have the prettiest syntax for my repr method. I was trying to be elegant and use a list comprehension. That looked like:

center_list = [f"{key}: '{value}'" for key, value in self.proxy_dictionary.items()]
center = (', '.join(center_list))

But without using a lamba or something, I couldn’t figure out how to implement the if/else logic in the list comprehension.

What I learned from Trey’s Solution

First of all, when I said this was an esoteric thing, I wasn’t kidding. There’s actually already a way to do this without any work:

from types import MappingProxyType as ProxyDict

Thanks mostly to Trey’s problem sets I knew that I wanted to use yield or that I probably wanted to do a generator. So I thought my solution was pretty good. But it turns out there are two simpler ways I could have done it. Since I’m proxying a dict, which already has an iter method, I could have done:

def __iter__(self):
   yield from self.proxy_dictionary

or I could have done:

    def __iter__(self):
        return iter(self.proxy_dictionary)

I actually think the first one is more readable. For the repr I kept thinking there must be some easier way to do this. Because the dictionary already has a repr. But I thought that would result in something like ProxyDict(dict(stuff)); apparently not. Because this is Trey’s solution:

def __repr__(self):
        return f"ProxyDict({repr(self.proxy_dictionary)})"

Although, that locks in the class name and causes issues if someone wants to do the same thing with our class. So the better way is:

def __repr__(self):
        return f"{type(self).__name__}({self.proxy_dictionary!r})"

the !r is the same as repr(self.proxy_dictionary).

Well, time to go check out that Pull Request on raspigaragealert!