bikes.io.services
Manage global context during execution.
1"""Manage global context during execution.""" 2 3# %% IMPORTS 4 5from __future__ import annotations 6 7import abc 8import contextlib as ctx 9import os 10import sys 11import typing as T 12import urllib.parse 13import warnings 14from typing import TYPE_CHECKING 15 16import loguru 17import mlflow 18import mlflow.tracking as mt 19import pydantic as pdt 20from plyer import notification 21 22if TYPE_CHECKING: 23 from loguru import Logger 24else: 25 Logger = type(loguru.logger) 26 27# Keep the platform notification proxy outside the documented service API. 28__all__ = ["AlertsService", "LoggerService", "MlflowService", "Service", "redact_uri"] 29 30# %% HELPERS 31 32 33def redact_uri(uri: str) -> str: 34 """Hide the password of a URI before it reaches the logs. 35 36 e.g., "postgresql://user:secret@host/db" becomes "postgresql://user:***@host/db". 37 38 Args: 39 uri (str): URI that may embed credentials. 40 41 Returns: 42 str: the same URI without its password. 43 """ 44 parts = urllib.parse.urlsplit(uri) 45 if parts.password is None: 46 return uri 47 _, _, host = parts.netloc.rpartition("@") 48 return parts._replace(netloc=f"{parts.username}:***@{host}").geturl() 49 50 51# %% SERVICES 52 53 54class Service(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"): 55 """Base class for a global service. 56 57 Use services to manage global contexts. 58 e.g., logger object, mlflow client, spark context, ... 59 """ 60 61 @abc.abstractmethod 62 def start(self) -> None: 63 """Start the service.""" 64 65 def stop(self) -> None: 66 """Stop the service.""" 67 # does nothing by default 68 69 70class LoggerService(Service): 71 """Service for logging messages. 72 73 https://loguru.readthedocs.io/en/stable/api/logger.html 74 75 Parameters: 76 sink (str): logging output. 77 level (str): logging level. 78 format (str): logging format. 79 colorize (bool): colorize output. 80 serialize (bool): convert to JSON. 81 backtrace (bool): enable exception trace. 82 diagnose (bool): enable variable display. 83 catch (bool): catch errors during log handling. 84 """ 85 86 sink: str = "stderr" 87 level: str = "DEBUG" 88 format: str = ( 89 "<green>[{time:YYYY-MM-DD HH:mm:ss.SSS}]</green>" 90 "<level>[{level}]</level>" 91 "<cyan>[{name}:{function}:{line}]</cyan>" 92 " <level>{message}</level>" 93 ) 94 colorize: bool = True 95 serialize: bool = False 96 backtrace: bool = True 97 diagnose: bool = False 98 catch: bool = True 99 100 @T.override 101 def start(self) -> None: 102 loguru.logger.remove() 103 config = self.model_dump() 104 # use standard sinks or keep the original 105 sinks = {"stderr": sys.stderr, "stdout": sys.stdout} 106 config["sink"] = sinks.get(config["sink"], config["sink"]) 107 loguru.logger.add(**config) 108 109 def logger(self) -> Logger: 110 """Return the main logger. 111 112 Returns: 113 Logger: the main logger. 114 """ 115 return loguru.logger 116 117 118class AlertsService(Service): 119 """Service for sending notifications. 120 121 Require libnotify-bin on Linux systems. 122 123 In production, use with Slack, Discord, or emails. 124 125 https://plyer.readthedocs.io/en/latest/api.html#plyer.facades.Notification 126 127 Parameters: 128 enable (bool): use notifications or print. 129 app_name (str): name of the application. 130 timeout (int | None): timeout in secs. 131 """ 132 133 enable: bool = True 134 app_name: str = "Bikes" 135 timeout: int | None = None 136 137 # Plyer's NOTIFYICONDATAW buffers reserve one UTF-16 unit for the terminator. 138 _MAX_APP_NAME_LENGTH: T.ClassVar[int] = 127 139 _MAX_TITLE_LENGTH: T.ClassVar[int] = 63 140 _MAX_MESSAGE_LENGTH: T.ClassVar[int] = 255 141 142 @T.override 143 def start(self) -> None: 144 pass 145 146 def notify(self, title: str, message: str) -> None: 147 """Send a notification to the system. 148 149 Args: 150 title (str): title of the notification. 151 message (str): message of the notification. 152 """ 153 if self.enable: 154 notify_title, notify_message, app_name = title, message, self.app_name 155 if sys.platform == "win32": 156 notify_title = self._truncate(title, self._MAX_TITLE_LENGTH) 157 notify_message = self._truncate(message, self._MAX_MESSAGE_LENGTH) 158 app_name = self._truncate(app_name, self._MAX_APP_NAME_LENGTH) 159 try: 160 # plyer warns while it probes for a backend (dbus, notify-send): the 161 # NotImplementedError fallback below already reports a missing one. 162 with warnings.catch_warnings(): 163 warnings.filterwarnings(action="ignore", category=UserWarning, module="plyer") 164 notification.notify( 165 title=notify_title, 166 message=notify_message, 167 app_name=app_name, 168 timeout=self.timeout, 169 ) 170 except NotImplementedError: 171 print("Notifications are not supported on this system.") # noqa: T201 # user-facing fallback 172 self._print(title=title, message=message) 173 else: 174 self._print(title=title, message=message) 175 176 @staticmethod 177 def _truncate(value: str, max_length: int) -> str: 178 """Fit a Windows field in UTF-16 units without splitting a surrogate pair.""" 179 encoded = value.encode("utf-16-le") 180 if len(encoded) <= max_length * 2: 181 return value 182 prefix = encoded[: (max_length - 1) * 2].decode("utf-16-le", errors="ignore") 183 return f"{prefix}\N{HORIZONTAL ELLIPSIS}" 184 185 def _print(self, title: str, message: str) -> None: 186 """Print a notification to the system. 187 188 Args: 189 title (str): title of the notification. 190 message (str): message of the notification. 191 """ 192 print(f"[{self.app_name}] {title}: {message}") # noqa: T201 # user-facing fallback 193 194 195class MlflowService(Service): 196 """Service for Mlflow tracking and registry. 197 198 Parameters: 199 tracking_uri (str): the URI for the Mlflow tracking server. 200 registry_uri (str): the URI for the Mlflow model registry. 201 experiment_name (str): the name of tracking experiment. 202 registry_name (str): the name of model registry. 203 autolog_disable (bool): disable autologging. 204 autolog_disable_for_unsupported_versions (bool): disable autologging for unsupported versions. 205 autolog_exclusive (bool): If True, enables exclusive autologging. 206 autolog_log_input_examples (bool): If True, logs input examples during autologging. 207 autolog_log_model_signatures (bool): If True, logs model signatures during autologging. 208 autolog_log_models (bool): If True, enables logging of models during autologging. 209 autolog_log_datasets (bool): If True, logs datasets used during autologging. 210 autolog_silent (bool): If True, suppresses all Mlflow warnings during autologging. 211 """ 212 213 class RunConfig(pdt.BaseModel, strict=True, frozen=True, extra="forbid"): 214 """Run configuration for Mlflow tracking. 215 216 Parameters: 217 name (str): name of the run. 218 description (str | None): description of the run. 219 tags (dict[str, T.Any] | None): tags for the run. 220 log_system_metrics (bool | None): enable system metrics logging. 221 """ 222 223 name: str 224 description: str | None = None 225 tags: dict[str, T.Any] | None = None 226 log_system_metrics: bool | None = True 227 228 # server uri 229 # SQLAlchemy backends are the supported store in MLflow 3: SQLite gives the local 230 # setup the same shape as a production database (Postgres, MySQL) with no server to 231 # run. The legacy file store is in maintenance mode and disabled by default. 232 # Explicit YAML wins; otherwise honor MLflow's environment at construction time. 233 tracking_uri: str = pdt.Field(default_factory=lambda: os.environ.get("MLFLOW_TRACKING_URI", "sqlite:///mlflow.db")) 234 registry_uri: str = pdt.Field( 235 default_factory=lambda data: os.environ.get("MLFLOW_REGISTRY_URI", data["tracking_uri"]) 236 ) 237 # experiment 238 experiment_name: str = "bikes" 239 # registry 240 registry_name: str = "bikes" 241 # autolog 242 autolog_disable: bool = False 243 autolog_disable_for_unsupported_versions: bool = False 244 autolog_exclusive: bool = False 245 autolog_log_input_examples: bool = True 246 autolog_log_model_signatures: bool = True 247 autolog_log_models: bool = False 248 autolog_log_datasets: bool = False 249 autolog_silent: bool = False 250 251 @T.override 252 def __repr_args__(self) -> T.Iterator[tuple[str | None, T.Any]]: 253 # jobs log their services: never print the credentials of a database URI 254 for key, value in super().__repr_args__(): 255 yield key, redact_uri(value) if key is not None and key.endswith("_uri") else value 256 257 @T.override 258 def start(self) -> None: 259 # server uri 260 mlflow.set_tracking_uri(uri=self.tracking_uri) 261 mlflow.set_registry_uri(uri=self.registry_uri) 262 # experiment 263 mlflow.set_experiment(experiment_name=self.experiment_name) 264 # autolog 265 mlflow.autolog( 266 disable=self.autolog_disable, 267 disable_for_unsupported_versions=self.autolog_disable_for_unsupported_versions, 268 exclusive=self.autolog_exclusive, 269 log_input_examples=self.autolog_log_input_examples, 270 log_model_signatures=self.autolog_log_model_signatures, 271 log_models=self.autolog_log_models, 272 log_datasets=self.autolog_log_datasets, 273 silent=self.autolog_silent, 274 ) 275 276 @ctx.contextmanager 277 def run_context(self, run_config: RunConfig) -> T.Generator[mlflow.ActiveRun]: 278 """Yield an active Mlflow run and exit it afterwards. 279 280 Args: 281 run_config (RunConfig): mlflow run parameters. 282 283 Yields: 284 T.Generator[mlflow.ActiveRun, None, None]: active run context. Will be closed at the end of context. 285 """ 286 with mlflow.start_run( 287 run_name=run_config.name, 288 tags=run_config.tags, 289 description=run_config.description, 290 log_system_metrics=run_config.log_system_metrics, 291 ) as run: 292 yield run 293 294 def client(self) -> mt.MlflowClient: 295 """Return a new Mlflow client. 296 297 Returns: 298 MlflowClient: the mlflow client. 299 """ 300 return mt.MlflowClient(tracking_uri=self.tracking_uri, registry_uri=self.registry_uri)
119class AlertsService(Service): 120 """Service for sending notifications. 121 122 Require libnotify-bin on Linux systems. 123 124 In production, use with Slack, Discord, or emails. 125 126 https://plyer.readthedocs.io/en/latest/api.html#plyer.facades.Notification 127 128 Parameters: 129 enable (bool): use notifications or print. 130 app_name (str): name of the application. 131 timeout (int | None): timeout in secs. 132 """ 133 134 enable: bool = True 135 app_name: str = "Bikes" 136 timeout: int | None = None 137 138 # Plyer's NOTIFYICONDATAW buffers reserve one UTF-16 unit for the terminator. 139 _MAX_APP_NAME_LENGTH: T.ClassVar[int] = 127 140 _MAX_TITLE_LENGTH: T.ClassVar[int] = 63 141 _MAX_MESSAGE_LENGTH: T.ClassVar[int] = 255 142 143 @T.override 144 def start(self) -> None: 145 pass 146 147 def notify(self, title: str, message: str) -> None: 148 """Send a notification to the system. 149 150 Args: 151 title (str): title of the notification. 152 message (str): message of the notification. 153 """ 154 if self.enable: 155 notify_title, notify_message, app_name = title, message, self.app_name 156 if sys.platform == "win32": 157 notify_title = self._truncate(title, self._MAX_TITLE_LENGTH) 158 notify_message = self._truncate(message, self._MAX_MESSAGE_LENGTH) 159 app_name = self._truncate(app_name, self._MAX_APP_NAME_LENGTH) 160 try: 161 # plyer warns while it probes for a backend (dbus, notify-send): the 162 # NotImplementedError fallback below already reports a missing one. 163 with warnings.catch_warnings(): 164 warnings.filterwarnings(action="ignore", category=UserWarning, module="plyer") 165 notification.notify( 166 title=notify_title, 167 message=notify_message, 168 app_name=app_name, 169 timeout=self.timeout, 170 ) 171 except NotImplementedError: 172 print("Notifications are not supported on this system.") # noqa: T201 # user-facing fallback 173 self._print(title=title, message=message) 174 else: 175 self._print(title=title, message=message) 176 177 @staticmethod 178 def _truncate(value: str, max_length: int) -> str: 179 """Fit a Windows field in UTF-16 units without splitting a surrogate pair.""" 180 encoded = value.encode("utf-16-le") 181 if len(encoded) <= max_length * 2: 182 return value 183 prefix = encoded[: (max_length - 1) * 2].decode("utf-16-le", errors="ignore") 184 return f"{prefix}\N{HORIZONTAL ELLIPSIS}" 185 186 def _print(self, title: str, message: str) -> None: 187 """Print a notification to the system. 188 189 Args: 190 title (str): title of the notification. 191 message (str): message of the notification. 192 """ 193 print(f"[{self.app_name}] {title}: {message}") # noqa: T201 # user-facing fallback
Service for sending notifications.
Require libnotify-bin on Linux systems.
In production, use with Slack, Discord, or emails.
https://plyer.readthedocs.io/en/latest/api.html#plyer.facades.Notification
Arguments:
- enable (bool): use notifications or print.
- app_name (str): name of the application.
- timeout (int | None): timeout in secs.
147 def notify(self, title: str, message: str) -> None: 148 """Send a notification to the system. 149 150 Args: 151 title (str): title of the notification. 152 message (str): message of the notification. 153 """ 154 if self.enable: 155 notify_title, notify_message, app_name = title, message, self.app_name 156 if sys.platform == "win32": 157 notify_title = self._truncate(title, self._MAX_TITLE_LENGTH) 158 notify_message = self._truncate(message, self._MAX_MESSAGE_LENGTH) 159 app_name = self._truncate(app_name, self._MAX_APP_NAME_LENGTH) 160 try: 161 # plyer warns while it probes for a backend (dbus, notify-send): the 162 # NotImplementedError fallback below already reports a missing one. 163 with warnings.catch_warnings(): 164 warnings.filterwarnings(action="ignore", category=UserWarning, module="plyer") 165 notification.notify( 166 title=notify_title, 167 message=notify_message, 168 app_name=app_name, 169 timeout=self.timeout, 170 ) 171 except NotImplementedError: 172 print("Notifications are not supported on this system.") # noqa: T201 # user-facing fallback 173 self._print(title=title, message=message) 174 else: 175 self._print(title=title, message=message)
Send a notification to the system.
Arguments:
- title (str): title of the notification.
- message (str): message of the notification.
71class LoggerService(Service): 72 """Service for logging messages. 73 74 https://loguru.readthedocs.io/en/stable/api/logger.html 75 76 Parameters: 77 sink (str): logging output. 78 level (str): logging level. 79 format (str): logging format. 80 colorize (bool): colorize output. 81 serialize (bool): convert to JSON. 82 backtrace (bool): enable exception trace. 83 diagnose (bool): enable variable display. 84 catch (bool): catch errors during log handling. 85 """ 86 87 sink: str = "stderr" 88 level: str = "DEBUG" 89 format: str = ( 90 "<green>[{time:YYYY-MM-DD HH:mm:ss.SSS}]</green>" 91 "<level>[{level}]</level>" 92 "<cyan>[{name}:{function}:{line}]</cyan>" 93 " <level>{message}</level>" 94 ) 95 colorize: bool = True 96 serialize: bool = False 97 backtrace: bool = True 98 diagnose: bool = False 99 catch: bool = True 100 101 @T.override 102 def start(self) -> None: 103 loguru.logger.remove() 104 config = self.model_dump() 105 # use standard sinks or keep the original 106 sinks = {"stderr": sys.stderr, "stdout": sys.stdout} 107 config["sink"] = sinks.get(config["sink"], config["sink"]) 108 loguru.logger.add(**config) 109 110 def logger(self) -> Logger: 111 """Return the main logger. 112 113 Returns: 114 Logger: the main logger. 115 """ 116 return loguru.logger
Service for logging messages.
https://loguru.readthedocs.io/en/stable/api/logger.html
Arguments:
- sink (str): logging output.
- level (str): logging level.
- format (str): logging format.
- colorize (bool): colorize output.
- serialize (bool): convert to JSON.
- backtrace (bool): enable exception trace.
- diagnose (bool): enable variable display.
- catch (bool): catch errors during log handling.
101 @T.override 102 def start(self) -> None: 103 loguru.logger.remove() 104 config = self.model_dump() 105 # use standard sinks or keep the original 106 sinks = {"stderr": sys.stderr, "stdout": sys.stdout} 107 config["sink"] = sinks.get(config["sink"], config["sink"]) 108 loguru.logger.add(**config)
Start the service.
196class MlflowService(Service): 197 """Service for Mlflow tracking and registry. 198 199 Parameters: 200 tracking_uri (str): the URI for the Mlflow tracking server. 201 registry_uri (str): the URI for the Mlflow model registry. 202 experiment_name (str): the name of tracking experiment. 203 registry_name (str): the name of model registry. 204 autolog_disable (bool): disable autologging. 205 autolog_disable_for_unsupported_versions (bool): disable autologging for unsupported versions. 206 autolog_exclusive (bool): If True, enables exclusive autologging. 207 autolog_log_input_examples (bool): If True, logs input examples during autologging. 208 autolog_log_model_signatures (bool): If True, logs model signatures during autologging. 209 autolog_log_models (bool): If True, enables logging of models during autologging. 210 autolog_log_datasets (bool): If True, logs datasets used during autologging. 211 autolog_silent (bool): If True, suppresses all Mlflow warnings during autologging. 212 """ 213 214 class RunConfig(pdt.BaseModel, strict=True, frozen=True, extra="forbid"): 215 """Run configuration for Mlflow tracking. 216 217 Parameters: 218 name (str): name of the run. 219 description (str | None): description of the run. 220 tags (dict[str, T.Any] | None): tags for the run. 221 log_system_metrics (bool | None): enable system metrics logging. 222 """ 223 224 name: str 225 description: str | None = None 226 tags: dict[str, T.Any] | None = None 227 log_system_metrics: bool | None = True 228 229 # server uri 230 # SQLAlchemy backends are the supported store in MLflow 3: SQLite gives the local 231 # setup the same shape as a production database (Postgres, MySQL) with no server to 232 # run. The legacy file store is in maintenance mode and disabled by default. 233 # Explicit YAML wins; otherwise honor MLflow's environment at construction time. 234 tracking_uri: str = pdt.Field(default_factory=lambda: os.environ.get("MLFLOW_TRACKING_URI", "sqlite:///mlflow.db")) 235 registry_uri: str = pdt.Field( 236 default_factory=lambda data: os.environ.get("MLFLOW_REGISTRY_URI", data["tracking_uri"]) 237 ) 238 # experiment 239 experiment_name: str = "bikes" 240 # registry 241 registry_name: str = "bikes" 242 # autolog 243 autolog_disable: bool = False 244 autolog_disable_for_unsupported_versions: bool = False 245 autolog_exclusive: bool = False 246 autolog_log_input_examples: bool = True 247 autolog_log_model_signatures: bool = True 248 autolog_log_models: bool = False 249 autolog_log_datasets: bool = False 250 autolog_silent: bool = False 251 252 @T.override 253 def __repr_args__(self) -> T.Iterator[tuple[str | None, T.Any]]: 254 # jobs log their services: never print the credentials of a database URI 255 for key, value in super().__repr_args__(): 256 yield key, redact_uri(value) if key is not None and key.endswith("_uri") else value 257 258 @T.override 259 def start(self) -> None: 260 # server uri 261 mlflow.set_tracking_uri(uri=self.tracking_uri) 262 mlflow.set_registry_uri(uri=self.registry_uri) 263 # experiment 264 mlflow.set_experiment(experiment_name=self.experiment_name) 265 # autolog 266 mlflow.autolog( 267 disable=self.autolog_disable, 268 disable_for_unsupported_versions=self.autolog_disable_for_unsupported_versions, 269 exclusive=self.autolog_exclusive, 270 log_input_examples=self.autolog_log_input_examples, 271 log_model_signatures=self.autolog_log_model_signatures, 272 log_models=self.autolog_log_models, 273 log_datasets=self.autolog_log_datasets, 274 silent=self.autolog_silent, 275 ) 276 277 @ctx.contextmanager 278 def run_context(self, run_config: RunConfig) -> T.Generator[mlflow.ActiveRun]: 279 """Yield an active Mlflow run and exit it afterwards. 280 281 Args: 282 run_config (RunConfig): mlflow run parameters. 283 284 Yields: 285 T.Generator[mlflow.ActiveRun, None, None]: active run context. Will be closed at the end of context. 286 """ 287 with mlflow.start_run( 288 run_name=run_config.name, 289 tags=run_config.tags, 290 description=run_config.description, 291 log_system_metrics=run_config.log_system_metrics, 292 ) as run: 293 yield run 294 295 def client(self) -> mt.MlflowClient: 296 """Return a new Mlflow client. 297 298 Returns: 299 MlflowClient: the mlflow client. 300 """ 301 return mt.MlflowClient(tracking_uri=self.tracking_uri, registry_uri=self.registry_uri)
Service for Mlflow tracking and registry.
Arguments:
- tracking_uri (str): the URI for the Mlflow tracking server.
- registry_uri (str): the URI for the Mlflow model registry.
- experiment_name (str): the name of tracking experiment.
- registry_name (str): the name of model registry.
- autolog_disable (bool): disable autologging.
- autolog_disable_for_unsupported_versions (bool): disable autologging for unsupported versions.
- autolog_exclusive (bool): If True, enables exclusive autologging.
- autolog_log_input_examples (bool): If True, logs input examples during autologging.
- autolog_log_model_signatures (bool): If True, logs model signatures during autologging.
- autolog_log_models (bool): If True, enables logging of models during autologging.
- autolog_log_datasets (bool): If True, logs datasets used during autologging.
- autolog_silent (bool): If True, suppresses all Mlflow warnings during autologging.
258 @T.override 259 def start(self) -> None: 260 # server uri 261 mlflow.set_tracking_uri(uri=self.tracking_uri) 262 mlflow.set_registry_uri(uri=self.registry_uri) 263 # experiment 264 mlflow.set_experiment(experiment_name=self.experiment_name) 265 # autolog 266 mlflow.autolog( 267 disable=self.autolog_disable, 268 disable_for_unsupported_versions=self.autolog_disable_for_unsupported_versions, 269 exclusive=self.autolog_exclusive, 270 log_input_examples=self.autolog_log_input_examples, 271 log_model_signatures=self.autolog_log_model_signatures, 272 log_models=self.autolog_log_models, 273 log_datasets=self.autolog_log_datasets, 274 silent=self.autolog_silent, 275 )
Start the service.
277 @ctx.contextmanager 278 def run_context(self, run_config: RunConfig) -> T.Generator[mlflow.ActiveRun]: 279 """Yield an active Mlflow run and exit it afterwards. 280 281 Args: 282 run_config (RunConfig): mlflow run parameters. 283 284 Yields: 285 T.Generator[mlflow.ActiveRun, None, None]: active run context. Will be closed at the end of context. 286 """ 287 with mlflow.start_run( 288 run_name=run_config.name, 289 tags=run_config.tags, 290 description=run_config.description, 291 log_system_metrics=run_config.log_system_metrics, 292 ) as run: 293 yield run
Yield an active Mlflow run and exit it afterwards.
Arguments:
- run_config (RunConfig): mlflow run parameters.
Yields:
T.Generator[mlflow.ActiveRun, None, None]: active run context. Will be closed at the end of context.
295 def client(self) -> mt.MlflowClient: 296 """Return a new Mlflow client. 297 298 Returns: 299 MlflowClient: the mlflow client. 300 """ 301 return mt.MlflowClient(tracking_uri=self.tracking_uri, registry_uri=self.registry_uri)
Return a new Mlflow client.
Returns:
MlflowClient: the mlflow client.
214 class RunConfig(pdt.BaseModel, strict=True, frozen=True, extra="forbid"): 215 """Run configuration for Mlflow tracking. 216 217 Parameters: 218 name (str): name of the run. 219 description (str | None): description of the run. 220 tags (dict[str, T.Any] | None): tags for the run. 221 log_system_metrics (bool | None): enable system metrics logging. 222 """ 223 224 name: str 225 description: str | None = None 226 tags: dict[str, T.Any] | None = None 227 log_system_metrics: bool | None = True
Run configuration for Mlflow tracking.
Arguments:
- name (str): name of the run.
- description (str | None): description of the run.
- tags (dict[str, T.Any] | None): tags for the run.
- log_system_metrics (bool | None): enable system metrics logging.
55class Service(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"): 56 """Base class for a global service. 57 58 Use services to manage global contexts. 59 e.g., logger object, mlflow client, spark context, ... 60 """ 61 62 @abc.abstractmethod 63 def start(self) -> None: 64 """Start the service.""" 65 66 def stop(self) -> None: 67 """Stop the service.""" 68 # does nothing by default
Base class for a global service.
Use services to manage global contexts. e.g., logger object, mlflow client, spark context, ...
34def redact_uri(uri: str) -> str: 35 """Hide the password of a URI before it reaches the logs. 36 37 e.g., "postgresql://user:secret@host/db" becomes "postgresql://user:***@host/db". 38 39 Args: 40 uri (str): URI that may embed credentials. 41 42 Returns: 43 str: the same URI without its password. 44 """ 45 parts = urllib.parse.urlsplit(uri) 46 if parts.password is None: 47 return uri 48 _, _, host = parts.netloc.rpartition("@") 49 return parts._replace(netloc=f"{parts.username}:***@{host}").geturl()
Hide the password of a URI before it reaches the logs.
e.g., "postgresql://user:secret@host/db" becomes "postgresql://user:***@host/db".
Arguments:
- uri (str): URI that may embed credentials.
Returns:
str: the same URI without its password.