0

In my code, I'm using a JSON file to decide what the values are to be used to initialize an object.

In my config.json:

{
  "ClassA": {
    "p1": 3,
    "p2"; 4
  }
}

My ClassA:

class ClassA:
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2

Then I'm instantiating ClassA via:

with open("config.json", "r") as f:
    config = json.load(f)

a = ClassA(**config["ClassA"])

Now the problem arises when either the keys associated with the value changes (e.g. "p1" changes to "p3") in the JSON; or the name of the parameters in ClassA changes. As any change would mean that using the instantiation method above fails.

In an attempt to decouple the configuration with my code, I've defined another class to map the configuration parameters names to my class parameter names and vice-versa.

class IInitializable(ABC):
    _parameter_name_lookup = dict()

    @classmethod
    def parameter_name_lookup(cls, mode):
        if mode == "ConfigurationToClass":
            return {y: x for x, y in cls._parameter_name_lookup.items()}
        elif mode == "ClassToConfiguration":
            return cls._parameter_name_lookup
        else:
            raise NameError("No such mode")

Now say I change my config.json to:

{
  "ClassA": {
    "p1": 3,
    "p9"; 4
  }
}

And my ClassA to:

class ClassA:
    def __init__(self, p1, p4):
        self.p1 = p1
        self.p2 = p4

I would let ClassA inherit from IInitializeable and define the mapping:

class ClassA(IInitializable):
    _parameter_name_lookup = {"p4": "p9"}
    def __init__(self, p1, p4):
        self.p1 = p1
        self.p2 = p4

Then I could initialize my ClassA with the new config.json by:

with open("config.json", "r") as f:
    config = json.load(f)

config_to_class_lookup = ClassA.parameter_name_lookup("ConfigurationToClass")
new_parameters_for_a = dict()
for config_parameter_name, value in config.items():
    class_parameter_name = config_to_class_lookup.get(config_parameter_name, config_parameter_name)
    new_parameters_for_a[class_parameter_name] = value

a = ClassA(**new_parameters_for_a)

Though, I'm sure that there is a more elegant way to approach this problem. I've read up my metaclasses and decorators, and it seems(?) like that is the direction to go. But I can't seem to fit all the pieces together.

Matheus Lacerda
  • 5,983
  • 11
  • 29
  • 45
YellowPillow
  • 4,100
  • 6
  • 31
  • 57
  • Take a look at this: https://stackoverflow.com/questions/45420138/is-it-possible-to-programmatically-set-instance-attributes – cosmic_inquiry Jun 14 '18 at 04:11

1 Answers1

0

I think you're making this too complicated.

Changing the API should be something you do rarely, not all the time.

You probably already need to version your config files so you know which API version they were designed for.

So, if you write code that migrates from earlier JSON schemas to the current one, you can just do that at load time. Then all of your classes only have to worry about how to initialize from the current version. (And presumably, if they also need to save, you never need to save an old version, making that half of the problem nonexistent.)

Migrating from one JSON schema version to another is a common problem, with known solutions and plenty of libraries to assist you. (And the more general database migration problem is even more common and well-known.)

And it's relatively easy even if you want to write it yourself—no need for metaclasses or decorators or anything else, just recursively walking and transforming a nested dict/list structure according to either hardcoded rules (for a quick&dirty migrate) or lookups in some kind of transformation dict (for a cleaner solution).

Plus, this makes it easy to write unit tests for your migrations, instead of needing some complicated integration tests between the cartesian product of all the versions of all of your classes.

abarnert
  • 354,177
  • 51
  • 601
  • 671