Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More code repository improvements #3327

Merged
merged 12 commits into from
Jan 30, 2025
26 changes: 26 additions & 0 deletions src/zenml/cli/code_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,32 @@ def register_code_repository(
cli_utils.declare(f"Successfully registered code repository `{name}`.")


@code_repository.command("describe", help="Describe a code repository.")
@click.argument(
"name_id_or_prefix",
type=str,
required=True,
)
def describe_code_repository(name_id_or_prefix: str) -> None:
"""Describe a code repository.

Args:
name_id_or_prefix: Name, ID or prefix of the code repository.
"""
client = Client()
try:
code_repository = client.get_code_repository(
name_id_or_prefix=name_id_or_prefix,
)
except KeyError as err:
cli_utils.error(str(err))
else:
cli_utils.print_pydantic_model(
title=f"Code repository '{code_repository.name}'",
model=code_repository,
)


@code_repository.command("list", help="List all connected code repositories.")
@list_options(CodeRepositoryFilter)
def list_code_repositories(**kwargs: Any) -> None:
Expand Down
9 changes: 2 additions & 7 deletions src/zenml/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
Union,
cast,
)
from uuid import UUID, uuid4
from uuid import UUID

from pydantic import ConfigDict, SecretStr

Expand Down Expand Up @@ -4980,12 +4980,7 @@ def _validate_code_repository_config(
)
)
try:
# This does a login to verify the credentials
code_repo_class(id=uuid4(), config=config)

# Explicitly access the config for pydantic validation, in case
# the login for some reason did not do that.
_ = code_repo_class.config
code_repo_class.validate_config(config)
except Exception as e:
raise RuntimeError(
"Failed to validate code repository config."
Expand Down
18 changes: 17 additions & 1 deletion src/zenml/code_repositories/base_code_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Type
from uuid import UUID
from uuid import UUID, uuid4

from zenml.config.secret_reference_mixin import SecretReferenceMixin
from zenml.logger import get_logger
Expand Down Expand Up @@ -82,6 +82,22 @@ def from_model(cls, model: CodeRepositoryResponse) -> "BaseCodeRepository":
)
return class_(id=model.id, config=model.config)

@classmethod
def validate_config(cls, config: Dict[str, Any]) -> None:
"""Validate the code repository config.

This method should check that the config/credentials are valid and
the configured repository exists.

Args:
config: The configuration.
"""
# The initialization calls the login to verify the credentials
code_repo = cls(id=uuid4(), config=config)

# Explicitly access the config for pydantic validation
_ = code_repo.config

@property
def id(self) -> UUID:
"""ID of the code repository.
Expand Down
18 changes: 15 additions & 3 deletions src/zenml/code_repositories/git/local_git_repository_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
from zenml.code_repositories import (
LocalRepositoryContext,
)
from zenml.constants import (
ENV_ZENML_CODE_REPOSITORY_IGNORE_UNTRACKED_FILES,
handle_bool_env_var,
)
from zenml.logger import get_logger

if TYPE_CHECKING:
Expand Down Expand Up @@ -70,11 +74,13 @@
from git.exc import InvalidGitRepositoryError
from git.repo.base import Repo
except ImportError:
logger.debug("Failed to import git library.")
return None

try:
git_repo = Repo(path=path, search_parent_directories=True)
except InvalidGitRepositoryError:
logger.debug("No git repository exists at path %s.", path)
return None

remote_name = None
Expand Down Expand Up @@ -124,13 +130,19 @@
def is_dirty(self) -> bool:
"""Whether the git repo is dirty.

A repository counts as dirty if it has any untracked or uncommitted
changes.
By defauly, a repository counts as dirty if it has any untracked or

Check warning on line 133 in src/zenml/code_repositories/git/local_git_repository_context.py

View workflow job for this annotation

GitHub Actions / spellcheck

"defauly" should be "default".

Check warning on line 133 in src/zenml/code_repositories/git/local_git_repository_context.py

View workflow job for this annotation

GitHub Actions / spellcheck

"defauly" should be "default".
uncommitted changes. Users can use an environment variable to ignore
untracked files.

Returns:
True if the git repo is dirty, False otherwise.
"""
return self.git_repo.is_dirty(untracked_files=True)
ignore_untracked_files = handle_bool_env_var(
ENV_ZENML_CODE_REPOSITORY_IGNORE_UNTRACKED_FILES, default=False
)
return self.git_repo.is_dirty(
untracked_files=not ignore_untracked_files
)

@property
def has_local_changes(self) -> bool:
Expand Down
3 changes: 3 additions & 0 deletions src/zenml/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ def handle_int_env_var(var: str, default: int = 0) -> int:
ENV_ZENML_PIPELINE_RUN_API_TOKEN_EXPIRATION = (
"ZENML_PIPELINE_API_TOKEN_EXPIRATION"
)
ENV_ZENML_CODE_REPOSITORY_IGNORE_UNTRACKED_FILES = (
"ZENML_CODE_REPOSITORY_IGNORE_UNTRACKED_FILES"
)

# Materializer environment variables
ENV_ZENML_MATERIALIZER_ALLOW_NON_ASCII_JSON_DUMPS = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

import os
import re
from typing import List, Optional
from typing import Any, Dict, List, Optional
from uuid import uuid4

import requests
from github import Consts, Github, GithubException
Expand Down Expand Up @@ -62,6 +63,20 @@ class GitHubCodeRepositoryConfig(BaseCodeRepositoryConfig):
class GitHubCodeRepository(BaseCodeRepository):
"""GitHub code repository."""

@classmethod
def validate_config(cls, config: Dict[str, Any]) -> None:
"""Validate the code repository config.

This method should check that the config/credentials are valid and
the configured repository exists.

Args:
config: The configuration.
"""
code_repo = cls(id=uuid4(), config=config)
# Try to access the project to make sure it exists
_ = code_repo.github_repo
schustmi marked this conversation as resolved.
Show resolved Hide resolved

@property
def config(self) -> GitHubCodeRepositoryConfig:
"""Returns the `GitHubCodeRepositoryConfig` config.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

import os
import re
from typing import Optional
from typing import Any, Dict, Optional
from uuid import uuid4

from gitlab import Gitlab
from gitlab.v4.objects import Project
Expand Down Expand Up @@ -63,6 +64,20 @@ class GitLabCodeRepositoryConfig(BaseCodeRepositoryConfig):
class GitLabCodeRepository(BaseCodeRepository):
"""GitLab code repository."""

@classmethod
def validate_config(cls, config: Dict[str, Any]) -> None:
"""Validate the code repository config.

This method should check that the config/credentials are valid and
the configured repository exists.

Args:
config: The configuration.
"""
code_repo = cls(id=uuid4(), config=config)
# Try to access the project to make sure it exists
_ = code_repo.gitlab_project

@property
def config(self) -> GitLabCodeRepositoryConfig:
"""Returns the `GitLabCodeRepositoryConfig` config.
Expand Down
4 changes: 2 additions & 2 deletions src/zenml/pipelines/pipeline_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,8 @@ def _create_deployment(
deployment=deployment, stack=stack
)

local_repo_context = (
code_repository_utils.find_active_code_repository()
local_repo_context = code_repository_utils.find_active_code_repository(
log=True
)
code_repository = build_utils.verify_local_repository_context(
deployment=deployment, local_repo_context=local_repo_context
Expand Down
11 changes: 10 additions & 1 deletion src/zenml/utils/code_repository_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ def set_custom_local_repository(

def find_active_code_repository(
path: Optional[str] = None,
log: bool = False,
) -> Optional["LocalRepositoryContext"]:
"""Find the active code repository for a given path.

Args:
path: Path at which to look for the code repository. If not given, the
source root will be used.
log: Log a message if there was no matching code repository.

Returns:
The local repository context active at that path or None.
Expand All @@ -106,7 +108,8 @@ def find_active_code_repository(
return _CODE_REPOSITORY_CACHE[path]

local_context: Optional["LocalRepositoryContext"] = None
for model in depaginate(list_method=Client().list_code_repositories):
code_repositories = depaginate(list_method=Client().list_code_repositories)
for model in code_repositories:
try:
repo = BaseCodeRepository.from_model(model)
except ImportError:
Expand All @@ -125,6 +128,12 @@ def find_active_code_repository(
local_context = repo.get_local_context(path)
if local_context:
break
else:
if code_repositories and log:
# There are registered code repositories, but none was matching the
# current path -> We log the path to help in debugging issues
# related to the source root.
logger.info("No matching code repository found at path %s.", path)
schustmi marked this conversation as resolved.
Show resolved Hide resolved

_CODE_REPOSITORY_CACHE[path] = local_context
return local_context
Loading