DIY Bitcoin Hardware Wallet with a Raspberry Pi Zero

I would like to make a Bitcoin Hardware Wallet (HWW) without trusting any single HWW vendor (such as Ledger), and it should also be able to easily handle multisig setups. Ideally, it should run Electrum so that I can be reasonably assured that any wallets managed on this device will be recoverable and exportable in the future.


Why the Pi Zero?

There are cost advantages to be sure, but the Pi Zero is a bare-bones computer without any networking interfaces. No wireless, no bluetooth, no Ethernet. Unless you install a network interface, the device is already airgapped. The onboard chips are so simple it also minimizes the risk of hardware vulnerabilities you'd get in a laptop or desktop.

It's also simple to setup!


You're going to need more than a Pi Zero for an Electrum-based HWW. In addition to the Pi Zero board itself (not the Pi Zero W), you'll need the following:

Camera to read QR Codes

Because the device is airgapped, you'll want a simple camera to read QR codes via Electrum. This will allow you to read unsigned transactions for signing, as well as loading public keys for multisig wallet creation. You can use an official Raspberry Pi camera. But really, a simple webcam would also work (if USB-based webcam, then obviously you'll need an adapter to connect it to one of the pi-zero micro-USB ports)

The advantage of a cheap Raspberry Pi camera is that it minimizes the risk hardware vulnerabilities that you might find with full-sized webcams. That said, the simplicity of the Pi camera and the slow processing of the pi-zero makes reading QR codes a bit slow.

Dedicated Display

Because this is an airgapped device, you will need a display. The display must be sufficient for Electrum, and in particular, for showing QR codes that can easily be read by other devices (other HWWs or smartphones).

Any portable monitor will work. The pi-zero has a mini-HDMI port, so you may need an adapter.

Input Device

I recommend a single wired mouse. Most wireless mouse + keyboard setups use an unencrypted wireless frequency and anyone with a cheap receiver in your neighborhood could be listening in on your keystrokes. I recommend a virtual keyboard (more on that later) and a single wired mouse (if USB you'll need a micro-USB adapter for the pi-zero).

Micro-SD for One-Time Software Load

This is the tricky part. The pi-zero will boot from a micro-SD card. So you'll need a micro-SD card. And because the pi-zero has no network, you won't be able to load any software onto the micro-SD card from the pi-zero itself.

Fortunately, there are MANY ways to do this. You could copy the needed files on a USB thumbdrive, but this introduces layer of vulnerability that you can avoid.

I strongly recommend a on-time software load from a separate device (a Raspberry Pi that does have Internet access), and if you ever need new software, you start the process over on a new micro-SD (and destroy the old one). See below for instructions.

If you don't already have a spare Raspberry Pi with a network interface, you'll need one (or modify this approach to use a USB drive if you're okay with those risks). You can use a Raspberry Pi Zero W, or even a Pi4; you'll only need to use it once for the initial setup (see below).

Initial Setup

You should have a Raspberry Pi Zero with a camera, display, and a wired mouse. You should also have a spare Raspberry Pi that has network access. And you should have one micro-SD card ready.

You will need a computer to burn the OS image onto the micro-SD card. I use an Ubuntu machine with Pi Imager:

Make sure to select the 32-Bit Raspberry Pi OS. Insert the micro-SD card and write the image.

One-Time Network Setup

Once you have written the OS to the micro-SD card, put the micro-SD card into the spare Raspberry Pi that does have a network adapter and a keyboard. You'll want to connect to the Internet and install the following software:

Virtual Keyboard

You can consider this step optional if you plan on using a normal keyboard with the airgapped Pi Zero. However, I would recommend avoiding wireless keyboards due to the security concerns.

sudo apt install matchbox-keyboard

Once installed, the virtual keyboard will be in the Accessories menu, as shown,


I recommend to install Electrum from the source and verify the package. At the time of this writing, this will look like,

sudo apt-get install python3-pyqt5 libsecp256k1-0 python3-cryptography

gpg --import ThomasV.asc

gpg --verify Electrum-4.0.9.tar.gz.asc

You should get the following output,

gpg: assuming signed data in 'Electrum-4.0.9.tar.gz'                                                                                              
gpg: Signature made Fri 18 Dec 2020 12:07:21 PM MST                                                                                               
gpg:                using RSA key 6694D8DE7BE8EE5631BED9502BD5824B7F9470E6                                                                        
gpg: Good signature from "Thomas Voegtlin ( <>" [unknown]                                                
gpg:                 aka "ThomasV <>" [unknown]                                                                                    
gpg:                 aka "Thomas Voegtlin <>" [unknown]                                                                            

Assuming things look good you can untar the package and rename it however you want,

tar -xvf Electrum-4.0.9.tar.gz
mv Electrum-4.0.9 Electrum

Shut down the Raspberry Pi and remove the micro-SD. This will be the very last time this micro-SD card should see the Internet.

Airgap the Pi Zero

Insert the micro-SD card into the Pi Zero, and physically tape over it with a tamper proof seal (such that it would be obvious if it was ever removed).

Depending on your level of paranoia, you'll want to make sure that no data can get off your pi-zero except via QR code or pen and paper. This means that the moment you generate private keys, you'll want to take precautions to never expose the device to a network or a USB thumb-drive.

I recommend attaching everything together (mouse, display, and the pi-zero) such that it looks like a Frankenstein computer. Use duct-tape if you have to. If you used a USB adapter that includes other USB ports, physically cover those with tamper proof seals. There are cases you can get for a pi-zero, I have no recommendations other than to make sure that you seal this thing such that no one can connect to it from any other device. Wrap it a faraday bag if you're truly paranoid.

Using your DIY HWW

To start Electrum on the airgapped Pi Zero, simply use the "run_electrum" command from the console,

cd Electrum

From here you'll be using Electrum in offline mode, which will be useful for doing the following,

Create Wallets

You can create as many wallets as you like, and import public keys for multisig wallets through QR codes, as well as export public keys for managing receive addresses on a separate device.

For every wallet you do create, you'll want to create a separate backup of the seed words. I recommend to use a metal backup (or a sturdy material that can withstand a house fire and still be legible).

Do not ever export the private keys or the wallet file. If you need to replace your micro-SD, simply create a new one and recover your wallets with the backup seeds. I would recommend to physically destroy the old micro-SD card. The whole point of this airgapped device is that the wallet is never exposed.

Sign Transactions

You can create transactions, and for multisig you'll need to import an unsigned transaction into your airgapped Pi Zero HWW. To do so, open the wallet in question, and then under Tools, select Load Transaction, and then select From QR code. You can scan the transaction with your Pi Zero camera, and then click Sign.

You can export a signed transaction by displaying the QR code. Scan this onto any device with Internet access and broadcast the transaction.

Verify Receive Addresses

For security, it is recommended that you verify your receive addresses (especially in a multisig setup). To do so, simply open the wallet in question and select the Addresses tab. If you don't see the addresses tab click View and then Show Addresses.

Make sure the addresses on this list match any corresponding watch-only wallets. If it is part of a multisig wallet, make sure all wallets in the group have the same receive addresses.

Posted in bitcoin


Typically, you would use mysql client cli as follows,

$ mysql -h -P 3306 -u bob -D somedb -p

You could also create a ~/.my.cnf file to set default properties, including connection info,

port = 3306
host =
database = somedb
user = bob

You could even store a password but I wouldn't recommend storing it in the clear. Also, the above approach is not useful when you have more than one MySQL database across multiple hosts, ports, etc.

Managing Multiple Connections

Unfortunately, mysql still doesn't have an easy way to do this, but if you add the following to your ~/.bashrc,

function mysqle() { mysql --defaults-group-suffix=$1 --password "${@:2}"; }

You can then setup your ~/.my.cnf as follows,

port = 3306

host =
database = somedb
user = bob

host =
port = 63306
database = superdb
user = bob

As long as you prefix the section with "client", then this will allow you to create as many different sections as you need (for different apps, different environments, whatever).

To connect, just use the shell function from earlier,

$ mysqle superdb
Posted in bash, mysql, shell tips

FP Error Handling

I would like to handle errors in pure FP (functional programming), something more idiomatic and easier to read than nested try/catch blocks.

Let's say we have the following procedure,

def sumCirclesDiv(rs: List[String], d: Int = 1): Int = {
    val areas = => {math.Pi * r.toDouble * r.toDouble})
    val divs = / d)

This is pretty straightforward. Given a list of numbers (represented as strings), compute the areas of these as circles, and then divide by a common divisor (default to 1) and return an Integer sum. But what happens if one of the input strings cannot be parsed? What happens if the divisor is 0?

We could use try/catch exception handling, and end up with something like this,

def safeSumCirclesDiv(rs: List[String], d: Int = 1): Int = {
    try {
      val divs =
    } catch {
      case x: ArithmeticException => {
        println("you cannot divide by " + d)
      case x: NumberFormatException => {
        println("you must provide parseable numbers")
      case unknown: Throwable => {
        println("Exception: " + unknown)

This works, but presents numerous problems. For starters we return Int 0 in the case of an exception. But what if we want to ignore invalid strings and return a value for the valid input strings? We could do something like this,

def saferSumCirclesDiv(rs: List[String], d: Int = 1): Int = {
    var acc = 0
    for (r <- rs) {
      try {
        val area = math.Pi * r.toDouble * r.toDouble
        acc += area.toInt / d
      } catch {
        case x: ArithmeticException => {
          println("you cannot divide by " + d)
        case x: NumberFormatException => {
          println("you must provide parseable numbers")
        case unknown: Throwable => {
          println("Exception: " + unknown)

This will work for lists that contain a mix of valid and invalid string inputs, but it's not exactly idiomatic, the function is not pure, and it's an unwarranted assumption that invalid inputs should result in a zero. A result of zero should not be indicative of input errors.


In Scala (as well as in Functional Java libraries) we can use the Option monad. This is built-in to Scala, although as we'll see, it is fairly easy to implement directly.

For starters, our function signature should not lie (that is, if we can't guarantee an Int, we shouldn't promise an Int). We should instead return an Option of an Int, e.g.,

def optionSumCirclesDiv(rs: List[String], d: Int = 1): Option[Int] = ...

In this monadic approach, we should get the following results,

scala> optionSumCirclesDiv(List("1.5", "4.2"))
res0: Option[Int] = Some(62)

scala> optionSumCirclesDiv(List("1.5", "4.2"), 0)
res1: Option[Int] = None

scala> optionSumCirclesDiv(List())
res2: Option[Int] = None

scala> optionSumCirclesDiv(List("0.0"))
res3: Option[Int] = Some(0)

scala> optionSumCirclesDiv(List("1.0", "foobar"))
res4: Option[Int] = Some(3)

When using Option in Scala, we have either Some or None, and importantly, Some(0) is not the same as None. Let's implement this in pieces, and do so in a way that keeps our functions pure. For starters, we have an input List of strings that could contain invalid inputs (like "foobar" in the above example). We'll want to filter out all entries that cannot be cast to a number. You can do this explicitly with a List.filter expression, but we can also do so by using Option.

def optionDouble(s: String): Option[Double] =
  try { Some(s.toDouble) } catch { case _: Throwable => None }

With this function, we can map over a list and then flatten it (removing the Some and None wrappers), which is the same as using a flatMap(),

scala> List("1.1", "foo", "4.2").map(optionDouble)
res0: List[Option[Double]] = List(Some(1.1), None, Some(4.2))

res1: List[Double] = List(1.1, 4.2)

scala> List("1.1", "foo", "4.2").flatMap(optionDouble)
res2: List[Double] = List(1.1, 4.2)

We can now filter out all invalid inputs, and be guaranteed a List with zero or more valid inputs. Using this same approach, we can define other functions that use Some and None,

def circled(r: Double) = Some(math.Pi * r * r)

def divN(y: Int)(x: Double): Option[Int] =
  try { Some(x.toInt / y) } catch { case _: Throwable => None }

The circled function is obvious, but why create a divN? In this case, we can curry divN (within a flatMap) as follows,

scala> List(2.0, 4.0, 6.0).flatMap(divN(2))

scala> List(2.0, 4.0, 6.0).flatMap(divN(3))
res1: List[Int] = List(0, 1, 2)

If we chain together these functions in flatMaps, we'll have exactly what we need to implement optionSumCirclesDiv, i.e.,

  rs.flatMap(optionDouble).flatMap(circled).flatMap(divN(d)) match {
    case Nil => None
    case x => Some(x.sum)

And because we have a chain of flatMaps, we can use a for-comprehension, e.g.,


// is equivalent to..

for {
      s  <- rs
      r  <- optionDouble(s)
      a  <- circled(r)
      ad <- divN(d)(a)
    } yield ad

Either / Try

The only potential problem with using the Option monad is that we're swallowing our errors. Maybe we want to know what specific exception occurred in our input list. A useful (albeit ugly) approach is to use the Either data type. Rather than Some or None, Either can be Left or Right. By convention, Left is used for errors and Right is used for success, e.g.,

def eitherDouble(s: String) = 
  try { Right(s.toDouble) } 
  catch { case _: Throwable => Left("Cannot parse " + s) }
def circled(r: Double) = Right(math.Pi * r * r)
def divN(y: Int)(x: Double) =
  try { Right(x.toInt / y) } 
  catch { case _: Throwable => Left("Cannot divide by " + y.toString) }

The return type for these functions is Either[String, Int], and we can use them as follows,

def eitherSumCirclesDiv(rs: List[String], d: Int = 1): Either[String, Int] = {
    val list =
    list.collect{ case Left(x) => x }.map(println) //side effect!
    list.collect{ case Right(x) => x } match {
      case Nil => Left("No parseable numbers to compute")
      case x => Right(x.sum)

A more idiomatic approach to this problem is to use the Try monad. This combines traditional exceptions with a data structure that is similar to Either, but instead of Left/Right we use the more intuitive Failure/Success, e.g.,

import scala.util.{Try,Success,Failure}
def tryDivArea(d: Int = 1)(s: String) =
    val r = s.toDouble        
    val a = math.Pi * r * r
    a.toInt / d

By simply returning a Try() structure, we'll get either Success() or a Failure() object that wraps the corresponding exception (e.g., NumberFormatException). We can use this function as follows,

def trySumCirclesDiv(rs: List[String], d: Int = 1): Try[Int] = {    
    val all =
    all.filter(_.isFailure).map(println) //side effect!
    all.filter(_.isSuccess).map(_.get) match {
      case Nil => Failure(new Exception("Empty List"))
      case x => Try(x.sum)

In the above cases we're simply printing the exception messages (a side effect to what is otherwise a pure function), and in practice we would not want to do that; keeping our functions pure will allow for proper exception logging as well as parallel streams.

All of the above code can be found on my github, and should run correctly in Scastie.

Posted in scala, software eng.

How to Find a Job as a Software Engineer

Software Engineering as changed a lot since the 1990s: we've seen the rise of the Internet, smartphones, machine learning, and a golden age of programming languages. Yet despite the technological advances, the actual process of finding a well-paying job as a software engineer hasn’t changed much.

The buzzwords have changed, the amount of money has skyrocketed, the volume of people and recruiters has definitely changed. But the specific frustrations of job seekers and hiring managers has remained unchanged. We spend the same amount of time, if not more, finding a job as we ever did.

Even with all the advanced machine learning -- algorithms promising to find the right candidates for the right job -- the actual job hunt is as clumsy as it ever was. As a job seeker, you’ll fill out the same information (address, veteran status, etc) over-and-over and wade through various recruiters and headhunters. Meanwhile, hiring managers are still combing through resumes desperate to find competent applicants.

In the end, third-party recruiters and third-party tools almost always get in the way and slow you down. None of them, not the recruiters, not the job sites, not the advanced job-matching algorithms; none of them are actually working for you; they exist only to profit from the company that is trying to hire you. Third-party recruiters are not interested in building an algorithm that actually finds you a good job. You as the job seeker are the product that third-party recruiters are actively selling to companies. No one is trying to find you a good job, except you.

Now, to be fair, there are some decent recruiters out there, but most third-party recruiters will spend their time calling you when you’re NOT looking for a job.

So, what is the best strategy for finding a job? Well, it turns out it’s pretty simple. It's a strategy I would recommend to everyone. And if some of this seems obvious and old-fashioned, that’s because it is.

Ask, Seek, Knock

Before you apply for anything, ask about the companies that you'd consider working for. Do they pay competitive market salaries? Are they working on important problems? Do people like working there?

Vet companies (on glassdoor or similar sites). Don't start applying right away, and don't talk to recruiters. Always start with vetting! Do not apply to any company that you haven’t properly vetted. It should be a great company to work for, a company working on important problems. Ask yourself: would working at this company be good for you, for your family, and your community?

For each vetted company, go to their jobs website, and/or find the hiring manager. Find all of the open jobs that you qualify for. Be honest about this. In some cases, the company may have no jobs that you qualify for. This is why doing your research up front is so useful; you’ll save yourself and all the hiring managers a lot of time by doing this kind of research.

If instead you went with recruiters and were using the latest job matching algorithm, be prepared to waste a lot of time talking to people about jobs that you are not a good match for (based on the easy to research questions listed above).

Finally, apply to each of the jobs that you are qualified for, and apply directly on the company website or directly to a hiring manager. And unless they specifically tell you to only apply once, apply to each of the job-postings that you are interested in. Most companies (especially large companies) have separate in-house recruiters working with different hiring managers and they may not go out of their way to find if you are a match for a job in a different department. It’s not uncommon to get rejected on some positions while getting calls from other positions, all at the same company (and in some cases it might be from the same in-house recruiter. They deal with lots of applicants, don’t take it personally).

It's timeless advice: Ask (the right vetting questions), Seek (the right companies), and Knock! You'll save yourself countless hours. Although a point of caution:

Beware of Scams

Sometimes you'll see a posting with an awesome salary, a great job description, but no company, just a recruiting agency. This is likely a bait and switch. Don’t fall for it. Remember, research the company first, before applying. Do not apply at a company that you haven’t vetted. If the recruiter doesn’t want to give you details, it means they’re obviously not the exclusive recruiter for that job and you’ve very likely already seen the real job posting elsewhere. Remember, the only person that’s trying to find the best job for you, is you!

And then What?

Unless your resume is an absolute mess, you'll likely start receiving calls from prospective companies. From here, you'll be asked to do online coding tests, phone screens, and possibly an in-person interview. Much has been written (arguably too much) about how to pass technical interviews -- the best advice I've learned: Interviews go both ways. While they are testing you (to determine if you'd be a good match), you should be testing them. Are they a good match for you and your life goals? Do they like working at this company? What would they change at the company? Be as up-front and honest as possible about what you're looking for and don't just try to impress them. Save yourself the pains of future stress by asking behavioral interview questions back at the interviewers.

Posted in software eng.

Extremely Large Numeric Bases with Unicode









This would allow shortened URLs to look like this:



= 7M85y0N8lZa
= 瑙稃瑰蜣丯
= 셁뻾뮋럣깐


= 3CG2Fy1
= 執洪仉
= 닁읛껅



def encode_urange(n, ustart, uend):
    chars = []
    while n > 0:
        n, d = divmod(n, uend-ustart)
    return ''.join(chars)

def decode_urange(nstr, ustart, uend):
    base = uend-ustart
    basem = 1
    n = 0
    for c in unicode(nstr):
        if uend > ord(c) < ustart:
        basem = basem*base
    return n




# base-10   922,111
base62:     LSR3
top16:      eewwt
CJK:        鶱丫
hangul:     쏉걒
braille:    ⣚⡴⡙
anglosaxon: ᛡᛇᛞᚻᚢ
greek:      οΒΦδ
yijing:     ䷫䷔䷫䷃
Posted in python

PseudoForm, a Bot-resistant Web Form




Honeypot Form


<div id="__honey__">







document.getElementById('__honey__').innerHTML = actual_form;




<div id="__honey__">


PseudoForm + formkey


this.getformkey = function() {
    function(err, data) {
      if (err !== null) {
        console.log('Something went wrong: ' + err);
      } else {
        if ("formkey" in data) {
          _self.formkey = data.formkey;


setInterval( PseudoForm.getformkey, 60*1000 );


    ''' return a time-sensitive hash that can be 
        calculated at a later date to determine if it matches
        the hash will change every divsec seconds (on the clock)
    if ts is None:
        ts = int(time.time())
    return hashlib.sha1((

    ''' return True IFF
        e.g., divsec=300, 
        e.g., lookback=6, 
    for i in range(lookback):
            return True
    return False


PseudoForm + handshake





PseudoForm example


The full process of the x404 PseudoForm is as follows,



3. user enters information and clicks "add"


4. wait a second...



5. success! user sees the results
Posted in css, html, javascript, python

multiple git remotes



$ git clone
$ cd dotfiles
$ git remote -v
origin (fetch)
origin (push)

Adding additional remotes is trivial, i.e.,

$ git remote add bob
$ git remote add ann
$ git remote add pat


$ git remote -v
origin (fetch)
origin (push)
bob (fetch)
bob (push)
ann (fetch)
ann (push)
pat (fetch)
pat (push)


$ git pull ann master


$ git push bob master

We can also rename and remove remotes, i.e.,

$ git remote rename bob bobbie
$ git remote remove bobbie


$ git checkout -b bob-master
$ git remote add bob
$ git fetch bob master


$ git checkout bob-master
$ git pull
$ git checkout master
$ git diff bob-master
Posted in git, software eng.

OOP or Procedural?


tl;dr: neither, go with functional programming


#include <stdio.h>

float f_to_c(float f) {
    return (f - 32) * 5 / 9;

int main() {
    float fahrenheit;
    scanf("%f", &fahrenheit);
    return 0;


import java.util.*;

interface TemperatureConverter {
    public float convert();

class Temperature {
    float degrees;
    Temperature(float t) {
        degrees = t;

class Fahrenheit extends Temperature implements TemperatureConverter {

    Fahrenheit(float t) {

    public float convert() {


class FahrenheitToCelsius {

    public static void main(String[] args) {
        Fahrenheit fahrenheit;
        Scanner in = new Scanner(;
        System.out.print("Enter temperature in Fahrenheit: ");
        fahrenheit = new Fahrenheit( in.nextFloat() );

        System.out.println("temperature in Celsius = " 
            + fahrenheit.convert());





class Celsius extends Temperature implements TemperatureConverter {

    Celsius(float t) {

    public float convert() {
        return 9*degrees/5 + 32;






#include <stdio.h>
#include <stdbool.h>

bool is_c_room_temperature(float c) {

float f_to_c(float f) {
    return (f - 32) * 5 / 9;

bool is_f_room_temperature(float f) {
    return is_c_room_temperature(f_to_c(f));

int main() {
    float fahrenheit;
    scanf("%f", &fahrenheit);
    if (is_f_room_temperature(fahrenheit)) {
    return 0;


For example, adding a Kelvin conversion would look like this,

float c_to_k(float c) {
    return c + 273.15;


float f_to_k(float f) {
    return c_to_k(f_to_c(f));









Posted in c, java, software eng.

Trie or Set



def _make_trie(wordict):
    trie = {}
    for word in wordict:
        current_trie = trie
        for letter in word:
            current_trie = current_trie.setdefault(letter, {})
        current_trie['$'] = '$'
    return trie

def _in_trie(trie, word):
    ''' True IFF prefix or word in trie
    current_trie = trie
    for letter in word:
        if letter in current_trie:
            current_trie = current_trie[letter]
            return False
    return True



almost_words = set([])
for word in wordict:
        almost_words.add( word[0:i+1] )




def _setscan_rtl(grid, wordict):
    ''' generator yielding word candidates
    almost_words = set([])
    maxlen = 0
    for word in wordict:
        if len(word) > maxlen:
            maxlen = len(word)
            almost_words.add( word[0:i+1] )
    for line in grid:
            candidate_word = ''
                candidate_word += line[i+c]
                if candidate_word not in almost_words:
                yield candidate_word


$ /usr/bin/time ./
0inputs+0outputs (0major+145801minor)pagefaults 0swaps

$ /usr/bin/time ./
0inputs+0outputs (0major+184571minor)pagefaults 0swaps

Full results and code are available on my github.

Posted in data arch., python



def iter_words(f):
    for line in f:
        for word in line.split():
            yield word

And then one can simply,

for word in iter_words(sys.stdin):
    # do something


from collections import Counter
c = Counter

for word in iter_words(sys.stdin):



def iter_words(sfile):
    chlist = []
    for ch in iter(lambda:, ''):
        if str.isspace(ch):
            if len(chlist) > 0:
                yield ''.join(chlist)
            chlist = []


def iter_words(sfile, buffer=1024):
    lastchunk = ''
    for chunk in iter(lambda:, ''):
        words = chunk.split()
        lastchunk = words[-1]
        for word in words[:-1]:
            yield word
        newchunk = []
        for ch in
            if str.isspace(ch):
                yield lastchunk + ''.join(newchunk)
Posted in python




>>> import urllib
>>> urllib.parse.quote('/叼')







>>> p.netloc.encode('idna')
>>> urllib.parse.quote(p.path)


>>> a = urllib.parse.urlparse('')
>>> urllib.parse.unquote(a.path)
Posted in python

Using getattr in Python


enqueue 1
enqueue 12
enqueue 5
enqueue 9

Afterwards, we should see:



class qlist(list):
    def enqueue(self, v):

    def dequeue(self):
        return self.pop()

    def print(self):


if __name__ == '__main__':
    thelist = qlist()
    while line in sys.stdin:
        cmd = line.split()
Posted in shell tips

Graph Search


         3: set([1, 6]),
         N: set([...] }



    ''' DFS search of graph, return all paths between
        from_node and to_node
    if path is None:
        path = [from_node]
    if to_node == from_node:
        return [path]
    paths = []
    return paths



def find_shortest_path(from_node, to_node, graph):
    ''' BFS search of graph, return shortest path between
        from_node and to_node
    queue = [(from_node, [from_node])]
    while queue:
        (qnode, path) = queue.pop(0) #deque
            if next_node == to_node:
                return path + [next_node]
                queue.append((next_node, path + [next_node]))




    queue = [(from_node, [from_node])]
    while queue and depth_limit > 0:
        depth_limit -= 1
        (qnode, path) = queue.pop(0) #deque
            if next_node == to_node:
                return path + [next_node]
                queue.append((next_node, path + [next_node]))
Posted in python, software eng.

python unittest




In other words, I end up with the following:

 - test/


#!/usr/bin/env python
import os, sys, unittest

## parent directory
import ModuleName

class test_ModuleName(unittest.TestCase):

    def setUp(self):
        self.moduledb = ModuleName.DB()

    def tearDown(self):

    def test_whatever(self):
        self.assertEqual( len(, 16 )

if __name__ == '__main__':

With this approach, the tests can be run by, or I can run the individual

The script also must add the parent directory on the path, i.e.,

#!/usr/bin/env python
import sys, os
import unittest

## set the path to include parent directory

## run all tests
loader = unittest.TestLoader()
testSuite =".")
text_runner = unittest.TextTestRunner().run(testSuite)
Posted in python

HTML + CSS + JavaScript Lessons




You can find the lessons here

It's also available on Github

Posted in css, html, javascript