lundi 24 octobre 2022

Python architecture, correct way to pass configurable initialization object to project modules

I have a big architecture question about how to pass set of configurable / replaceable objects to modules of my project? For example the set may be a bot, logger, database, etc.
Currently I'm just importing they, it make a big problem when you want to replace them during a tests.
Lets' say import app.bot will hard to test and patch

I had tried a multiple solutions but failed with them:

Option 1:
Create some base class with which will accepts set of such objects (db, bot, etc).
Every logic class (who need this set) will inherit this class.
AFAIK the similar approach there in SqlAlchemy ORM.

So the code will looks like:

app.config.py:

Class Config:
    db: DB
    ...

tests.py:

import app.config

Config.db = Mock()

create_app.py:

import app.config

def create_app(db):
    app.config.Config.db = db

logic.py

import app.config

User(app.config.Config):
    def handle_text(text):
        self.db.save_text(text=text)
        ...

The problem with this case is that most likely you can't importing as from app.config import Config
because it will lead to wrong behavior and this is implicit restriction.

Option 2

Pass this set in __init__ arguments to every instance.
(It's ma be a problem if app has many classes, > 20 like in my app).

User:
    def __init__(..., config: ProductionConfig):
        ...

Option 3
In many backend frameworks (flask for example) there are context object.
Well we can inject our config into this context during initialization.

usage.py:

my_handler(update, context):
    context.user.handle_text(text=update.text, db=context.db) 

The problem with this approach is that every time we need to pass context to access a database in our logic.

Option 4
Create config by condition and import it directly.
This solution may be bad because conditions increases code complexity.
I'm following rule "namespace preferable over conditions".

app.config.py:

db = get_test_db() if DEBUG else get_production_db()
bot = Mock() if DEBUG else get_production_bot()

P.S. this question isn't "opinion based" because in some point the wrong solutions will leads to bad design and bugs therefore.

Aucun commentaire:

Enregistrer un commentaire