-
Notifications
You must be signed in to change notification settings - Fork 184
Expand file tree
/
Copy pathapplication_base.py
More file actions
132 lines (105 loc) · 4.84 KB
/
application_base.py
File metadata and controls
132 lines (105 loc) · 4.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
"""Abstract base for the application bootstrap sequence.
Orchestrates the startup order: load .env → read Azure App Configuration →
populate AppContext with configuration and credentials → configure logging.
The concrete ``initialize()`` hook is invoked
explicitly via ``bootstrap()``
after construction is complete.
"""
import inspect
import logging
import os
from abc import ABC, abstractmethod
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
from app.libs.application.application_configuration import (
AppConfiguration,
EnvConfiguration,
)
from app.libs.application.application_context import AppContext
from app.libs.azure.app_configuration.helper import AppConfigurationHelper
class Application_Base(ABC):
"""Abstract bootstrap base that every concrete application must extend.
Responsibilities:
1. Load environment variables from a ``.env`` file.
2. Pull remote config from Azure App Configuration into the process env.
3. Build and populate an AppContext with configuration and credentials.
4. Set up Python logging based on configuration flags.
5. Invoke the concrete ``initialize()`` hook.
Attributes:
application_context: Fully wired DI container available after ``__init__``.
"""
application_context: AppContext = None
@abstractmethod
def run(self):
"""Start the application server (must be implemented by subclasses)."""
raise NotImplementedError("The run method must be implemented by subclasses.")
@abstractmethod
def initialize(self):
"""Wire up middleware, routers, and dependencies (must be implemented)."""
raise NotImplementedError(
"The initialize method must be implemented by subclasses."
)
def __init__(self, env_file_path: str | None = None, **data):
"""Execute base bootstrap setup.
Steps:
1. Load ``.env`` from *env_file_path* (or derive from subclass location).
2. Read Azure App Configuration and inject values into ``os.environ``.
3. Populate ``application_context`` with config and Azure credentials.
4. Configure Python logging if enabled in config.
Args:
env_file_path: Explicit path to a ``.env`` file (optional).
"""
super().__init__(**data)
self._load_env(env_file_path=env_file_path)
self.application_context = AppContext()
self.application_context.set_credential(DefaultAzureCredential())
app_config_endpoint: str | None = EnvConfiguration().app_config_endpoint
if app_config_endpoint != "" and app_config_endpoint is not None:
AppConfigurationHelper(
app_config_endpoint=app_config_endpoint
).read_and_set_environmental_variables()
self.application_context.set_configuration(AppConfiguration())
if self.application_context.configuration.app_logging_enable:
logging_level = getattr(
logging, self.application_context.configuration.app_logging_level
)
logging.basicConfig(level=logging_level)
if self.application_context.configuration.azure_logging_packages:
azure_level = getattr(
logging,
self.application_context.configuration.azure_package_logging_level.upper(),
logging.WARNING,
)
for logger_name in filter(
None,
(
pkg.strip()
for pkg in self.application_context.configuration.azure_logging_packages.split(
","
)
),
):
logging.getLogger(logger_name).setLevel(azure_level)
def bootstrap(self):
"""Run subclass initialization after construction has completed."""
self.initialize()
def _load_env(self, env_file_path: str | None = None):
"""Load a ``.env`` file, deriving the path from the subclass if not given.
Args:
env_file_path: Explicit path; when ``None`` the directory of the
concrete subclass file is used.
Returns:
The resolved path that was loaded.
"""
if env_file_path:
load_dotenv(dotenv_path=env_file_path)
return env_file_path
derived_class_location = self._get_derived_class_location()
env_file_path = os.path.join(os.path.dirname(derived_class_location), ".env")
load_dotenv(dotenv_path=env_file_path)
return env_file_path
def _get_derived_class_location(self):
"""Return the filesystem path of the concrete subclass source file."""
return inspect.getfile(self.__class__)