Skip to content

Project

sereto.project

Project dataclass

Source code in sereto/project.py
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
@dataclass
class Project:
    _settings: Settings | None = None
    _project_path: DirectoryPath | None = None
    _config: Config | None = None

    @property
    def settings(self) -> Settings:
        if self._settings is None:
            self._settings = load_settings_function()
        return self._settings

    @property
    def path(self) -> DirectoryPath:
        if self._project_path is None:
            self._project_path = get_project_path_from_dir(dir=Path.cwd(), dir_subtree=self.settings.projects_path)
        return self._project_path

    @property
    def config(self) -> Config:
        if self._config is None:
            self._config = Config.load_from(self.path / "config.json")
        return self._config

    @property
    def config_path(self) -> Path:
        """Get the path to the project configuration file."""
        return self.path / "config.json"

    @classmethod
    def load_from(cls, path: DirectoryPath) -> Self:
        if not is_project_dir(path):
            raise SeretoPathError("not a project directory")
        return cls(_project_path=path)

config_path property

Get the path to the project configuration file.

get_project_path_from_dir(dir=None, dir_subtree=Path('/'))

Get the path to the project directory.

Start the search from the 'dir' directory and go up the directory tree until the project directory is found or 'dir_subtree' is reached.

Parameters:

Name Type Description Default
dir DirectoryPath | None

The directory to start the search from. Defaults to the current working directory.

None
dir_subtree DirectoryPath

The directory to stop the search at. Defaults to the root directory.

Path('/')

Raises:

Type Description
SeretoPathError

If the current working directory is not inside the project's (sub)directory.

Returns:

Type Description
Path

The path to the project directory.

Source code in sereto/project.py
 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
@validate_call
def get_project_path_from_dir(dir: DirectoryPath | None = None, dir_subtree: DirectoryPath = Path("/")) -> Path:
    """Get the path to the project directory.

    Start the search from the 'dir' directory and go up the directory tree until the project directory is
    found or 'dir_subtree' is reached.

    Args:
        dir: The directory to start the search from. Defaults to the current working directory.
        dir_subtree: The directory to stop the search at. Defaults to the root directory.

    Raises:
        SeretoPathError: If the current working directory is not inside the project's (sub)directory.

    Returns:
        The path to the project directory.
    """
    if dir is None:
        dir = Path.cwd()

    # start from the current working directory and go up the directory tree
    for d in [dir] + list(dir.parents):
        # if the current directory is inside the subtree
        if d.is_relative_to(dir_subtree):
            # if the current directory is a project directory
            if is_project_dir(d):
                return d
        else:
            # stop the search before leaving the subtree
            break

    raise SeretoPathError("not inside project's (sub)directory")

init_build_dir(project_path, version_config=None, target=None)

Initialize the build directory.

Source code in sereto/project.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@validate_call
def init_build_dir(
    project_path: DirectoryPath, version_config: VersionConfig | None = None, target: Target | None = None
) -> None:
    """Initialize the build directory."""
    if (version_config is None and target is None) or (version_config is not None and target is not None):
        raise SeretoValueError("either 'version_config' or 'target' must be specified")

    # Create ".build" directory
    if not (build_dir := project_path / ".build").is_dir():
        build_dir.mkdir(parents=True)

    # Create target directories
    targets: list[Target] = [target] if target is not None else version_config.targets  # type: ignore[union-attr]
    for target in targets:
        if not (target_dir := build_dir / target.uname).is_dir():
            target_dir.mkdir(parents=True)

is_project_dir(path)

Check if the provided path is a root directory of a project.

A project directory contains at least .sereto and config.json files.

Parameters:

Name Type Description Default
path Path

The path to examine.

required

Returns:

Type Description
bool

True if the path is a project directory, False otherwise.

Source code in sereto/project.py
60
61
62
63
64
65
66
67
68
69
70
71
72
@validate_call
def is_project_dir(path: Path) -> bool:
    """Check if the provided path is a root directory of a project.

    A project directory contains at least `.sereto` and `config.json` files.

    Args:
        path: The path to examine.

    Returns:
        True if the path is a project directory, False otherwise.
    """
    return path.is_dir() and path.exists() and (path / ".sereto").is_file() and (path / "config.json").is_file()

new_project(projects_path, templates_path, id, name)

Generates a new project with the specified ID.

Parameters:

Name Type Description Default
projects_path DirectoryPath

The path to the projects directory.

required
templates_path DirectoryPath

The path to the templates directory.

required
id TypeProjectId

The ID of the new project. This should be a string that uniquely identifies the project.

required
name str

The name of the new project.

required

Raises:

Type Description
SeretoValueError

If a project with the specified ID already exists in the projects directory.

Source code in sereto/project.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
@validate_call
def new_project(projects_path: DirectoryPath, templates_path: DirectoryPath, id: TypeProjectId, name: str) -> None:
    """Generates a new project with the specified ID.

    Args:
        projects_path: The path to the projects directory.
        templates_path: The path to the templates directory.
        id: The ID of the new project. This should be a string that uniquely identifies the project.
        name: The name of the new project.

    Raises:
        SeretoValueError: If a project with the specified ID already exists in the `projects` directory.
    """
    Console().log(f"Generating a new project with ID {id!r}")

    if (new_path := projects_path / id).exists():
        raise SeretoPathError("project with specified ID already exists")
    else:
        new_path.mkdir()

    sereto_ver = importlib.metadata.version("sereto")

    Console().log("Copy project skeleton")
    copy_skel(templates=templates_path, dst=new_path)

    config_path = new_path / "config.json"

    Console().log(f"Writing the config '{config_path}'")
    Config(
        sereto_version=SeretoVersion.from_str(sereto_ver),
        version_configs={
            ProjectVersion.from_str("v1.0"): VersionConfig(
                version=ProjectVersion.from_str("v1.0"),
                id=id,
                name=name,
                version_description="Initial",
            ),
        },
        path=config_path,
    ).save()

project_create_missing(project_path, version_config)

Creates missing content in the project.

Parameters:

Name Type Description Default
project_path DirectoryPath

The path to the project directory.

required
version_config VersionConfig

Configuration for a specific project version.

required
Source code in sereto/project.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
@validate_call
def project_create_missing(project_path: DirectoryPath, version_config: VersionConfig) -> None:
    """Creates missing content in the project.

    Args:
        project_path: The path to the project directory.
        version_config: Configuration for a specific project version.
    """
    # Initialize the build directory
    init_build_dir(project_path=project_path, version_config=version_config)

    # Make sure that "layouts/generated" directory exists
    if not (layouts_generated := project_path / "layouts" / "generated").is_dir():
        layouts_generated.mkdir(parents=True)

    for target in version_config.targets:
        # Generate risks plot for the target
        risks_plot(risks=target.findings.risks, path=project_path / ".build" / target.uname / "risks.png")