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:
- Get and set are context-light terms for retrieval operations.
- Looking outside of vocabulary to language convention leads to a universal understanding of our interface.
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 GET
s, POST
s, DELETE
s and PUT
s 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:
- Implementing these is 🏄very cowabunga🏄.
- Doing these reps at the Python gym connects us to the underlying language design.
- 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