import random
from typing import Callable, ParamSpec, TypeVar
T = TypeVar("T")
P = ParamSpec("P")
def some_global_condition() -> bool:
return random.choice([True, False])
def my_return_type_preserving_decorator(val: T) -> Callable[[Callable[P, T]], Callable[P, T]]:
def decorator(func: Callable[P, T]) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
if some_global_condition():
return val
return func(*args, **kwargs)
return wrapper
return decorator
@my_return_type_preserving_decorator((True, True, True))
def my_func(n: int) -> tuple[bool, bool, bool]:
return n > 10, n > 100, n > 1000
x = my_func(5)
for r in x:
print("yes" if r else "no")
Format Percentage for Display in JavaScript
| Input | Output |
|---|---|
| 0.12 | 12% |
| 0.0012345 | 0.12% |
| 0.0 | 0 |
2 significant digits but locale-sensitive, strip meaningless trailing zeroes and decimal dividers.
const formatPercentage = function (value) {
return new Intl.NumberFormat(
undefined, // use browser locale
// Choose the options that get us closest to our desired style – a percentage that tells you enough to be useful but no unnecessary decimal places or trailing zeroes.
{
style: "percent", // Treat decimals as percentages, multiplying by 100 and adding a % sign.
minimumSignificantDigits: 2,
maximumSignificantDigits: 2
}
)
.format(value)
// \D here is the locale-agnostic decimals separator.
.replace(/(\D[0-9]*?)0+%$/, "$1%") // Get rid of trailing zeroes after the decimal separator.
.replace(/\D%/, "%") // Clean up the scenario where we got rid of everything after the decimal separator and now have something like "4.%.
.replace(/^0%$/, "0"); // Replace literal "0%" with "0" as zero is unitless.
};
Force pipenv to use Charles Proxy
- Export charles-ssl-proxying-certificate.pem from Charles Proxy
REQUESTS_CA_BUNDLE=/path/to/charles-ssl-proxying-certificate.pem pipenv {cmd} {args}
Python Types for Union of String Literal and Other Type
I wanted to add a single special value to another type for a union type, something like this:
from typing import Literal
UNMEASURED = "unmeasured"
Unmeasured = Literal[UNMEASURED]
Resource = int | Unmeasured
notes: dict[Resource, str] = {1: "foo", 2: "bar", 3: "baz", UNMEASURED: "quux"}
resources = [1, 2, 3] + [UNMEASURED]
for resource in resources:
print(notes[resource])
This works, but mypy complains: ‘error: Parameter 1 of Literal[…] is invalid [valid-type]’. So to make this pass mypy typechecking you have to repeat “unmeasured” as a literal. Even if you do that:
from typing import Literal
UNMEASURED = "unmeasured"
Unmeasured = Literal["unmeasured"]
Resource = int | Unmeasured
notes: dict[Resource, str] = {1: "foo", 2: "bar", 3: "baz", UNMEASURED: "quux"}
resources = [1, 2, 3] + [UNMEASURED]
for resource in resources:
print(notes[resource])
mypy will complain: ‘Dict entry 3 has incompatible type “str”: “str”; expected “int | Literal[‘unmeasured’]”: “str” [dict-item]’ and also ‘Invalid index type “str | int” for “dict[int | Literal[‘unmeasured’], str]”; expected type “int | Literal[‘unmeasured’]” [index]’
Eventually I came up with:
from enum import Enum
from typing import Literal
class Aspect(Enum):
UNMEASURED = "unmeasured"
Resource = int | Aspect
notes: dict[Resource, str] = {1: "foo", 2: "bar", 3: "baz", Aspect.UNMEASURED: "quux"}
resources = [1, 2, 3] + [Aspect.UNMEASURED]
for resource in resources:
print(notes[resource])
which both produces the same result, and passes the typechecker. I don’t love the weird Enum hanging out on its own but it does at least put the magic string in one place only and pass. I wonder what would be better.
Automatially Activate venv When Found in Directory
# Function to activate virtualenv if present
auto_activate_virtualenv() {
if [ -f "bin/activate" ]; then
source bin/activate
fi
}
# Add the function to chpwd hooks
add-zsh-hook chpwd auto_activate_virtualenv
How to indicate that a single file module is typed to mypy
You can’t. Repackage your module as the only module in a package.
mypy needs a py.typed in the package’s directory and if you have a single module at the top level of your repo there’s no way to get setuptools to package that in such a way that the wheel will contain the py.typed in a useful place.
Example of the changes needed: github.com/wheerd/multiset/pull/127/files
Remember the Milk Alfred Workflow
This uses rtm-cli which you can install with npm.
query=$1 logfile="/Users/bakert/u/scratch/rtm.log" rtm=/opt/homebrew/bin/rtm max_lines=1000 # Log the query echo "[$(date)] $query" >> "$logfile" # Ensure the logfile does not grow beyond $max_lines tail -n $max_lines "$logfile" > "$logfile.tmp" && mv "$logfile.tmp" "$logfile" # Send the task to Remember the Milk "$rtm" add $query if [ $? -ne 0 ]; then echo "SOMETHING WENT WRONG! Please check that rtm-cli is installed: npm install -g rtm-cli" else echo "[Added] $query" fi
Gmail Conversations Archiving Has Gotten Worse
assertRaises in table-driven tests
This is what I want to do but it fails passing None to assertRaises:
tests = [
(0, None),
(1, None),
(-1, TooFewException),
(99, None),
(100, TooManyException),
]
for n, exc in tests:
with self.assertRaises(exc):
results = my_code(n)
assert len(results) == n
Here’s a version of assertRaises that will let you do that:
def assert_raises(self, exception: Type[Exception]):
if exception:
return self.assertRaises(exception)
return contextlib.nullcontext()
Line Count
Number of lines in a (python + js) codebase:
#!/bin/zsh
# This script counts the number of non-blank lines of code in a directory and its subdirectories
# for Python and JavaScript code only
# Set the directory to search for code files
dir='.'
# Count the number of non-blank lines of code for Python and JavaScript files
num_lines=$(find "$dir" -type f \( -name '*.py' -or -name '*.js' \) -not -path '*venv*' -not -path '*node_modules*' -exec grep -he '^[^[:space:]]' {} + | awk 'NF{count++} END{print count}')
echo "Number of non-blank lines of code: $num_lines"
Number of lines per-file in a (python + js) codebase:
#!/bin/zsh # This script counts the number of non-blank lines of code in a directory and its subdirectories # for Python and JavaScript code only # Set the directory to search for code files dir='.' # Count the number of non-blank lines of code for Python and JavaScript files num_lines=$(find "$dir" -type f \( -name '*.py' -or -name '*.js' \) -not -path '*venv*' -print0 | xargs -0 grep -cve '^[[:space:]]* ) echo "Number of non-blank lines of code: $num_lines"
