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