Skip to content

Commit

Permalink
Adding handling for namespace repo defaults (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
djpugh authored Feb 25, 2024
1 parent 31c3a45 commit f3769f1
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 24 deletions.
12 changes: 8 additions & 4 deletions src/nskit/vcs/codebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@
class Codebase(BaseConfiguration):
"""Object for managing a codebase."""
root_dir: Path = Field(default_factory=Path.cwd)
settings: Annotated[CodebaseSettings, Field(validate_default=True)] = None
namespaces_dir: Path = Path('.namespaces')
settings: Annotated[CodebaseSettings, Field(validate_default=True)] = None
namespace_validation_repo: Optional[NamespaceValidationRepo] = None

@field_validator('settings', mode='before')
@classmethod
def _validate_settings(cls, value):
def _validate_settings(cls, value, info: ValidationInfo):
if value is None:
value = CodebaseSettings()
namespace_validation_repo = None
if (info.data.get('root_dir')/info.data.get('namespaces_dir')).exists():
# Namespaces repo exists
namespace_validation_repo = NamespaceValidationRepo(local_dir=info.data.get('root_dir')/info.data.get('namespaces_dir'))
value = CodebaseSettings(namespace_validation_repo=namespace_validation_repo)
return value

@field_validator('namespace_validation_repo', mode='before')
Expand Down Expand Up @@ -157,7 +161,7 @@ def create_namespace_repo(
self.namespace_validation_repo = NamespaceValidationRepo(
name=name,
namespaces_filename=namespaces_filename,
local_dir=self.namespaces_dir
local_dir=self.root_dir/self.namespaces_dir
)
self.namespace_validation_repo.create(
namespace_options=namespace_options,
Expand Down
6 changes: 3 additions & 3 deletions src/nskit/vcs/namespace_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@

class ValidationEnum(Enum):
"""Enum for validation level."""
strict = 2
warn = 1
none = 0
strict = '2'
warn = '1'
none = '0'


class NamespaceValidator(BaseConfiguration):
Expand Down
8 changes: 1 addition & 7 deletions src/nskit/vcs/providers/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,8 @@

class GithubRepoSettings(BaseConfiguration):
"""Github Repo settings."""
model_config = SettingsConfigDict(env_prefix='GITHUB_REPO', env_file='.env')

# # This is not ideal behaviour, but due to the issue highlighted in
# # https://github.com/pydantic/pydantic-settings/issues/245 and the
# # non-semver compliant versioning in pydantic-settings, we need to add this behaviour
# # this now changes the API behaviour for these objects as they will
# # also ignore additional inputs in the python initialisation
# # We will pin to version < 2.1.0 instead of allowing 2.2.0+ as it requires the code below:
# model_config = ConfigDict(extra='ignore') noqa: E800
private: bool = True
has_issues: Optional[bool] = None
has_wiki: Optional[bool] = None
Expand Down
2 changes: 1 addition & 1 deletion src/nskit/vcs/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def validate_name(self, proposed_name: str):
@classmethod
def _validate_local_dir(cls, value: Any, info: ValidationInfo):
if value is None:
value = Path(tempfile.tempdir)/info.data['name']
value = Path(tempfile.gettempdir())/info.data['name']
return value

def _download_namespaces(self):
Expand Down
18 changes: 15 additions & 3 deletions src/nskit/vcs/settings.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
"""Codebase Settings."""
from __future__ import annotations

from pathlib import Path
import sys
from typing import Optional
from typing import Optional, Union

if sys.version_info.major <= 3 and sys.version_info.minor <= 8:
from typing_extensions import Annotated
else:
from typing import Annotated

from pydantic import Field, field_validator, ValidationError
from pydantic import Field, field_validator, model_validator, ValidationError
from pydantic_settings import SettingsConfigDict

from nskit._logging import logger_factory
Expand All @@ -34,7 +35,7 @@ class CodebaseSettings(BaseConfiguration):

default_branch: str = 'main'
vcs_provider: Annotated[ProviderEnum, Field(validate_default=True)] = None
namespace_validation_repo: Optional[NamespaceValidationRepo] = None
namespace_validation_repo: Optional[Union[NamespaceValidationRepo, str]] = None
validation_level: ValidationEnum = ValidationEnum.none
_provider_settings = None

Expand All @@ -59,6 +60,17 @@ def _validate_vcs_provider(cls, value):
raise ValueError('Extension Not Found')
return value

@model_validator(mode='after')
def _validate_namespace_validation_repo_default(self):
if self.namespace_validation_repo is None and Path('.namespaces').exists():
# Can we handle the root pathing
self.namespace_validation_repo = '.namespaces'
if isinstance(self.namespace_validation_repo, str):
self.namespace_validation_repo = NamespaceValidationRepo(name=self.namespace_validation_repo,
local_dir=Path(self.namespace_validation_repo),
provider_client=self.provider_settings.repo_client)
return self

@property
def provider_settings(self) -> VCSProviderSettings:
"""Get the instantiated provider settings."""
Expand Down
36 changes: 31 additions & 5 deletions tests/unit/test_vcs/test_codebase.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import call, create_autospec, MagicMock, patch

Expand Down Expand Up @@ -33,8 +33,9 @@ def repo_client(self2):
def extension(self):
return TestExtension('dummy', ENTRYPOINT, self._provider_settings_cls)

def env(self):
return Env(override={'TEST_ABACUS': 'A'})
def env(self, **override):
override.update({'TEST_ABACUS': 'A'})
return Env(override=override)

@patch.object(RepoClient, '__abstractmethods__', set())
def test_init_no_settings_error(self):
Expand Down Expand Up @@ -106,6 +107,31 @@ def test_validation_namespace_local_dir(self):
self.assertEqual(c.namespace_validation_repo.name, 'abc')
self.assertEqual(c.namespace_validation_repo.local_dir, Path.cwd()/'a'/'.namespaces2')

@patch.object(RepoClient, '__abstractmethods__', set())
def test_init_namespace_validation_repo_from_env_default(self):
with ChDir(): # Make sure theres no .env file when running tests
# Create the namespaces dir
Path('.namespaces23').mkdir(parents=True)
nsv = NamespaceValidator(options=[{'a': ['b', 'c']}])
Path('.namespaces23/namespaces.yaml').write_text(nsv.model_dump_yaml())
with self.extension():
with self.env(NSKIT_VCS_CODEBASE_NAMESPACES_DIR='.namespaces23'):
settings.ProviderEnum._patch()
c = Codebase(namespaces_dir='.namespaces23')
self.assertIsInstance(c.namespace_validation_repo, NamespaceValidationRepo)
self.assertEqual(c.namespace_validation_repo.name, '.namespaces')
self.assertEqual(c.namespace_validation_repo.local_dir.name, '.namespaces23')

@patch.object(RepoClient, '__abstractmethods__', set())
def test_init_namespace_validation_repo_from_env_missing(self):
with ChDir(): # Make sure theres no .env file when running tests
# Create the namespaces dir
with self.extension():
with self.env(NSKIT_VCS_CODEBASE_NAMESPACES_DIR='.namespaces'):
settings.ProviderEnum._patch()
c = Codebase()
self.assertIsNone(c.namespace_validation_repo, NamespaceValidationRepo)

@patch.object(RepoClient, '__abstractmethods__', set())
def test_namespace_validator_provided(self):
r = NamespaceValidationRepo(name='abc', provider_client=RepoClient())
Expand Down Expand Up @@ -445,7 +471,7 @@ def test_create_namespace_repo_name(self, nsv_rp):
options = [{'a': ['b', 'c']}, 'd']
c.create_namespace_repo(name='test-namespaces', namespace_options=options)
self.assertIsNotNone(c.namespace_validation_repo)
nsv_rp.assert_called_once_with(name='test-namespaces', namespaces_filename='namespaces.yaml', local_dir=Path('.namespaces'))
nsv_rp.assert_called_once_with(name='test-namespaces', namespaces_filename='namespaces.yaml', local_dir=c.root_dir/'.namespaces')
c.namespace_validation_repo.create.assert_called_once_with(namespace_options=options, delimiters=None, repo_separator=None)

@patch.object(codebase, 'NamespaceValidationRepo', autospec=True)
Expand All @@ -460,6 +486,6 @@ def test_create_namespace_repo_no_name(self, nsv_rp):
options = [{'a': ['b', 'c']}, 'd']
c.create_namespace_repo(namespace_options=options)
self.assertIsNotNone(c.namespace_validation_repo)
nsv_rp.assert_called_once_with(name='.namespaces2', namespaces_filename='namespaces.yaml', local_dir=Path('.namespaces2'))
nsv_rp.assert_called_once_with(name='.namespaces2', namespaces_filename='namespaces.yaml', local_dir=c.root_dir/'.namespaces2')
c.namespace_validation_repo.create.assert_called_once_with(namespace_options=options, delimiters=None, repo_separator=None)

92 changes: 91 additions & 1 deletion tests/unit/test_vcs/test_settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

from pathlib import Path
import sys
import unittest
from unittest.mock import call, create_autospec, MagicMock, patch
import uuid

if sys.version_info.major >= 3 and sys.version_info.minor >= 9:
Expand All @@ -12,16 +14,49 @@
from pydantic import Field, ValidationError
from pydantic_settings import SettingsConfigDict

from nskit.common.contextmanagers import Env, TestExtension
from nskit.common.contextmanagers import ChDir, Env, TestExtension
from nskit.common.extensions import ExtensionsEnum
from nskit.vcs import settings
from nskit.vcs.namespace_validator import NamespaceValidator
from nskit.vcs.providers import ENTRYPOINT
from nskit.vcs.providers.abstract import VCSProviderSettings
from nskit.vcs.repo import NamespaceValidationRepo, RepoClient
from nskit.vcs.settings import CodebaseSettings


class VCSSettingsTestCase(unittest.TestCase):

def setUp(self):

self._MockedRepoClientKls = create_autospec(RepoClient)
self._mocked_repo_client = self._MockedRepoClientKls()

class DummyVCSProviderSettings(VCSProviderSettings):

test_abacus: str

@property
def repo_client(self2):
return self._mocked_repo_client

self._provider_settings_cls = DummyVCSProviderSettings
self._entrypoint = f'nskit.vcs.providers.e{uuid.uuid4()}'

def extension(self, solo=True):
return TestExtension('dummy', self._entrypoint, self._provider_settings_cls, solo=solo)

def patched_settings(self):
PatchedProviderEnum = ExtensionsEnum.from_entrypoint('PatchedProviderEnum', self._entrypoint)

class PatchedSettings(CodebaseSettings):
model_config = SettingsConfigDict(env_file=None)
vcs_provider: Annotated[PatchedProviderEnum, Field(validate_default=True)] = None

return PatchedSettings

def env(self, **override):
override.update({'TEST_ABACUS': 'A'})
return Env(override=override)

def test_default_init_failed(self):

Expand Down Expand Up @@ -51,6 +86,61 @@ class PatchedSettings(CodebaseSettings):
with self.assertRaises(ValidationError):
PatchedSettings(model_config=dict(env_file=f'{uuid.uuid4()}.env'))

def test_namespaces_validation_repo_from_str(self):

with ChDir(): # Make sure theres no .env file when running tests
# Create the namespaces dir
Path('.namespaces23').mkdir(parents=True)
nsv = NamespaceValidator(options=[{'a': ['b', 'c']}])
Path('.namespaces23/namespaces.yaml').write_text(nsv.model_dump_yaml())
with self.extension():
PatchedSettings = self.patched_settings()
with Env(override={'TEST_ABACUS': 'A'}):
s = PatchedSettings(namespace_validation_repo='.namespaces23')
self.assertIsInstance(s.namespace_validation_repo, NamespaceValidationRepo)
self.assertEqual(s.namespace_validation_repo.local_dir.name, '.namespaces23')

def test_namespaces_validation_repo_from_env(self):

with ChDir(): # Make sure theres no .env file when running tests
# Create the namespaces dir
Path('.namespaces23').mkdir(parents=True)
nsv = NamespaceValidator(options=[{'a': ['b', 'c']}])
Path('.namespaces23/namespaces.yaml').write_text(nsv.model_dump_yaml())
with self.extension():
PatchedSettings = self.patched_settings()
with Env(override={'TEST_ABACUS': 'A', 'NSKIT_VCS_CODEBASE_NAMESPACE_VALIDATION_REPO': '.namespaces23'}):
s = PatchedSettings()
self.assertIsInstance(s.namespace_validation_repo, NamespaceValidationRepo)
self.assertEqual(s.namespace_validation_repo.local_dir.name, '.namespaces23')

def test_namespaces_validation_repo_not_exists(self):

with ChDir(): # Make sure theres no .env file when running tests
# Create the namespaces dir
Path('.namespaces23').mkdir(parents=True)
nsv = NamespaceValidator(options=[{'a': ['b', 'c']}])
Path('.namespaces23/namespaces.yaml').write_text(nsv.model_dump_yaml())
with self.extension():
PatchedSettings = self.patched_settings()
with Env(override={'TEST_ABACUS': 'A'}):
s = PatchedSettings()
self.assertIsNone(s.namespace_validation_repo)

def test_namespaces_validation_repo_from_default(self):

with ChDir(): # Make sure theres no .env file when running tests
# Create the namespaces dir
Path('.namespaces').mkdir(parents=True)
nsv = NamespaceValidator(options=[{'a': ['b', 'c']}])
Path('.namespaces/namespaces.yaml').write_text(nsv.model_dump_yaml())
with self.extension():
PatchedSettings = self.patched_settings()
with Env(override={'TEST_ABACUS': 'A'}):
s = PatchedSettings()
self.assertIsInstance(s.namespace_validation_repo, NamespaceValidationRepo)
self.assertEqual(s.namespace_validation_repo.local_dir.name, '.namespaces')


def test_default_init_test_with_valid_provider_found(self):

Expand Down

0 comments on commit f3769f1

Please sign in to comment.