File size: 5,106 Bytes
e515383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
133
134
135
136
137
138
139
140
141
142
143
from pathlib import Path
from typing import Literal, List
from pydantic import BaseModel
from pydantic_settings import BaseSettings, DotEnvSettingsSource, EnvSettingsSource, SettingsConfigDict, PydanticBaseSettingsSource, YamlConfigSettingsSource
import yaml

def join_tag(loader, node):
    """
    Help joining pathes in config.YAML directly.
    """
    parts = loader.construct_sequence(node)
    path = Path(*(str(part) for part in parts)).resolve()
    return str(path)
# It didn't work before, After some research, .SafeLoaded is unmentioned must for my case. 
yaml.SafeLoader.add_constructor("!join", join_tag)


class PathsConfig(BaseModel):
    """Contains paths of directories"""
    project_dir: str
    models_dir: str
    logs_dir: str

class YoloConfig(BaseModel):
    """Contains yolo configurations"""
    model_path: str
    classes: List[str]
    batch_size: int
    epochs: int
    wandb: bool
    augment: bool
    data_path: str

class SecurityDetector(BaseModel):
    "Contains Security Detectors like Smoke - Fire"
    model_path: str
    classes: List[str]

class DepthConfig(BaseModel):
    "Contains depths estimation configurations"
    model_path: str
    encoder: Literal["vits", "vitb", "vitl", "vitg"]

class IntervalsConfig(BaseModel):
    system_metrics_seconds: float
    frames_summary_every: int
    realtime_updates_every: float


class AppConfig(BaseSettings):
    """
    Main app Configuration
    - Gets defaults from config.yaml (via load_config)
    - Override values with .env
    """

    # Note that it doesn't show error, Take care. 
    model_config = SettingsConfigDict(
        env_file=Path(__file__).parent / ".env",
        env_file_encoding="utf-8",
        yaml_file=Path(__file__).parent / "config.yaml",
        # case_sensitive=False,         # default True
        # env_prefix="YOLO_",         # Means configs we are talking about starts with YOLO_
        # env_nested_delimiter="__",  # Means we use _ instead of spaces for the same var
        extra="ignore"              # Ignore other settings in yaml and env as they are not mentioedhere
    )

    project_name:str
    project_desc:str
    task: Literal["indoor", "outdoor"]

    paths: PathsConfig
    yolo: YoloConfig
    security_detector: SecurityDetector
    depth: DepthConfig
    intervals: IntervalsConfig

    # Backend


    @classmethod
    def settings_customise_sources(cls, 
        settings_cls: type[BaseSettings],  # Base param.
        # init_settings: PydanticBaseSettingsSource,   # Values passed to __init__
        # env_settings: PydanticBaseSettingsSource,    # OS Env variables
        # dotenv_settings: PydanticBaseSettingsSource, 
        # file_secret_settings: PydanticBaseSettingsSource # Secret Directories
        **kwargs
        ) -> tuple[PydanticBaseSettingsSource, ...] :
        """
        Once you use this, no need to use load_config, it is already the same.
        But this time it fixs the priority part, order by parameters priority.  
        """

        # Order by priority
        return (
            DotEnvSettingsSource(settings_cls),    # Most important
            EnvSettingsSource(settings_cls),       # This allow for ex. hugging face to override .env values with its values. 
            YamlConfigSettingsSource(settings_cls), 
            )  # The return must be a tuple


    # @classmethod
    # def load_config(cls, yaml_path: Path | str = Path(__file__).parent / "config.yaml") -> "AppConfig":
    #     """Loading confiuration and settings from Config.yaml file then override using .env"""

    #     yaml_path = Path(yaml_path).resolve()  # Absolute path
    #     if not yaml_path.is_file():
    #         raise FileNotFoundError(f"Config file not found: {yaml_path}")

    #     with yaml_path.open("r") as f:
    #         yaml_data = yaml.safe_load(f) or {}

    #     # env_data = cls() # This one loaded .env and not Yaml
    #     # When this project grow, you are going to create different types of .yaml files for products and debugging and so on
    #     # Feel free to stack them here, so we use the yaml required for our testing
    #     # Note that in debuging.yaml file, we only override the base, not starting from scratch. 
    #     # return cls(**{
    #     #     **yaml_data,   # Loading config.yaml configurations
    #     #     # **env_data.model_dump()     # TODO(FIX) Overriding everything using .env
    #     #     })
    #     return cls(**yaml_data)




if __name__ == "__main__":  
    # Note that we must use AppConfig without () to take .env in mind.
    # Checking for YAML part.
    # config = AppConfig.load_config()
    # print(config.model_dump())
    # print(config.model_dump()["project_name"])

    # Checking for .env file. 
    # config = AppConfig()
    # print(f".env Path we are talking about: {Path(__file__).parent / ".env"}")
    # print(config.model_config)
    # print(config.project_name)

    # Trying to checking both yaml and .env.   This works really fine now. 
    config = AppConfig()
    print(config.model_dump())
    print(config.model_dump()["project_name"])