From a4ed8e7fa514031d24abdcd512fedc4a8a9fa6ea Mon Sep 17 00:00:00 2001
From: David Beniamine <david.beniamine@tetras-libre.fr>
Date: Tue, 21 Feb 2023 00:08:21 +0100
Subject: [PATCH] =?UTF-8?q?WIP=C2=A0trying=20to=20setup=20dependency=20inj?=
 =?UTF-8?q?ection?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Mirador_backend/app.py              | 35 ++++++++++++++++++-----------
 Mirador_backend/models.py           | 22 +++++++++++++-----
 Mirador_backend/resources/base.py   |  3 ---
 Mirador_backend/tests/tester.py     |  7 +++---
 Mirador_backend/utils/app.py        |  9 --------
 Mirador_backend/utils/config.py     | 16 +++++--------
 Mirador_backend/utils/containers.py | 14 ++++++++++++
 Mirador_backend/utils/database.py   |  6 ++---
 setup.py                            |  1 +
 9 files changed, 66 insertions(+), 47 deletions(-)
 delete mode 100644 Mirador_backend/utils/app.py
 create mode 100644 Mirador_backend/utils/containers.py

diff --git a/Mirador_backend/app.py b/Mirador_backend/app.py
index 9ac4c93..f8e1395 100644
--- a/Mirador_backend/app.py
+++ b/Mirador_backend/app.py
@@ -4,25 +4,34 @@ from flask_restful_swagger import swagger
 from flask_apispec.extension import FlaskApiSpec
 
 from Mirador_backend.routes import v1
-from Mirador_backend.utils.config import ConfigFactory
+from Mirador_backend.utils.containers import Container
 from Mirador_backend.utils.database import db
-from Mirador_backend.utils.app import setApp
+from Mirador_backend.utils.config import Config
+from dependency_injector import providers
+from dependency_injector.wiring import Provide, inject
 
-app = Flask(__name__)
-setApp(app)
-api = Api(app)
-docs = FlaskApiSpec(app)
 
-app.config.from_object(ConfigFactory())
-app.db = db(app.config['DB'], app.config['DEBUG'])
+@inject
+def shutdown_session(db: db = Provide[Container.db], exception=None):
+    db.close()
 
-v1.register_routes(api, docs)
 
+@inject
+def do_create_app(config: Config = Provide[Container.config]) -> Flask:
+    app = Flask(__name__)
+    app.container = Container()
+    app.config = config
+    app.teardown_appcontext(shutdown_session)
+    api = Api(app)
+    docs = FlaskApiSpec(app)
+    v1.register_routes(api, docs)
+    return app
 
-@app.teardown_appcontext
-def shutdown_session(exception=None):
-    app.db.close()
+
+# Just for flask resolver
+def create_app():
+    return do_create_app()
 
 
 if __name__ == '__main__':
-    app.run()
+    create_app().run()
diff --git a/Mirador_backend/models.py b/Mirador_backend/models.py
index e6e15a9..260868d 100644
--- a/Mirador_backend/models.py
+++ b/Mirador_backend/models.py
@@ -4,8 +4,10 @@ from sqlalchemy.orm import mapped_column
 from sqlalchemy import Integer, JSON
 from sqlalchemy import select
 from sqlalchemy.exc import NoResultFound
+from Mirador_backend.utils.containers import Container
+from Mirador_backend.utils.database import db
+from dependency_injector.wiring import Provide, inject
 import json
-from Mirador_backend.utils.app import getApp
 
 
 class NotFound(NoResultFound):
@@ -16,10 +18,10 @@ class BaseModel(DeclarativeBase):
     """A generic model class
     """
 
-    session = getApp().db.session
+    session = None
     """Access the db session"""
 
-    engine = getApp().db.engine
+    engine = None
     """Access the db session"""
 
     fillable = ['id']
@@ -33,13 +35,21 @@ class BaseModel(DeclarativeBase):
               }
     """Actual data fields"""
 
-    def create_all():
+    @inject
+    def __init__(self, db: db = Provide[Container.db]):
+        super().__init__()
+        if db is None:
+            raise Exception('DB must be set')
+        self.session = db.session
+        self.engine = db.engine
+
+    def create_all(self):
         """Creates all tables for all models"""
-        BaseModel.metadata.create_all(getApp().db.engine)
+        BaseModel.metadata.create_all(self.engine)
 
     def drop_all():
         """drop all tables for all models"""
-        BaseModel.metadata.drop_all(getApp().db.engine)
+        BaseModel.metadata.drop_all(self.engine)
 
     @classmethod
     def query(cls, stmt):
diff --git a/Mirador_backend/resources/base.py b/Mirador_backend/resources/base.py
index e264ec2..945ceae 100644
--- a/Mirador_backend/resources/base.py
+++ b/Mirador_backend/resources/base.py
@@ -10,9 +10,6 @@ from flask_apispec import doc
 from flask_restful import marshal, fields
 
 from Mirador_backend.models import BaseModel, NotFound
-from Mirador_backend.utils.app import getApp
-
-logger = getApp().logger
 
 
 class ResourceGroup(MethodResource, Resource):
diff --git a/Mirador_backend/tests/tester.py b/Mirador_backend/tests/tester.py
index f2f6882..2a1d61a 100644
--- a/Mirador_backend/tests/tester.py
+++ b/Mirador_backend/tests/tester.py
@@ -3,14 +3,15 @@ import json
 from os.path import dirname, abspath
 from flask_fixtures import FixturesMixin
 
-from Mirador_backend.app import app
+from Mirador_backend.app import create_app
 from Mirador_backend.models import BaseModel
 
 
 class TestCase(unittest.TestCase, FixturesMixin):
     fixtures = ['test.json']
-    app = app
-    db = BaseModel
+
+    app = create_app()
+    db = BaseModel()
 
     def setUp(self):
         self.base = 'v1/mirador_resource'
diff --git a/Mirador_backend/utils/app.py b/Mirador_backend/utils/app.py
deleted file mode 100644
index 71f6b56..0000000
--- a/Mirador_backend/utils/app.py
+++ /dev/null
@@ -1,9 +0,0 @@
-def getApp():
-    return getApp.app
-
-
-getApp.app = None
-
-
-def setApp(app):
-    getApp.app = app
diff --git a/Mirador_backend/utils/config.py b/Mirador_backend/utils/config.py
index 002af56..23e4297 100644
--- a/Mirador_backend/utils/config.py
+++ b/Mirador_backend/utils/config.py
@@ -23,13 +23,9 @@ class Config(object):
         'BASE': getenv('MYSQL_DATABASE', 'base'),
     }
 
-
-class TestingConfig(Config):
-    TESTING = True
-    DEBUG = True
-    DB = None
-    FIXTURES_DIRS = ['tests/fixtures']
-
-
-def ConfigFactory():
-    return TestingConfig if getenv('ENV', 'dev') == 'test' else Config
+    def __init__():
+        if getenv('ENV', 'dev') == 'test':
+            self.TESTING = True
+            self.DEBUG = True
+            self.DB = None
+            self.FIXTURES_DIRS = ['tests/fixtures']
diff --git a/Mirador_backend/utils/containers.py b/Mirador_backend/utils/containers.py
new file mode 100644
index 0000000..624b421
--- /dev/null
+++ b/Mirador_backend/utils/containers.py
@@ -0,0 +1,14 @@
+from dependency_injector import containers, providers
+from Mirador_backend.utils.config import Config
+from Mirador_backend.utils.database import db
+
+
+class Container(containers.DeclarativeContainer):
+    wiring_config = containers.WiringConfiguration(modules=[
+        "Mirador_backend.app",
+        "Mirador_backend.models",
+        "Mirador_backend.resources.base",
+        "Mirador_backend.tests.tester"
+    ])
+    config = providers.Factory(Config)
+    db = providers.Factory(db, config=config)
diff --git a/Mirador_backend/utils/database.py b/Mirador_backend/utils/database.py
index 8d35a0d..5f7c4ee 100644
--- a/Mirador_backend/utils/database.py
+++ b/Mirador_backend/utils/database.py
@@ -9,9 +9,9 @@ class db():
             return 'sqlite://'
         return f'{params["TYPE"]}://{params["USER"]}:{params["PASSWORD"]}@{params["HOST"]}/{params["BASE"]}'
 
-    def __init__(self, config, debug):
-        self.config = config
-        self.engine = create_engine(self.__getDBUri(), echo=debug)
+    def __init__(self, config):
+        self.config = config['DB']
+        self.engine = create_engine(self.__getDBUri(), echo=config['DEBUG'])
         self.session = scoped_session(sessionmaker(autocommit=False,
                                                    autoflush=False,
                                                    bind=self.engine))
diff --git a/setup.py b/setup.py
index 5cfd0c1..c939828 100644
--- a/setup.py
+++ b/setup.py
@@ -39,6 +39,7 @@ setup(
         # 1.1.x is incompatible with mariadb connector already in python:3.10 docker image
         "mariadb>=1.0.11,<1.1.0",
         "python-dotenv",
+        "dependency-injector",
     ],
 
     extras_require={
-- 
GitLab