Coverage for .nox/test-3-12/lib/python3.12/site-packages/nskit/vcs/codebase.py: 99%
96 statements
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-19 17:42 +0000
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-19 17:42 +0000
1"""Manage a codebase."""
2from __future__ import annotations
4from pathlib import Path
5import sys
6from typing import List, Optional
8if sys.version_info.major <= 3 and sys.version_info.minor <= 8:
9 from typing_extensions import Annotated
10else:
11 from typing import Annotated
13from pydantic import Field, field_validator, ValidationInfo
15from nskit.common.configuration import BaseConfiguration
16from nskit.mixer import Recipe
17from nskit.vcs.namespace_validator import NamespaceValidator
18from nskit.vcs.repo import NamespaceOptionsType, NamespaceValidationRepo, Repo
19from nskit.vcs.settings import CodebaseSettings
22class Codebase(BaseConfiguration):
23 """Object for managing a codebase."""
24 root_dir: Path = Field(default_factory=Path.cwd)
25 settings: Annotated[CodebaseSettings, Field(validate_default=True)] = None
26 namespaces_dir: Path = Path('.namespaces')
27 namespace_validation_repo: Optional[NamespaceValidationRepo] = None
29 @field_validator('settings', mode='before')
30 @classmethod
31 def _validate_settings(cls, value):
32 if value is None:
33 value = CodebaseSettings()
34 return value
36 @field_validator('namespace_validation_repo', mode='before')
37 @classmethod
38 def _validate_namespace_validation_repo_from_settings(cls, value, info: ValidationInfo):
39 if value is None:
40 try:
41 value = info.data.get('settings').namespace_validation_repo
42 except (AttributeError) as e:
43 raise ValueError(e) from None
44 return value
46 @field_validator('namespace_validation_repo', mode='after')
47 @classmethod
48 def _validate_namespace_validation_repo(cls, value, info: ValidationInfo):
49 if value:
50 value.local_dir = info.data.get('root_dir')/info.data.get('namespaces_dir')
51 return value
53 def model_post_init(self, *args, **kwargs):
54 """Set the settings namespace validation repo to the same as the codebase."""
55 super().model_post_init(*args, **kwargs)
56 self.settings.namespace_validation_repo = self.namespace_validation_repo
58 @property
59 def namespace_validator(self):
60 """Get the namespace validator object."""
61 if self.namespace_validation_repo:
62 return self.namespace_validation_repo.validator
63 else:
64 return NamespaceValidator(options=None)
66 def list_repos(self):
67 """Get the repo names that are validated by the namespace_validator if provided."""
68 potential_repos = self.settings.provider_settings.repo_client.list()
69 sdk_repos = []
70 for repo in potential_repos:
71 result, _ = self.namespace_validator.validate_name(repo)
72 if result:
73 sdk_repos.append(repo)
74 return sdk_repos
76 def clone(self):
77 """Clone all repos that match the codebase to a local (nested) directory."""
78 # List repos
79 root = self.root_dir
80 root.mkdir(exist_ok=True, parents=True)
81 repos = self.list_repos()
82 # Create folder structure based on namespacing
83 cloned = []
84 for repo in repos:
85 repo_dir = self.root_dir/Path(*self.namespace_validator.to_parts(repo))
86 r = Repo(
87 name=repo,
88 local_dir=repo_dir,
89 namespace_validation_repo=self.namespace_validation_repo,
90 validation_level=self.settings.validation_level,
91 provider_client=self.settings.provider_settings.repo_client)
92 if not r.exists_locally:
93 r.clone()
94 r.install(codebase=self, deps=False)
95 cloned.append(r)
96 # Once installed all with no deps, install deps again
97 for repo in cloned:
98 repo.install(codebase=self, deps=True)
99 return cloned
101 def create_repo(self, name, with_recipe: Optional[str] = None, **recipe_kwargs):
102 """Create a repo in the codebase.
104 with_recipe will instantiate it with a specific recipe - the kwargs need to be provided to the call.
105 """
106 repo_dir = self.root_dir/Path(*self.namespace_validator.to_parts(name))
107 r = Repo(
108 name=name,
109 local_dir=repo_dir,
110 namespace_validation_repo=self.namespace_validation_repo,
111 validation_level=self.settings.validation_level,
112 provider_client=self.settings.provider_settings.repo_client)
113 if r.exists or r.exists_locally:
114 raise ValueError(f'Repo {name} already exists')
115 r.create()
116 if with_recipe is not None:
117 repo = recipe_kwargs.get('repo', {})
118 repo['url'] = repo.get('url', r.url)
119 repo['repo_separator'] = repo.get('repo_separator', self.namespace_validator.repo_separator)
120 recipe_kwargs['repo'] = repo
121 recipe = Recipe.load(
122 with_recipe,
123 name=repo['repo_separator'].join(self.namespace_validator.to_parts(r.name)),
124 **recipe_kwargs
125 )
126 created = recipe.create(
127 base_path=r.local_dir.parent,
128 override_path=self.namespace_validator.to_parts(r.name)[-1]
129 )
130 r.commit('Initial commit', hooks=False)
131 r.push()
132 r.install(codebase=self, deps=True)
133 return created
135 def delete_repo(self, name):
136 """Delete a repo from the codebase."""
137 repo_dir = self.root_dir/Path(*self.namespace_validator.to_parts(name))
138 r = Repo(
139 name=name,
140 local_dir=repo_dir,
141 namespace_validation_repo=self.namespace_validation_repo,
142 validation_level=self.settings.validation_level,
143 provider_client=self.settings.provider_settings.repo_client)
144 r.delete()
146 def create_namespace_repo(
147 self,
148 name: str | None = None,
149 *,
150 namespace_options: NamespaceOptionsType | NamespaceValidator,
151 delimiters: List[str] | None = None,
152 repo_separator: str | None = None,
153 namespaces_filename: str | Path = 'namespaces.yaml'):
154 """Create and populate the validator repo."""
155 if name is None:
156 name = self.namespaces_dir.name
157 self.namespace_validation_repo = NamespaceValidationRepo(
158 name=name,
159 namespaces_filename=namespaces_filename,
160 local_dir=self.namespaces_dir
161 )
162 self.namespace_validation_repo.create(
163 namespace_options=namespace_options,
164 delimiters=delimiters,
165 repo_separator=repo_separator
166 )