Home > Software engineering >  Is calling an API from inside an initializer in Python bad?
Is calling an API from inside an initializer in Python bad?

Time:04-27

I have a Python class that requires some data in order to be initialized. This data is usually obtained using a function from another module, which makes calls to an API. One of the parameters my class' initializer takes is the same ID that can be used to obtain the resource with the API.

Calling the API from inside the initializer, and obtaining the data it needs would make for shorter (and cleaner?) initialization. But I am concerned this could make the class harder to test, and introduce a dependency deep inside the code.

I'm trying to devise the best way to implement this in a maintainable and testable way.

Would it be bad to call the API module directly from within the initializer, and obtain the data it needs to complete initialization? Or is it better to just call the API from outside and pass the data to the initializer?

CodePudding user response:

The "normal" way(1) is the pass the dependent function, module, or class, into the constructor itself.

Then, in your production code, pass in the real thing. In your test code, pass in a dummy one that will behave exactly as you desire for the specific test case.

That's actually a half-way measure between the two things you posit.

In other words, something like:

def do_something_with(something_generator):
    something = something_generator.get()
    print(something)

# Real code.
do_something_with(ProductionGenerator())

# Test code.
class TestGenerator:
    def get(self):
        return 42

do_something_with(TestGenerator())

If you're reticent to always pass in a dependency, you can get around that with something like a default value and creating it inside the function if not given:

def do_something(something_generator=None):
    if something_generator is None:
        local_gen = ProductionGenerator()
    else:
        local_gen = something_generator

    something = something_generator.get()
    print(something)

# Real code.
do_something()

# Test code.
class TestGenerator:
    def get(self):
        return 42

do_something(TestGenerator())

(1) Defined, of course, as the way I do it :-)

  • Related