Skip to content

Commit

Permalink
Improve UX (#117)
Browse files Browse the repository at this point in the history
* allow registering smaller versions; fix exceptions style

* print created git tag names in cmd output
  • Loading branch information
aguschin authored Apr 12, 2022
1 parent f9e939a commit 3c397c8
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 151 deletions.
22 changes: 19 additions & 3 deletions gto/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,20 @@ def remove(repo: Union[str, Repo], name: str):


def register(
repo: Union[str, Repo], name: str, ref: str, version: str = None, bump: str = None
repo: Union[str, Repo],
name: str,
ref: str,
version: str = None,
bump: str = None,
stdout: bool = False,
):
"""Register new artifact version"""
return GitRegistry.from_repo(repo).register(
name=name, ref=ref, version=version, bump=bump
name=name,
ref=ref,
version=version,
bump=bump,
stdout=stdout,
)


Expand All @@ -72,10 +81,17 @@ def promote(
promote_ref: str = None,
name_version: str = None,
simple: bool = False,
stdout: bool = False,
):
"""Assign stage to specific artifact version"""
return GitRegistry.from_repo(repo).promote(
name, stage, promote_version, promote_ref, name_version, simple=simple
name,
stage,
promote_version,
promote_ref,
name_version,
simple=simple,
stdout=stdout,
)


Expand Down
12 changes: 9 additions & 3 deletions gto/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class BasePromotion(BaseModel):
creation_date: datetime
author: str
commit_hexsha: str
tag: str


class BaseVersion(BaseModel):
Expand All @@ -28,13 +29,14 @@ class BaseVersion(BaseModel):
author: str
commit_hexsha: str
discovered: bool = False
tag: Optional[str] = None
promotions: List[BasePromotion] = []
enrichments: List[Enrichment] = []

@property
def is_registered(self):
"""Tells if this is an explicitly registered version"""
return SemVer.is_valid(self.name)
return self.tag is not None # SemVer.is_valid(self.name)

@property
def version(self):
Expand Down Expand Up @@ -69,9 +71,13 @@ def __repr__(self) -> str:
stages = ", ".join(f"'{l}'" for l in self.unique_stages)
return f"Artifact(versions=[{versions}], stages=[{stages}])"

def get_latest_version(self) -> Optional[BaseVersion]:
def get_latest_version(self, registered=False) -> Optional[BaseVersion]:
versions = sorted(
(v for v in self.versions if not v.discovered),
(
v
for v in self.versions
if not v.discovered and (not registered or v.is_registered)
),
key=lambda x: x.creation_date,
)
if versions:
Expand Down
34 changes: 18 additions & 16 deletions gto/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import sys
from collections import defaultdict
from enum import Enum # , EnumMeta, _EnumDict
from functools import partial, wraps
Expand Down Expand Up @@ -317,8 +316,6 @@ def gto_callback(

echo(CONFIG.__repr_str__("\n"))
echo()
else:
sys.tracebacklimit = 0

ctx.obj = {"traceback": traceback}

Expand Down Expand Up @@ -454,10 +451,9 @@ def register(
Choose a part to bump version by:
$ gto register nn --bump minor
"""
registered_version = gto.api.register(
repo=repo, name=name, ref=ref or "HEAD", version=version, bump=bump
gto.api.register(
repo=repo, name=name, ref=ref or "HEAD", version=version, bump=bump, stdout=True
)
echo(f"Registered {registered_version.artifact} version {registered_version.name}")


@gto_command(section=COMMANDS.REGISTER_PROMOTE)
Expand Down Expand Up @@ -496,10 +492,16 @@ def promote(
else:
name_version = None
promote_version = version
promotion = gto.api.promote(
repo, name, stage, promote_version, ref, name_version, simple=simple
gto.api.promote(
repo,
name,
stage,
promote_version,
ref,
name_version,
simple=simple,
stdout=True,
)
click.echo(f"Promoted {name} version {promotion.version} to stage {stage}")


@gto_command(section=COMMANDS.REGISTRY)
Expand All @@ -518,11 +520,11 @@ def latest(
latest_version = gto.api.find_latest_version(repo, name)
if latest_version:
if path:
click.echo(latest_version.artifact.path)
echo(latest_version.artifact.path)
elif ref:
click.echo(latest_version.commit_hexsha)
echo(latest_version.commit_hexsha)
else:
click.echo(latest_version.name)
echo(latest_version.name)


@gto_command(section=COMMANDS.REGISTRY)
Expand All @@ -548,11 +550,11 @@ def which(
version = gto.api.find_promotion(repo, name, stage)
if version:
if path:
click.echo(version.artifact.path)
echo(version.artifact.path)
elif ref:
click.echo(version.commit_hexsha)
echo(version.commit_hexsha)
else:
click.echo(version.version)
echo(version.version)


@gto_command(hidden=True)
Expand Down Expand Up @@ -769,7 +771,7 @@ def describe(
"""
infos = gto.api.describe(repo=repo, name=name, rev=rev)
for info in infos:
click.echo(info.get_human_readable())
echo(info.get_human_readable())


if __name__ == "__main__":
Expand Down
24 changes: 7 additions & 17 deletions gto/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ def __init__(self, path) -> None:


class UnknownType(GTOException):
_message = (
"Type '{type}' is not present in your config file. Allowed values are: {types}."
)
_message = "Type '{type}' is not present in your config file. Allowed values are: '{types}'"

def __init__(self, type, types) -> None:
self.message = self._message.format(type=type, types=types)
Expand All @@ -54,7 +52,7 @@ def __init__(self, name) -> None:


class PathIsUsed(GTOException):
_message = "Provided path conflicts with {path} ({type} {name})"
_message = "Provided path conflicts with '{path}' ('{type}' '{name}')"

def __init__(self, type, name, path) -> None:
self.message = self._message.format(type=type, name=name, path=path)
Expand All @@ -70,7 +68,7 @@ def __init__(self, name) -> None:


class ManyVersions(GTOException):
_message = "{versions} versions of artifact {name} found"
_message = "'{versions}' versions of artifact '{name}' found"

def __init__(self, name, versions) -> None:
self.message = self._message.format(name=name, versions=versions)
Expand All @@ -86,23 +84,15 @@ def __init__(self, version) -> None:


class VersionExistsForCommit(GTOException):
_message = "The model {model} was already registered in this commit with version '{version}'."
_message = "The artifact '{model}' is already registered in this commit with version '{version}'."

def __init__(self, model, version) -> None:
self.message = self._message.format(model=model, version=version)
super().__init__(self.message)


class VersionIsOld(GTOException):
_message = "Version '{suggested}' is younger than the latest {latest}"

def __init__(self, latest, suggested) -> None:
self.message = self._message.format(latest=latest, suggested=suggested)
super().__init__(self.message)


class UnknownStage(GTOException):
_message = "Stage '{stage}' is not present in your config file. Allowed stages are: {stages}."
_message = "Stage '{stage}' is not present in your config file. Allowed stages are: '{stages}'."

def __init__(self, stage, stages) -> None:
self.message = self._message.format(stage=stage, stages=stages)
Expand All @@ -126,15 +116,15 @@ def __init__(self, ref) -> None:


class InvalidVersion(GTOException):
_message = "Supplied version {version} doesn't look like {cls} version"
_message = "Supplied version '{version}' doesn't look like '{cls}' version"

def __init__(self, version, cls) -> None:
self.message = self._message.format(version=version, cls=cls)
super().__init__(self.message)


class IncomparableVersions(GTOException):
_message = "You can compare only versions of the same system, but not {} and {}"
_message = "You can compare only versions of the same system, but not '{}' and '{}'"

def __init__(self, this, that) -> None:
self.message = self._message.format(this, that)
Expand Down
59 changes: 33 additions & 26 deletions gto/registry.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import os
from typing import Union

import click
import git
from git import InvalidGitRepositoryError, Repo
from pydantic import BaseModel

from gto.base import BaseManager, BasePromotion, BaseRegistryState
from gto.base import BasePromotion, BaseRegistryState
from gto.config import CONFIG_FILE_NAME, RegistryConfig
from gto.exceptions import (
NoRepo,
VersionAlreadyRegistered,
VersionExistsForCommit,
VersionIsOld,
)
from gto.index import EnrichmentManager
from gto.tag import TagStageManager, TagVersionManager
from gto.ui import echo
from gto.versions import SemVer


class GitRegistry(BaseModel):
repo: git.Repo
version_manager: BaseManager
stage_manager: BaseManager
enrichment_manager: BaseManager
version_manager: TagVersionManager
stage_manager: TagStageManager
enrichment_manager: EnrichmentManager
config: RegistryConfig

class Config:
Expand Down Expand Up @@ -80,43 +79,46 @@ def find_artifact(
name, create_new=create_new # type: ignore
)

def register(self, name, ref, version=None, bump=None):
def register(self, name, ref, version=None, bump=None, stdout=False):
"""Register artifact version"""
ref = self.repo.commit(ref).hexsha
# TODO: add the same check for other actions, to promote and etc
# also we need to check integrity of the index+state
found_artifact = self.find_artifact(name, create_new=True)
# check that this commit don't have a version already
found_version = found_artifact.find_version(commit_hexsha=ref)
if found_version is not None:
if found_version is not None and found_version.is_registered:
raise VersionExistsForCommit(name, found_version.name)
# if version name is provided, use it
if version:
if found_artifact.find_version(name=version) is not None:
raise VersionAlreadyRegistered(version)
if found_artifact.discovered:
latest_ver = found_artifact.get_latest_version().name
if SemVer(version) < latest_ver:
raise VersionIsOld(latest=latest_ver, suggested=version)
# if version name wasn't provided but there were some, bump the last one
elif found_artifact.discovered:
version = (
SemVer(self.find_artifact(name).get_latest_version().name)
.bump(**({"part": bump} if bump else {}))
.version
)
# if no versions exist, use the minimal version possible
else:
version = SemVer.get_minimal().version
# if version name wasn't provided but there were some, bump the last one
last_version = found_artifact.get_latest_version(registered=True)
if last_version:
version = (
SemVer(last_version.name)
.bump(**({"part": bump} if bump else {}))
.version
)
# if no versions exist, use the minimal version possible
else:
version = SemVer.get_minimal().version
self.version_manager.register(
name,
version,
ref,
message=f"Registering artifact {name} version {version}",
)
return self.find_artifact(name).find_version(
registered_version = self.find_artifact(name).find_version(
name=version, raise_if_not_found=True
)
if stdout:
echo(
f"Created git tag '{registered_version.tag}' that registers a new version"
)
return registered_version

def promote(
self,
Expand All @@ -126,6 +128,7 @@ def promote(
promote_ref=None,
name_version=None,
simple=False,
stdout=False,
) -> BasePromotion:
"""Assign stage to specific artifact version"""
self.config.assert_stage(stage)
Expand All @@ -142,9 +145,8 @@ def promote(
else:
found_version = found_artifact.find_version(commit_hexsha=promote_ref)
if found_version is None:
version = self.register(name, version=name_version, ref=promote_ref)
click.echo(
f"Registered new version '{version.name}' of '{name}' at commit '{promote_ref}'"
self.register(
name, version=name_version, ref=promote_ref, stdout=stdout
)
self.stage_manager.promote( # type: ignore
name,
Expand All @@ -153,7 +155,12 @@ def promote(
message=f"Promoting {name} version {promote_version} to stage {stage}",
simple=simple,
)
return self.get_state().find_artifact(name).promoted[stage]
promotion = self.get_state().find_artifact(name).promoted[stage]
if stdout:
echo(
f"Created git tag '{promotion.tag}' that promotes '{promotion.version}'"
)
return promotion

def check_ref(self, ref: str):
"Find out what was registered/promoted in this ref"
Expand Down
2 changes: 2 additions & 0 deletions gto/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def version_from_tag(tag: git.Tag) -> BaseVersion:
creation_date=mtag.creation_date,
author=tag.tag.tagger.name,
commit_hexsha=tag.commit.hexsha,
tag=tag.name,
)


Expand Down Expand Up @@ -177,6 +178,7 @@ def promotion_from_tag(
creation_date=mtag.creation_date,
author=tag.tag.tagger.name,
commit_hexsha=tag.commit.hexsha,
tag=tag.name,
)


Expand Down
2 changes: 1 addition & 1 deletion gto/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def is_valid(cls, version):
try:
cls.parse(version)
return True
except ValueError:
except (InvalidVersion, ValueError) as e: # pylint: disable=unused-variable
return False

@classmethod
Expand Down
Loading

0 comments on commit 3c397c8

Please sign in to comment.