Michael Uloth
TIL - Today I Learned
- SELECT DISTINCT outputs a column's unique values
Here’s how to see all the different values in a column:
unique-column-values.sqlSELECT DISTINCT column1, column2, ...columnN FROM table_name;
I found that helpful today when I was trying to understand the significance of a value by comparing it to the other options.
- It's tricky to statically type a "pipe" function in Python
I wanted a type-safe
pipe
utility to help me write Python in a more functional style, but unfortunatelytoolz.pipe
andreturns.pipelines.flow
both outputAny
.Happily, it turns out creating your own pipe mechanism is a one-liner:
script.pyfrom functools import reduce result = reduce(lambda acc, f: f(acc), (fn1, fn2, fn3), value)
Which you can reuse by wrapping it in a function:
fp.pyfrom functools import reduce from typing import Callable, TypeVar _A = TypeVar("A") _B = TypeVar("B") def pipe(value: _A, *functions: Callable[[_A], _A]) -> _A: """Pass a value through a series of functions that expect one argument of the same type.""" return reduce(lambda acc, f: f(acc), functions, value)
And calling it with any number of functions:
assert pipe("1", int, float, str) == "1.0" # => i.e. str(float(int('1'))) # => i.e. int("1") -> float(1) -> str(1.0) -> "1.0"
So you can stop thinking up names for throwaway variables like these:
def str_to_float_str(value: string): as_integer = int(value) as_float = float(as_integer) as_string = str(as_float) return as_string assert str_to_float_str("1") == "1.0"
Credit to Statically-Typed Functional Lists in Python with Mypy by James Earl Douglas and the
returns.pipeline.flow
source code for the inspiration.Update on Nov 29, 2024
While the
pipe
function above with theCallable[[A], A]
type hint works fine if every function in the pipeline outputs the same type (A
), the example I showed above doesn’t actually work out very well! Mypy notices that some of the functions (int
andfloat
) output a different type than we started with (str
), so we aren’t actually passingA
all the way through.After trying a number of workarounds (and getting some good advice on Reddit), I learned that you can either tell Mypy what’s going on by painstakingly articulating every possible overload:
_A = TypeVar("A") _B = TypeVar("B") _C = TypeVar("C") _D = TypeVar("D") _E = TypeVar("E") @overload def pipe(value: _A) -> _A: ... @overload def pipe(value: _A, f1: Callable[[_A], _B]) -> _B: ... @overload def pipe( value: _A, f1: Callable[[_A], _B], f2: Callable[[_B], _C] ) -> _C: ... @overload def pipe( value: _A, f1: Callable[[_A], _B], f2: Callable[[_B], _C], f3: Callable[[_C], _D] ) -> _D: ... @overload def pipe( value: _A, f1: Callable[[_A], _B], f2: Callable[[_B], _C], f3: Callable[[_C], _D], f4: Callable[[_D], _E], ) -> _E: ... def pipe(value: Any, *functions: Callable[[Any], Any]) -> Any: return reduce(lambda acc, f: f(acc), functions, value)
Or, you can just use expression, which already does this for you.
I’m going to do the latter, but this was a fun exercise in the meantime. 😎
- An iOS Shortcut can add data to a Google Sheet
I followed this short guide by Thiago Alves for sending arbitrary data to Google Sheets from iOS via an iOS Shortcut and a Google Form and it worked like a charm.
In brief, here are the steps:
- Create a Google Form
- Link it to a Google Sheet
- Copy the pre-filled link to your form and edit it to end with
&submit=Submit
and sayformResponse
instead ofviewform
- Use a “Text” shortcut action to insert your data into that link
- Use a “Get contents of URL” action to send your data to your form results sheet
A response link for a form with one question will look something like this:
https://docs.google.com/forms/d/e/<form-id>/formResponse?usp=pp_url&entry.<question-id>=<answer>&submit=Submit
Here’s what my shortcut to save the current URL looks like:
- How to query a BigQuery table from Python
- Install the SDK:
pyproject.tomldependencies = [ "google-cloud-bigquery[bqstorage, pandas]", ]
- Create a BigQuery client:
client.pyfrom google.cloud import bigquery bq_client = bigquery.Client(project="project")
- Execute your query and store the result:
query.pyquery = """ SELECT column_one, column_two FROM bq_table WHERE column_three = true """ df = bq_client.query(query).to_dataframe()
- CloudFlare sells domain names at cost
I thought of a domain I wanted to grab.
I looked it up on my usual domain registrar (Hover):
$20.99
I looked it up on CloudFlare:
$12.99
Sold.
(The free security features don’t hurt either.)
- Homebrew packages might install dependencies
I wondered why
brew list
showed so many packages I didn’t install myself.Then, I discovered those are the dependencies of the packages I installed. And the dependencies of those dependencies. And so on.
Seems kinda obvious now.
# Which packages and casks are installed? brew list
# Why is <package-name> installed? Which packages are using it? brew uses <package-name> --installed
# Which dependencies came with <package-name>? brew deps <package-name> --tree
- Shell functions don't need parentheses
Copilot surprised me by generating a
zsh
function that looked like this:function act { # do stuff }
Instead of like this:
act() { # do stuff }
It turns out function parentheses are optional in bash and zsh. Though, using parentheses is more POSIX compliant if that’s relevant for your use case.
In fact, all of these variations are equivalent:
function act () { command } function act() { command } function act { command } act () { command } act() { command } act () command act() command
- You can run shell scripts in tmux.conf
- Create a shell script:
battery.sh#!/usr/bin/env bash echo "♥" $(pmset -g batt | grep -Eo '[0-9]+%')
- Make the file executable:
chmod +x battery.sh
- Call it from within
tmux.conf
:
tmux.confset -g status-right "#($HOME/.config/tmux/battery.sh)"