Behold the Dictionary Dispatch Monster
A function that receives a payload and converts it to any file format imaginable.
Well, well, well, what do we have here? I'll tell you what. It's a thing that will stop you from writing functions that contain lots and lots of if
statements. Why? Because it's a pain in the a$$, it creates spaghetti code that is difficult to maintain, debug and test blah blah blah.
Say, you want to write a function that receives a payload and converts it to any file format imaginable. Here's an example of what to avoid:
from pathlib import Path
from typing import Dict, Any
def convert_payload(
payload: Dict[str, Any],
output_file_name: Path,
output_format: str
) -> None:
if output_format == "json":
with open(file=output_file_name, mode="w") as f:
f.write("I am json")
elif output_format in {"yaml", "yml"}:
with open(file=output_file_name, mode="w") as f:
f.write("I am yaml")
elif output_format == "csv":
with open(file=output_file_name, mode="w") as f:
f.write("I am csv")
elif output_format == "tsv":
with open(file=output_file_name, mode="w") as f:
f.write("I am tsv")
elif output_format == "avro":
with open(file=output_file_name, mode="w") as f:
f.write("I am avro")
elif output_format == "parquet":
with open(file=output_file_name, mode="w") as f:
f.write("I am parquet")
elif output_format == "arrow":
with open(file=output_file_name, mode="w") as f:
f.write("I am arrow")
elif output_format == "pickle":
with open(file=output_file_name, mode="w") as f:
f.write("I am pickle")
else:
raise ValueError("unexpected file conversion")
We haven't really written any of the conversion code yet and the function already looks like it's going to explode. Each file format has it's own quirks and before you know it you may have one 500+ line function. Fabulous? No. Before we go down that route, let's think about slicing convert_payload
to smaller independently testable pieces. For example:
def to_json(payload: Dict[str, Any]) -> Any:
return "I am json"
def to_yaml(payload: Dict[str, Any]) -> Any:
return "I am yaml"
def to_pickle(payload: Dict[str, Any]) -> Any:
return "I am pickle"
def to_...(payload: Dict[str, Any]) -> Any:
return "I am ..."
We shouldn't really be using pickle
for a number of reasons but let's go ahead and use it for illustration purposes and because it's cringeworthy.
The next step would be to create a dictionary with each convert function assigned to it's name.
converter_dispatch = {
"json": to_json,
"yaml": to_yaml,
"yml": to_yaml,
"pickle": to_pickle
}
Hopefully, it all starts to become clearer now. Our convert_payload
function becomes something like this:
def convert_payload(
payload: Dict[str, Any],
output_file_name: Path,
output_format: str
):
func = converter_dispatch[output_format]
converted_payload = func(payload=payload)
with open(file=output_file_name, mode="w") as f:
f.write(converted_payload)
Let's unpack as per the blog's tradition:
- Get the function to call, that corresponds to each file format. If
output_format
isjson
thenfunc
will be equivalent toto_json
. Magic huh?
func = converter_dispatch[output_format]
2. Therefore, func(payload=payload)
is identical to to_json(payload=payload)
.
converted_payload = func(payload=payload)
3. Finally the file is written.
with open(file=output_file_name, mode="w") as f:
f.write(converted_payload)
理解。- ¿Comprende? - verstanden?
See ya.