0

I'm trying to find an elegant solution to this basic problem. I'm working on a webhook, I receive data in form of a json.

I have to handle the data based on one of the values.

import json

data = json.loads(raw_json)
data = {
  'type': 'A', # Type can be A, B or C
  'SomeAttribute' : 'SomeValue',
  'SomeOtherAttribute' : 'SomeOtherValue',
}

Based on the type I have to process the whole dictionary and perform some actions based one the type. Of course I could simply define some functions and make an infinite stack of ifs, but that doesn't look elegant.

if data['type'] == 'A':
    handle_type_A(data)
elif data['type'] == 'B':
    handle_type_B(data)
elif data['type'] == 'C':
    handle_type_C(data)
elif data['type'] == 'D':
    handle_type_D(data)
elif data['type'] == 'E':
    handle_type_E(data)

Strategy pattern seems the way ? I'm a beginner.

petezurich
  • 9,280
  • 9
  • 43
  • 57

2 Answers2

2

I think it should be possible as following:

data = {
  'type': 'A', # Type can be A or B
  'SomeAttribute' : 'SomeValue',
  'SomeOtherAttribute' : 'SomeOtherValue',
}

def str2function(string:str):
   module = __import__('foo')
   func = getattr(module, 'bar')
   return func

func = str2function("handle_type_" + data["type"])
func(data)

Not absolutely shure about this method tho. Resource: https://stackoverflow.com/a/4605/20443541

or

data = {
  'type': 'A', # Type can be A or B
  'SomeAttribute' : 'SomeValue',
  'SomeOtherAttribute' : 'SomeOtherValue',
}

rule = {"A":hande_type_A, "B":handly_type_B}

rule[data["type"]](data)

You might adapt the code for other letters

kaliiiiiiiii
  • 925
  • 1
  • 2
  • 21
  • This would lead to a big dictionary. Also everytime I have to add a new case I'd need to add a strategy and also edit the dictionary. Definetely better than a lot of ifs. I'm wondering if there is anything more elegant. – user13019610 Jan 19 '23 at 17:54
  • Edited my answer. Not absolutely shure about tho. – kaliiiiiiiii Jan 19 '23 at 18:15
1

Welcome to Python! You haven't stated what "elegance" mean in this context, but my guess is that you don't like the if statements, mostly because you'd have to code, say, 25 branches for 25 types, in addition to your 25 different implementations of handle.

If you are new to classes in Python, and you are certain that every datum in your dataset is different only by its type, you may want to look at something like the following code:

import abc

class Datum(metaclass=abc.ABCMeta):

    def __init__(self, some_attr, some_other_attr):
        self._some_attr = some_attr
        self._some_other_attr = some_other_attr

    @abc.abstractmethod
    def tag():
        pass

    @abc.abstractmethod
    def handle(self):
        pass


class TypeA(Datum):
 
    @staticmethod
    def tag():
        return "A"

    def __init__(self, some_attr, some_other_attr):
        super().__init__(some_attr, some_other_attr)

    def handle(self):
        print("Type-A stuff.")


class TypeB(Datum):

    @staticmethod
    def tag():
        return "B"

    def __init__(self, some_attr, some_other_attr):
        super().__init__(some_attr, some_other_attr)

    def handle(self):
        print("Type-B stuff.")


class TypeC(Datum):

    @staticmethod
    def tag():
        return "C"

    def __init__(self, some_attr, some_other_attr):
        super().__init__(some_attr, some_other_attr)

    def handle(self):
        print("Type-C stuff.")

types = {datatype.tag() : datatype for datatype in [TypeA, TypeB, TypeC]}

Here, Datum is an abstract parent class, from which A, B, C, and possibly more derive or inherit. Every derived class will have its own implementation of handle, while still retaining its meaning as a datum, including its own other attributes. The final line defining types provides a way to select the correct type given a tag, as explained in the next code:

# your json file.
dataset = [{"Type" : "A", "SomeAttr" : "Thor", "SomeOtherAttr" : "Blue"},
           {"Type" : "B", "SomeAttr" : "Hulk", "SomeOtherAttr" : "Green"},
           {"Type" : "C", "SomeAttr" : "IronMan", "SomeOtherAttr" : "Red"}]

data = []
for entry in dataset:
    # `entry["Type"]` will be "A", "B", "C", ...
    # `types[entry["Type"]]` will be TypeA, TypeB, TypeC, ...
    # `types[entry["Type"]](entry["SomeAttr"], entry["SomeOtherAttr"])` will
    # construct Data object with appropriate handle function
    data.append(types[entry["Type"]](entry["SomeAttr"], entry["SomeOtherAttr"]))

# This will process every datum with appropriate handle function.
for d in data:
    d.handle()
JustLearning
  • 1,435
  • 1
  • 1
  • 9
  • I'm playing around a little to understand ... but im wondering ... with this method it looks like everytime I need to add a new type I have to add a subclass to implement the new way of handling it and add it to the list in order to generate the dictionary. There is no way of getting around the dictionary with the subclasses and the types ? Ideally I'd love to simply add a new subclass everytime a I need to handle a new type. Sorry if I'm talking no sense – user13019610 Jan 20 '23 at 11:46
  • Even if you didnt want to add every sub class for every type, you would still have to add every function implementation somewhere. This approach would give you some organization that could potentially adapt well to future applications of your code. – JustLearning Jan 21 '23 at 00:25
  • As for the dictionary, Im afraid there's no way around it, as the data type in your program is unavoidably chosen at _runtime_. – JustLearning Jan 21 '23 at 00:26