Collection CRUDite 📦

Get and set methods are ubiquitous. If you read our previous text: Get Geleé, you know we like to replace those methods with collection interfaces. We explain why in that text, but here is a TL;DR:

Pottery

One velvety example of this replacement is the Pottery library. The following lifted straight from the pottery readme:

Redis is awesome, but Redis commands are not always intuitive. Pottery is a Pythonic way to access Redis. If you know how to use Python dicts, then you already know how to use Pottery.

Put another way: maybe a dict that happens to be in Redis can be interacted with like any other dict.

dict in memory:

doughs = {"filo": 2}
def use(dough):
	doughs[dough] -= 1

dict in Redis:

doughs = RedisDict({"choux": 2}, redis=redis, key='doughs')
def use(dough):
	doughs[dough] -= 1

Where did setex go? How about serialization and marshaling? Pottery facades them away. Very cool, but we did not pick Pottery without an ulterior motive: there exists juicy insight locked behind a door. The key is that Pottery’s abstraction of dicts… leaks.

Ordinary python dicts accept any object as a key that implements hashable. RedisDict, in contrast, requires keys to be JSON serializable. This leak teaches us that RedisDict does not map 1:1 to python dicts. Let us embrace a mental model re-wire: not all mappings are precisely dicts.

(To go even further beyond)[https://www.youtube.com/watch?v=8TGalu36BHA]

Dear reader, this next section contains unstable potions. We less mean this section as a suggestion for implementation, more as food for thought.

Just outside our convention blinders, so much can map to collection interfaces. Bear with us as we try to stuff CRUD into a mapping. As an example, here are GETs, POSTs, DELETEs and PUTs for an example API:

@app.route('/user', methods=('GET', 'POST', 'DELETE', 'PUT'))
def user():
	if request.method == "POST":
		return jsonify(users(request.form))

	user_id = request.form["id"]

	if request.method == "GET":
		return jsonify(users[user_id])
	if request.method == "PUT":
		users[user_id].update(request.form)
		return jsonify(users[user_id])
	if request.method == "DELETE":
		del users[user_id]
		return jsonify({"id": user_id})

	raise RuntimeError

How do you imagine this working with related entities and many-to-many relationships? Where would you place business logic and side effects?

We do not want you to refactor your app based on this idea. But given:

  1. Implementing these is 🏄very cowabunga🏄.
  2. Doing these reps at the Python gym connects us to the underlying language design.
  3. These patterns can be very natural to users.

We want you to consider subclassing abc.Mapping or abc.MutableMapping next time you need a class that smells getty (raspberry) or setty (butterscotch).

Love, Salomon & Ainsley