Ephes Blog

Miscellaneous things. Mostly Weeknotes and links I stumbled upon.

Date -

Unwrapping functools.wraps: How to Remove Decorators from a Python Function

, Jochen

Sometimes, you need to call a Python function without its decorators. I encountered this need while implementing custom exception handling for the ACS (Assertion Consumer Service) view in django-saml2-auth. This view was decorated with an exception_handler that rendered responses, which interfered with my custom handling. Fortunately, functools.wraps comes to the rescue, storing the original function in a __wrapped__ attribute.

Here’s an example to demonstrate:

from functools import wraps

def heading(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        to_decorate = f()
        return f"<h1>{to_decorate}</h1>"
    return wrapper


@heading
def decorated_hello():
    return "Hello World!"


# Using the decorated function
print(decorated_hello())
# Output: '<h1>Hello World!</h1>'

# Accessing the original, undecorated function
print(decorated_hello.__wrapped__())
# Output: 'Hello World!'

Python String Capitalization

, Jochen

I frequently copy titles from websites to use as anchor texts for links. However, these titles are often in all caps, and I don’t want to seem like I’m shouting. Previously, I would paste the all-caps title into a Python REPL, convert it to lowercase, and then manually capitalize the necessary words to make it look like a proper title. But today, I discovered a much simpler built-in solution:

>>> print("THIS IS A LOUD TITLE".title())
'This Is A Loud Title'

I had no idea about this function! 😅


Workaround for Paying OpenAI via bunq

, Jochen

tldr: The chatGPT app on iOS allows you to pay openAI using Apple Pay


Last week openAI stopped working for me and I got an error about a billing issue. I use a credit card from bunq to pay openAI, which worked fine for almost a year. When I investigated the issue, I got several misleading error messages from bunq like "your monthly limit is not sufficient" or "you have already added the merchant to your allowed merchants list". I couldn't fix the problem. So I contacted bunq support and they couldn't help either.

Today I thought about using another credit card, but then I found a workaround:

The chatGPT app on iOS allows you to pay openAI using Apple Pay. I couldn't do it with Chrome, and for Safari the Stripe interface lets you choose Apple Pay, but that didn't work either. But the app finally worked 🎉.


Upgrading Postgres

, Jochen

I am mainly writing this article to remember how to do this next time 😅 (it is 14 or 15 to 16 here, but it should also work with newer versions too). This writeup is for Debian / Ubuntu and is based on this article I found as the first search result on how to upgrade postgres. First, make sure that the packages on your system are up to date and that you have the version of postgres you want to upgrade installed (usually running on port 5433).

apt-get update
apt dist-upgrade

# Show installed postgres versions
apt list --installed | rg postgresql

Apply any configuration changes you made to the running version to the version you want to upgrade. Then you can stop your running postgres version and run pg_upgrade.

systemctl stop postgresql.service
su - postgres

# Check if clusters are compatible
/usr/lib/postgresql/16/bin/pg_upgrade \
  --old-datadir=/var/lib/postgresql/15/main \
  --new-datadir=/var/lib/postgresql/16/main \
  --old-bindir=/usr/lib/postgresql/15/bin \
  --new-bindir=/usr/lib/postgresql/16/bin \
  --old-options '-c config_file=/etc/postgresql/15/main/postgresql.conf' \
  --new-options '-c config_file=/etc/postgresql/16/main/postgresql.conf' \
  --check

# Run without --check to upgrade your cluster

Swap the ports of the old and new versions and restart the postgres service.

# change "port = 5434" to "port = 5432"
vim /etc/postgresql/16/main/postgresql.conf

# change "port = 5432" to "port = 5433"
vim /etc/postgresql/15/main/postgresql.conf

systemctl start postgresql.service

Make sure you are running the new postgres version.

su - postgres
psql -c "SELECT version();"

Remove the old Postgres Version

apt-get remove postgresql-15 postgresql-server-dev-15
rm -rf /etc/postgresql/15/
su - postgres
./delete_old_cluster.sh

Postgres on a Mac with Homebrew

I use Macs running Homebrew for development. And I like to keep my postgres databases in the project repository to make it easy to remove the database by just deleting a directory. And to start the right database along with all the other needed services using a Procfile. Upgrading postgres there works the same way. First install the postgres version you want to upgrade to and create a new data directory.

brew install postgresql@16
/opt/homebrew/Cellar/postgresql@16/16.0/bin/initdb -D databases/postgres_new

Then upgrade the database with pg_upgrade and remove the old data directory.

/opt/homebrew/Cellar/postgresql@16/16.0/bin/pg_upgrade \
 --old-datadir databases/postgres \
--new-datadir databases/postgres_new \
--old-bindir /opt/homebrew/Cellar/postgresql@14/14.9/bin \
--new-bindir /opt/homebrew/Cellar/postgresql@16/16.0/bin \
--check

# run pg_upgrade without --check to actually upgrade the database

rm -r databases/postgres
mv databases/postgres_new databases/postgres

How to Pass a Request Object from Wagtail API to a Page

, Jochen

Introduction

When using Wagtail's API to fetch website pages, the APIField class allows you to add custom fields. But what if you also need to incorporate the request object into your custom field?

Why the Request Object is Important

I ran into this issue because I wanted to fetch the fully-rendered HTML of a page via the Wagtail API, eliminating the need to manually render the StreamField in my Vue.js blog theme.

Code Example

To achieve this, use the APIField class as demonstrated in the code snippet below:

from wagtail.models import Page
from wagtail.api import APIField

from rest_framework.fields import Field

class HtmlField(Field):
    def to_representation(self, page):
        return page.serve(self.context["request"])

class Post(Page):
    ...
    api_fields = [
        APIField("html", serializer=HtmlField(source="*")),
    ]

Understanding the source="*" Parameter

The `source="*"` parameter is special: it enables the `to_representation` method to work with the entire page object, rather than just a specific field on that page.