Hello World, Again

I’ve spent the last few years treating my blog posts like the children of a divorced couple who live on opposite ends of the world. I move them all every few months from platform to platform looking for a better home. From Blogger to WordPress. From WordPress to Jekyll. From Jekyll to WordPress. From WordPress to a custom site I built for no great reason. From the custom site I built for no great reason back to WordPress. Back to Jekyll.

Sometimes the writing gets better with age, like a stinky cheese. Other times I need to cut off the mold when it gets too crusty. This time I am moving back to WordPress and will try to stick with it.

I am also going to try to focus more on writing about what I have to say to the world and less on attempting to create a bespoke random supplemental software documentation repository. I think it would be better to contribute to the official documentation for a project when I run across a problem that is confusing or needs further explanation. For anyone who read this blog for those random nuggets, I am sorry.

2020 has caused all of us to look at things from a different perspective. Here’s to fresh starts.


Pretty Print Relative Dates in Python

In this post, we will learn how to “pretty print” relative dates using the dateutil library in Python. Specifically, given a relativedelta object, we will print a string that represents the number of years, months, and days that have passed between two dates.


I am working on a project called ManagerManager that helps me keep track of the people I work for. As my team has grown, it has become important to me to have a quick reminder to celebrate milestones such as work anniversaries.

PostgreSQL age Function

PostgreSQL has a useful built-in age function that allows you to get a human readable relative date from a date or datetime column.

For example, If I was in a database somewhere, I could use the age() function to find out how old I am.

select name, birthday, age(birthday) from person;
Lev1988-06-0931 years 11 mons 6 days

Abstracting Away the SQL

In the first iteration of this project, I relied on “raw” SQL for all database operations. This made it easy to take advantage of the age() function in PostgreSQL. As the project became more complex, working in SQL got out of hand.

I recently switched to SQLAlchemy to make working with the database simpler. Although SQLAlchemy allows you to call out to native database functions, I didn’t want to have a hard dependency on PostgreSQL for this application, so I started to search for a quick and dirty way to do the same thing in Python.

Exploring Python datetime

My initial investigation led me to the familiar datetime module in Python. This built-in module is great for manipulating date and time objects. It includes a timedelta class which allows you to compare two datetimes at up to microseconds of resolution. This is generally useful, but unfortunately the maximum interval that this class provides is days.

import datetime

dob ='1988-06-09')
today =
today - dob

>>> datetime.timedelta(days=11662)

It turns out converting days to years, months, and weeks is much more complicated than I initially thought since you need to account for leap years and other nuances.

The dateutil Library

Luckily, the dateutil library provides several enhancements to the built-in datetime module. Most importantly for this use case, it has the relativedelta class which breaks the timedelta into years, months, days, all the way down to microseconds.

import datetime
from dateutil.relativedelta import relativedelta

relativedelta(today, dob)
dob ='1988-06-09')
today =

>>> relativedelta(years=+31, months=+11, days=+6)

The only thing missing from dateutil.relativedelta is a nice way to “pretty print” the result. For example, what if years, months, or days is equal to 0? I don’t want a string that says “0 years, 4 months, and 1 day”.

Ternary Operators to the Rescue

This was the first time that I’ve used a ternary operator in python and it actually feels like a good use case here.

def tenure(self):
    rd = relativedelta(, self.start_date)
    years = f'{rd.years} years, ' if rd.years > 0 else ''
    months = f'{rd.months} months, ' if rd.months > 0 else ''
    days = f'{rd.days} days' if rd.days > 0 else ''
    return f'{years}{months}{days}'

A ternary operator is a shortcut to write an if/else statement. In my opinion, it is useful for simple situations like this. Let’s break this down a little bit.

In the tenure() function described above, the code for determining years is equivalent to the following:

if years > 0:
    years = f'{rd.years} years, '
    years = ''

Or, you could also write it like this:

years = ''

if years > 0:
    years = f'{rd.years} years, '

However, I think the simple one-liner ternary operator makes the entire function a bit cleaner.

Going back to the birthday example, the final output of this function unsurprisingly looks like this:

31 years, 11 months, 6 days


To sum things up, we can use the dateutil library coupled with your choice of if/else syntax to provide a human readable way to show the difference between two dates.


Docker Compose for Local WordPress Development

Using Docker Compose for Local WordPress development is an excellent way to get up and running with WordPress development as quickly as possible.

Benefits of Using Docker Compose for WordPress Development

Traditionally, developing WordPress locally means that you must have PHP, MySQL, and Apache (or some other web server) installed on your local workstation. The complexity of installing and configuring these tools varies by the operating system. Furthermore, you may quickly find yourself in configuration hell attempting to configure new releases of WordPress, PHP, MySQL, or Apache.

Luckily, Docker and Docker Compose remove all of the guesswork from installing, configuring, and upgrading these tools. For example, upgrading to a new version of MySQL with Docker Compose is as simple as changing a single value in a single configuration file.

image: mysql:5.7

image: mysql:8.0

Getting Started with Docker Compose

You can start using Docker Compose for local WordPress development with four steps.

Install Docker Locally

First, make sure that you have Docker installed on your workstation. You can verify that everything is working properly by opening up a terminal and running a few commands.

Note for Windows Users

By default docker and docker-compose are not in the Windows PATH variable. For these commands to work, be sure to add C:\Program Files\Docker\Docker\resources\bin to the PATH.

Executing docker version should produce output that looks like this:

Windows Terminal
PS C:\Users\Lev Lazinskiy> docker version
Client: Docker Engine - Community
Version: 19.03.8
API version: 1.40
Go version: go1.12.17
Git commit: afacb8b
Built: Wed Mar 11 01:23:10 2020
OS/Arch: windows/amd64
Experimental: false

Server: Docker Engine - Community
Version: 19.03.8
API version: 1.40 (minimum version 1.12)
Go version: go1.12.17
Git commit: afacb8b
Built: Wed Mar 11 01:29:16 2020
OS/Arch: linux/amd64
Experimental: false
Version: v1.2.13
GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
Version: 0.18.0
GitCommit: fec3683

Executing docker-compose version should produce output that looks like this:

Windows Terminal
PS C:\Users\Lev Lazinskiy> docker-compose version
docker-compose version 1.25.4, build 8d51620a
docker-py version: 4.1.0
CPython version: 3.7.4
OpenSSL version: OpenSSL 1.1.1c 28 May 2019

Create a Project Directory Structure

Next, create an appropriate project directory structure. We’re going to mount our local project directory to a running instance of a WordPress docker container. To keep things simple, create a new directory called wordpress with the following directory structure.

PS C:\Users\Lev Lazinskiy\git\wordpress> tree
Folder PATH listing

    │   └───my_new_plugin

As illustrated above, if you’re making a new plugin, the code for your plugin would go into the wordpress\plugins\my_new_plugin directory. Likewise, if you’re making a new theme, then the code for your theme would go into the wordpress\themes\my_new_theme directory.

Create a Docker Compose for Local WordPress Configuration File

The last step is to create a Docker Compose configuration file. In the wordpress directory from the previous step, create a new file called docker-compose.yml. This file should have the following contents:

version: "3"


    image: wordpress:latest
    restart: always
        WORDPRESS_DB_PASSWORD: wordpress
        - "8080:80"
      - "./wp-content:/var/www/html/wp-content"
        - mysql

    image: mysql:5.7
    restart: always
        MYSQL_ROOT_PASSWORD: wordpress
        MYSQL_DATABASE: wordpress
        - "13306:3306"
        - "./mysql-data:/var/lib/mysql"

A couple of things worth highlighting in this configuration are the ports and volumes directives on the mysql container. We’re exposing port 13306 so you can use a MySQL client to connect to the running docker instance. Since we’re mounting a local volume called mysql-data any changes you make will persist across reboots.

Lastly, since we’re mounting wp-content in the wordpress container, any changes that you make to your new theme or plugin will be immediately available in your local WordPress instance.

Note for Windows Users

In order for volume mounting to work correctly on Windows, you must enable File Sharing in the Docker settings.

Open the Docker app, navigate to Resources and then File Sharing. Make sure that your primary drive is selected. Select Apply & Restart.

Screenshot of Docker Desktop for Windows configuration settings for sharing volumes.

Run Docker Compose

You should now be able to run docker-compose up and navigate to http://localhost:8080 to see your new WordPress site running locally.

Screenshot of WordPress installation wizard.

If you see the page shown above, complete the installation as you would any other WordPress site. When you log in, you will see your new theme and plugin available to install.

The first time you run Docker Compose it will take some time to download the WordPress and MySQL base images. Subsequent restarts will be quicker since Docker Compose will use a locally cached image.


In summary, using Docker Compose for local WordPress development is a quick and easy way to hack on WordPress themes and plugins. This approach has the added bonus of not needing to deal with the complexity of installing PHP, MySQL, or Apache on your local machine.


I’ve Been Reading Books Wrong

I read a lot. I try to balance out a healthy mix of fiction and nonfiction. I have an unhealthy habit of not being able to give up on a book once I have started reading it. This had led me to some brutal weeks-long slogs through dull historic fan fiction. There are hundreds of books on my shelf at home that I’ve not read yet. I am also a glutton for punishment because recently I’ve been exploring the Personal MBA reading list, which is adding another 99 books to my pile of books that I want to read. The good news is that there is a hack to reading that I didn’t know about until today. 

I saw the Personal MBA at a local bookstore recently and initially I thought that it was yet another modern snake oil book which over promises and under delivers. However, when I got home I did a bit more research and I am really motivated by the work that Josh Kaufman has done over the last decade with this project. 

As a part of reading through his manifesto, I discovered a hidden gem that teaches you how you are actually supposed to read non-fiction books.  


Apparently I’ve been doing it wrong all this time. According to Paul Edwards, the purpose of a reading any non-fiction work is to discover, understand, and remember what the author has to say. There are some great tips in that paper. My biggest takeaways were that you should read actively and with a strategy, go over the work several times with specific goals, and review the information that you’ve learned using multiple modes of thinking. 

Reading Strategy 

Edwards recommends having a strategy for every piece of nonfiction work that you read. You should be trying to answer these questions as soon as you can. 

  1. Who is the author?
  2. What are the books arguments?
  3. What evidence supports these arguments?
  4. What are the conclusions?

This creates another handy acronym: ACE (arguments, conclusions, evidence) which we can use to guide the next parts of our reading strategy. ACE helps us with the discovery and understanding process. 

In addition to finding out the ACE of the article, you should also begin to start thinking about these things: 

  1. Are there any weaknesses in the authors ACE?
  2. What do you think about the ACE?
  3. How does the author, if at all, respond to these weaknesses?

Read Three Times 

This is a bit counterintuitive because the purpose of this article is to help you save time in reading nonfiction. After thinking about it, it makes more sense to me. 

  1. During the first reading (more like skimming) your goal is to get a sense of the whole piece of work, and start to generate questions for ACE. 
  2. During the second reading your goal is to start getting answers to the questions that were raised. 
  3. During the third reading (which is also the part that helps you remember), you should make notes about ACE in your own words and using your own mental model.

Review and Apply 

This was one of my favorite tips. It’s not enough to just read something. You should write about it, speak about it, listen to other people speaking about it, and visualize it. If you are able to hit this grand slam on a specific topic then you will start to develop some real expertise in a given subject. 

After reading all of these tips, I feel much more prepared to start tackling the 99 nonfiction books on this reading list, along with the hundred or so more that I have on my bookshelf at home. 


Sitepoint is filled with Bullshit

If you haven’t seen Brad Frost’s masterpiece commentary on the state of the internet, check that out before you continue reading.

I was fiddling around with a WordPress plugin recently and searched for some tips on how to add customized Meta Boxes to my plugin. Thanks to SEO the first 10 pages of Google are filled with sites that provided recycled bits and pieces from other blogs and the official WordPress documentation.

One of the biggest culprits in this type of “content” is a Sitepoint. It starts off pretty innocuously. When you first come to the site it seems pretty clean, useful, and distraction free. However, as you start to scroll through the article ads begin to pop up to the left, and then the right, and at some point (if you’re lucky) they just take over the entire screen.

Bullshit from sitepoint
Bullshit from Sitepoint

I only noticed this because I recently moved to a new Google Account and none of my plugins were synced up in Chrome. Including, of course, an ad blocker that makes the web slightly more tolerable. I’ve not browsed the web without an ad blocker for a long time. I am surprised at how bad things seem to have gotten.

In my mind, this is how the algorithm that shows ads at these types of sites work.

  1. User comes to a website to read an article. No ads are shown. The ad AI sends out the warning sirens throughout the network — “We’ve got a live one, no ad blocker! All hands on deck.”
  2. User is surprised not to see any ads. The ad AI begins its strategy — “Don’t scare them away, wait until they scroll down.”
  3. User scrolls down. The ad AI sends out a signal — “Let’s pop one out on the left, slowly, slowly, there.”
  4. User keeps scrolling. The ad AI is pleased — “Didn’t bounce, OK lets try an attack from the right this time. Go slow.”
  5. User becomes sad, but keeps reading. The ad AI is ready for the final attack.
Mortal Kombat – User vs Ads

The entire screen is blanketed by ads, banners come up on the top, bottom, left, right, every single click just spawns more and more ads. User gives up, signs up for Square cash. AI retreats to its corner, waiting for the next poor soul to travel around the web without an ad blocker.


How to Use Apple AirPods in Debian

If you try to connect Apple AirPods to a computer running Debian, you will be able to pair them without any issues, but unfortunately you will not be able to use them to play any sounds. When I tried to do this, after pairing the AirPods, clicking on the Sound Settings menu option in the Bluetooth settings only showed the default output as an option.

In order to use AirPods to play sound you need to tweak the Bluetooth configuration file a bit. I poked around in that file a bit but I have no idea what the difference between “le” and “br/edr” was until I read this other stack overflow post which explained it succinctly.

For whatever reason, it looks like AirPods are only able to operate in BR/EDR mode. To get them to work with your Debian computer you should do the following:

  1. Edit /etc/bluetooth/main.conf and update the ControllerMode to equal bredr, by default it is set to dual mode.
  2. Restart the Bluetooth service with sudo systemctl restart bluetooth.
  3. You should now see your AirPods as an output option in the Sound Settings (screenshot shown below).
Sound Settings in GNOME

I am not 100% sure what the impact of disabling dual mode will be, but for now all of my other Bluetooth devices along with the AirPods work without any issues.


After a couple days of using Airpods with my Debian laptop, I’ve noticed that every once in a while they stop showing up in the sound settings menu. This bug is a bit annoying, but if you restart the bluetooth service with sudo systemctl restart bluetooth.service then they will begin to work again. In addition, I still have not figured out how to get the airpods to be an input source.


How to Disable Wayland in Debian Testing

I’m running Debian Testing on a laptop, and I would like to disable Wayland because not all of the tools that I use currently have first-class support for it. There is an old post that I used to reference to get this to work, but it appears that gdm3 changed the naming conventions for the configuration file in a newer version.

There used to be a file called /etc/gdm3/custom.conf which appears to have been renamed to /etc/gdm3/daemon.conf in newer versions of Debian. Luckily the contents of this file are more or less the same.

# GDM configuration storage
# See /usr/share/gdm/gdm.schemas for a list of available options.

# Uncomment the line below to force the login screen to use Xorg

# Enabling automatic login
# AutomaticLoginEnable = true
# AutomaticLogin = user1

# Enabling timed login
# TimedLoginEnable = true
# TimedLogin = user1
# TimedLoginDelay = 10




# Uncomment the line below to turn on debugging
# More verbose logs
# Additionally lets the X server dump core if it crashes

In order to disable Wayland and switch back to X11, you should uncomment #WaylandEnable=flalse in this configuration file and restart your computer to apply the changes.


How to Leave a LinkedIn Group in 2019

LinkedIn Groups might have been interesting in the past, but it seems like with each iteration LinkedIn makes the functionality less and less useful.

In order to clean up some of my activity in LinkedIn I wanted to leave some of the groups that I was a part of.

I searched on how to do this, and a lot of the results showed a LinkedIn UI from several years ago. It was not immediately clear to me how to do this.

I figured it out eventually and am documenting the process step by step with screenshots below.

Navigate to the Groups Page to see all of the groups that you are a part of.
Select the group that you want to leave and then click on the “” link to see a list of menu options. Select Leave this group
Confirm that you want to leave the group by selecting Leave.

56 Books, My Literary Journey in 2018

According to goodreads, I read 56 books in 2018. This was 4 over my goal of 52 books! I spent a lot of the year reading books from my never ending list of tralev books but I also took some time to appreciate the classics, award winners, and a random selection of history and business books from Prime reading.

I think my absolute favorite book of the year was The Dark Forest (#2 of the Remembrance of Earth’s Past Book series). I read the entire series this year and gave each book 5 stars. Im a huge sci-fi fan and this is the best series I’ve ever read hands down.

I’m pretty conservative with my good reads ratings.

1 – why did I ever read this

2 – “ok”, if you’re a fan of this topic

3 – good book

4 – great book

5 – read this before you die

A couple other books that I really enjoyed last year were:

  • Giant of Enterprise – a book about business tycoons that was recommended to me by Nathan.
  • Kitchen Confidential – a book by the late Anthony Bourdain which gives us a peek into the real world of a career as a chef.
  • Less – a pulitzer prize winning book about life, love, and loss.
  • East of Eden – a masterpiece by one of Americas most legendary authors.

I hate being negative, but my least favorite book by far last year year was “That Dark and Bloody River” I wrote a scathing review of this one on my other blog. It was also the longest book I read last year. I have a terrible problem of not being able to stop reading a book halfway in between. I read this tome with anger over a series of several long haul flights.

I want to read 52 more books in 2019. Join me in the challenge. Happy reading!

data science

Converting CSV to a SQLite Database

As a part of my data science course on EdX we have been working with a lot of csv files. I spoke SQL long before I spoke Pandas and I find that it is much easier to do initial exploration of the data using raw SQL queries compared to the Pandas DSL.

Kaggle is a great repository full of useful data sets that are ripe for exploration. While a lot of these data sets come in both csv and sql flavors, some of them are CSV only. Using SQLit we are able to easily import these csv files into a database and then run queries for further data exploration. 

Im going to use the kickstarter data set for this tutorial, feel free to download the csv files from kaggle so that you can follow along. 

Pre Requisites

Make sure that you have SQLite installed before getting started with this tutorial. 

Steps to Convert CSV to SQLite

First, Download the data set from kaggle, this will come in the form of a zip file. Unzip this and open up a terminal in the directory where you have the new unzipped kickstarter-projects folder. 

In your terminal open up a new sqlite session followed by the name of the file that you want to save your new database to. 

sqlite3 ks.db

Inside of the sqlite shell, change the mode to csv. 

.mode csv

Import the csv file, and add the name of the table that you want the data to be imported into. 

.import kickstarter-projects/ks-projects-201801.csv ks

Verify that everything was imported correctly. Take a look at the schema, and first couple of rows. Your output should look something like this: 

sqlite> .schema ks

"name" TEXT,
"category" TEXT,
"main_category" TEXT,
"currency" TEXT,
"deadline" TEXT,
"goal" TEXT,
"launched" TEXT,
"pledged" TEXT,
"state" TEXT,
"backers" TEXT,
"country" TEXT,
"usd pledged" TEXT,
"usd_pledged_real" TEXT,
"usd_goal_real" TEXT

sqlite> select * from ks limit 5;

1000002330|The Songs of Adelaide & Abullah|Poetry|Publishing|GBP|2015-10-09|1000.00|2015-08-11 12:12:28|0.00|failed|0|GB|0.00|0.00|1533.95
1000003930|Greeting From Earth: ZGAC Arts Capsule For ET|Narrative Film|Film & Video|USD|2017-11-01|30000.00|2017-09-02 04:43:57|2421.00|failed|15|US|100.00|2421.00|30000.00
1000004038|Where is Hank?|Narrative Film|Film & Video|USD|2013-02-26|45000.00|2013-01-12 00:20:50|220.00|failed|3|US|220.00|220.00|45000.00
1000007540|ToshiCapital Rekordz Needs Help to Complete Album|Music|Music|USD|2012-04-16|5000.00|2012-03-17 03:24:11|1.00|failed|1|US|1.00|1.00|5000.00
1000011046|Community Film Project: The Art of Neighborhood Filmmaking|Film & Video|Film & Video|USD|2015-08-29|19500.00|2015-07-04 08:35:03|1283.00|canceled|14|US|1283.00|1283.00|19500.00

Excellent! Now you can query this entire data set as your normally would. Happy data exploration!