From 17023400c78ca02eaf994fc2b33476989d9d3ffb Mon Sep 17 00:00:00 2001
From: Alexander Guschin <1aguschin@gmail.com>
Date: Mon, 25 Jul 2022 18:19:51 +0600
Subject: [PATCH] Deprecate Stages and introduce Labels instead (#218)
* DDD for labels
* upd
* how to remove label and git tag
* fixing tests and update 'gto which' output
* fix bug
* update readme; fix 'gto show name'
* support --last-stage for 'gto show'
* typos in readme
* rename 'promote' to 'assign'
* Update README.md
---
README.md | 169 ++++++++++++++++++++++++++++-------------
gto/api.py | 64 ++++++++++------
gto/base.py | 66 ++++++++--------
gto/cli.py | 72 ++++++++++--------
gto/constants.py | 8 +-
gto/registry.py | 52 ++++++-------
gto/tag.py | 45 +++++------
tests/conftest.py | 12 +--
tests/test_api.py | 65 +++++++++-------
tests/test_cli.py | 27 ++++---
tests/test_config.py | 14 ++--
tests/test_registry.py | 14 ++--
tests/test_showcase.py | 33 ++++----
tests/test_tag.py | 16 ++--
14 files changed, 388 insertions(+), 269 deletions(-)
diff --git a/README.md b/README.md
index 0a8fafb7..f8d37302 100644
--- a/README.md
+++ b/README.md
@@ -8,10 +8,10 @@ Git Tag Ops. Turn your Git repository into an Artifact Registry:
- Registry: Track new artifacts and their versions for releases and significant
changes.
-- Lifecycle Management: Promote or roll back versions among a structured set of
- stages.
+- Lifecycle Management: Create actionable stages for versions marking status of
+ artifact or it's readiness to be consumed by a specific environment.
- GitOps: Signal CI/CD automation or other downstream systems to act upon these
- lifecycle updates.
+ new versions and lifecycle updates.
- Enrichments: Annotate and query artifact metadata with additional information.
GTO works by creating annotated Git tags in a standard format.
@@ -64,39 +64,73 @@ several versions in a given commit, ordered by their automatic version numbers.
-### Promoting
+### Assing a stage
-Promote a specific artifact version to a lifecycle stage with `gto promote`.
-Stages can be seen as the status of your artifact, signaling readiness for usage
-by downstream systems, e.g. via CI/CD or web hooks. For example: redeploy an ML
-model.
+Assing an actionable stage for a specific artifact version with `gto assign`.
+Stages can mark it's readiness for a specific consumer. You can plug in a real
+downsteam system via CI/CD or web hooks. For example: redeploy an ML model.
```console
-$ gto promote awesome-model prod
-Created git tag 'awesome-model#prod#1' that promotes 'v0.0.1'
+$ gto assign awesome-model prod
+Created git tag 'awesome-model#prod#1' that adds stage 'prod' to 'v0.0.1'
```
-GTO creates a special Git tag for the artifact promotion, in a standard format:
+GTO creates a special Git tag in a standard format:
`{artifact_name}#{stage}#{e}`.
The event is now associated to the latest version of the artifact. There can be
multiple events for a given version, ordered by an automatic incremental event
-number (`{e}`). This will keep the history of your promotions.
+number (`{e}`). This will keep the history of your stages creation.
-Note: if you prefer, you can use simple promotion tag format without the
-incremental `{e}`, but this will disable the `gto history` command. This is
-because promoting an artifact where a promotion tag already existed will require
-deleting the existing tag.
+Note: if you prefer, you can use simple stage tag format without the incremental
+`{e}`, but this will disable the `gto history` command. This is because
+assigning a stage to an artifact version where a stage tag already existed will
+require deleting the existing tag.
+
+
+
+### Unassign a stage
+
+Note: this functionality is in development and will be introduced soon.
+
+Sometimes you need to mark an artifact version no longer ready for a specific
+consumer, and maybe signal a downstream system about this. You can use
+`gto unassign` for that:
+
+```console
+$ gto unassign awesome-model prod
+Created git tag 'awesome-model#prod#2!' that unassigns stage 'prod' from 'v0.0.1'
+```
+
+
+
+GTO creates a special Git tag in a standard format:
+`{artifact_name}#{stage}#{e}!`.
+
+Note, that later you can create this stage again, if you need to, by calling
+`$ gto assign`.
+
+You also may want to delete the git tag instead of creating a new one. This is
+useful if you don't want to keep extra tags in you Git repo, don't need history
+and don't want to trigger a CI/CD or another downstream system. For that, you
+can use:
+
+```console
+$ gto unassign --delete
+Deleted git tag 'awesome-model#prod#1' that assigned 'prod' to 'v0.0.1'
+To push the changes upsteam, run:
+git push origin awesome-model#prod#1 --delete
+```
### Annotating
-So far we've seen how to register and promote artifact versions, but we still
-don't have much information about them. What about the type of artifact
-(dataset, model, etc.) or the file path to find it in the working tree?
+So far we've seen how to register and assign a stage to an artifact versions,
+but we still don't have much information about them. What about the type of
+artifact (dataset, model, etc.) or the file path to find it in the working tree?
For simple projects (e.g. single artifact) we can assume the details in a
downstream system. But for more advanced cases, we should codify them in the
@@ -125,8 +159,8 @@ GTO the artifact file is committed to Git.
- Physical files/directories are committed to the repo. When you register a new
- version or promote it, Git guarantees that it's immutable -- you can return a
- year later and get the same artifact by providing a version.
+ version or assign a stage to it, Git guarantees that it's immutable -- you can
+ return a year later and get the same artifact by providing a version.
- Virtual artifacts could be an external path (e.g. `s3://mybucket/myfile`) or a
local path to a metafile representing an externally stored artifact file (as
@@ -149,14 +183,14 @@ Let's look at the usage of the `gto show` and `gto history`.
### Show the current state
This is the entire state of the registry: all artifacts, their latest versions,
-and what is promoted to stages right now.
+and the greatest versions for each stage.
```console
$ gto show
╒═══════════════╤══════════╤════════╤═════════╤════════════╕
│ name │ latest │ #dev │ #prod │ #staging │
╞═══════════════╪══════════╪════════╪═════════╪════════════╡
-│ churn │ v3.1.0 │ - │ v3.0.0 │ v3.1.0 │
+│ churn │ v3.1.0 │ v3.0.0 │ v3.0.0 │ v3.1.0 │
│ segment │ v0.4.1 │ v0.4.1 │ - │ - │
│ cv-class │ v0.1.13 │ - │ - │ - │
│ awesome-model │ v0.0.1 │ - │ v0.0.1 │ - │
@@ -167,16 +201,38 @@ Here we'll see both artifacts that have Git tags only and those annotated in
`artifacts.yaml`. Use `--all-branches` or `--all-commits` to read
`artifacts.yaml` from more commits than just `HEAD`.
-Add an artifact name to print all og its versions instead:
+Add an artifact name to print all of its versions instead:
```console
$ gto show churn
-╒════════════╤═══════════╤═════════╤═════════════════════╤═══════════════════╤══════════════╕
-│ artifact │ version │ stage │ created_at │ author │ ref │
-╞════════════╪═══════════╪═════════╪═════════════════════╪═══════════════════╪══════════════╡
-│ churn │ v3.0.0 │ prod │ 2022-04-08 23:46:58 │ Alexander Guschin │ churn@v3.0.0 │
-│ churn │ v3.1.0 │ staging │ 2022-04-13 14:53:38 │ Alexander Guschin │ churn@v3.1.0 │
-╘════════════╧═══════════╧═════════╧═════════════════════╧═══════════════════╧══════════════╛
+╒════════════╤═══════════╤═══════════╤═════════════════════╤═══════════════════╤══════════════╕
+│ artifact │ version │ stage │ created_at │ author │ ref │
+╞════════════╪═══════════╪═══════════╪═════════════════════╪═══════════════════╪══════════════╡
+│ churn │ v3.1.0 │ staging │ 2022-07-13 15:25:41 │ Alexander Guschin │ churn@v3.1.0 │
+│ churn │ v3.0.0 │ prod, dev │ 2022-07-09 00:19:01 │ Alexander Guschin │ churn@v3.0.0 │
+╘════════════╧═══════════╧═══════════╧═════════════════════╧═══════════════════╧══════════════╛
+```
+
+#### Enabling Kanban workflow
+
+In some cases, you would like to have a latest stage for an artifact version to
+replace all the previous stages. In this case the version will have a single
+stage. This resembles Kanban workflow, when you "move" your artifact version
+from one column ("stage-1") to another ("stage-2"). This is how MLFlow and some
+other Model Registries work.
+
+To achieve this, you can use `--last-stage` flag (or `--ls` for short):
+
+```console
+$ gto show --ls
+╒═══════════════╤══════════╤════════╤═════════╤════════════╕
+│ name │ latest │ #dev │ #prod │ #staging │
+╞═══════════════╪══════════╪════════╪═════════╪════════════╡
+│ churn │ v3.1.0 │ - │ v3.0.0 │ v3.1.0 │
+│ segment │ v0.4.1 │ v0.4.1 │ - │ - │
+│ cv-class │ v0.1.13 │ - │ - │ - │
+│ awesome-model │ v0.0.1 │ - │ v0.0.1 │ - │
+╘═══════════════╧══════════╧════════╧═════════╧════════════╛
```
### See the history of an artifact
@@ -189,12 +245,13 @@ $ gto history churn
╒═════════════════════╤════════════╤══════════════╤═══════════╤═════════╤══════════╤═══════════════════╕
│ timestamp │ artifact │ event │ version │ stage │ commit │ author │
╞═════════════════════╪════════════╪══════════════╪═══════════╪═════════╪══════════╪═══════════════════╡
-│ 2022-04-07 20:00:18 │ churn │ commit │ - │ - │ 54d6d39 │ Alexander Guschin │
-│ 2022-04-08 23:46:58 │ churn │ registration │ v3.0.0 │ - │ 54d6d39 │ Alexander Guschin │
-│ 2022-04-12 11:06:58 │ churn │ commit │ - │ - │ 26cafe9 │ Alexander Guschin │
-│ 2022-04-13 14:53:38 │ churn │ registration │ v3.1.0 │ - │ 26cafe9 │ Alexander Guschin │
-│ 2022-04-14 18:40:18 │ churn │ promotion │ v3.1.0 │ staging │ 26cafe9 │ Alexander Guschin │
-│ 2022-04-15 22:26:58 │ churn │ promotion │ v3.0.0 │ prod │ 54d6d39 │ Alexander Guschin │
+│ 2022-07-17 02:45:41 │ churn │ assignment │ v3.0.0 │ prod │ 631520b │ Alexander Guschin │
+│ 2022-07-15 22:59:01 │ churn │ assignment │ v3.1.0 │ staging │ be340cc │ Alexander Guschin │
+│ 2022-07-14 19:12:21 │ churn │ assignment │ v3.0.0 │ dev │ 631520b │ Alexander Guschin │
+│ 2022-07-13 15:25:41 │ churn │ registration │ v3.1.0 │ - │ be340cc │ Alexander Guschin │
+│ 2022-07-12 11:39:01 │ churn │ commit │ - │ - │ be340cc │ Alexander Guschin │
+│ 2022-07-09 00:19:01 │ churn │ registration │ v3.0.0 │ - │ 631520b │ Alexander Guschin │
+│ 2022-07-07 20:32:21 │ churn │ commit │ - │ - │ 631520b │ Alexander Guschin │
╘═════════════════════╧════════════╧══════════════╧═══════════╧═════════╧══════════╧═══════════════════╛
```
@@ -203,14 +260,14 @@ $ gto history churn
Let's look at integrating with GTO via Git as well as using the `gto check-ref`,
`gto latest`, `gto which`, and `gto describe` utility commands downstream.
-### Act on new versions and promotions in CI
+### Act on new versions and stage assignments in CI
To act upon annotations (Git tags), you can create simple CI workflow. With
[GitHub Actions](https://github.com/features/actions) for example, it can look
like this:
```yaml
-name: Act on versions or promotions of the "churn" actifact
+name: Act on versions or stage assignments of the "churn" actifact
on:
push:
tags:
@@ -254,17 +311,31 @@ $ gto latest churn --ref
churn@v3.1.0
```
-To get the version that is currently promoted to an environment (stage), use
+To get the version that is currently assigned to an environment (stage), use
`gto which`:
```console
-$ gto which churn prod
+$ gto which churn dev
v3.0.0
-$ gto which churn prod --ref
-churn#prod#2
+$ gto which churn dev --ref
+churn#dev#1
```
+
+
+If you prefer Kanban workflow described above, you could use `--last` flag. This
+will take into account the last stage for a version only.
+
+```console
+$ gto which churn dev --last
+```
+
+Since the last stage for version `v3.0.0` was `prod`, this doesn't return
+anything.
+
+
+
To get details about an artifact (from `artifacts.yaml`) use `gto describe`:
```console
@@ -272,11 +343,7 @@ $ gto describe churn
```
```yaml
-{
- "type": "model",
- "path": "models/churn.pkl",
- "virtual": false
-}
+{ "type": "model", "path": "models/churn.pkl", "virtual": false }
```
> The output is in JSON format for ease of parsing programatically.
@@ -326,16 +393,16 @@ $ pip install --upgrade pip setuptools wheel ".[tests]"
### 3. Run
```console
-$ pytest --basetemp=.pytest-cache
+$ pytest --basetemp=pytest-basetemp
```
-This will create `pytest-cache/` directory with some fixtures that can serve as
-examples.
+This will create `pytest-basetemp/` directory with some fixtures that can serve
+as examples.
Notably, check out this dir:
```console
-$ cd .pytest-cache/test_api0/
+$ cd pytest-basetemp/test_api0/
$ gto show -v
```
diff --git a/gto/api.py b/gto/api.py
index 26170418..ceeb481a 100644
--- a/gto/api.py
+++ b/gto/api.py
@@ -2,9 +2,10 @@
from datetime import datetime
from typing import List, Optional, Union
+from funcy import distinct
from git import Repo
-from gto.constants import NAME, STAGE, VERSION
+from gto.constants import NAME, STAGE, VERSION, Event
from gto.exceptions import NoRepo, WrongArgs
from gto.ext import EnrichmentInfo
from gto.index import (
@@ -101,12 +102,12 @@ def register(
)
-def promote(
+def assign(
repo: Union[str, Repo],
name: str,
stage: str,
- promote_version: str = None,
- promote_ref: str = None,
+ version: str = None,
+ ref: str = None,
name_version: str = None,
message: str = None,
simple: bool = False,
@@ -117,11 +118,11 @@ def promote(
author_email: Optional[str] = None,
):
"""Assign stage to specific artifact version"""
- return GitRegistry.from_repo(repo).promote(
+ return GitRegistry.from_repo(repo).assign(
name,
stage,
- promote_version,
- promote_ref,
+ version,
+ ref,
name_version,
message=message,
simple=simple,
@@ -161,7 +162,7 @@ def find_versions_in_stage(
def check_ref(repo: Union[str, Repo], ref: str):
- """Find out what have been registered/promoted in the provided ref"""
+ """Find out what have been registered/assigned in the provided ref"""
reg = GitRegistry.from_repo(repo)
return reg.check_ref(ref)
@@ -169,11 +170,12 @@ def check_ref(repo: Union[str, Repo], ref: str):
def show(
repo: Union[str, Repo],
name: Optional[str] = None,
- table: bool = False,
all_branches=False,
all_commits=False,
truncate_hexsha=False,
registered_only=False,
+ last_stage=False,
+ table: bool = False,
):
return (
_show_versions(
@@ -182,6 +184,7 @@ def show(
all_branches=all_branches,
all_commits=all_commits,
registered_only=registered_only,
+ last_stage=last_stage,
table=table,
truncate_hexsha=truncate_hexsha,
)
@@ -191,6 +194,7 @@ def show(
all_branches=all_branches,
all_commits=all_commits,
registered_only=registered_only,
+ last_stage=last_stage,
table=table,
truncate_hexsha=truncate_hexsha,
)
@@ -202,8 +206,9 @@ def _show_registry(
all_branches=False,
all_commits=False,
registered_only=False,
+ last_stage: bool = False,
table: bool = False,
- truncate_hexsha: bool = False, # pylint: disable=unused-argument
+ truncate_hexsha: bool = False,
):
"""Show current registry state"""
@@ -219,9 +224,14 @@ def format_hexsha(hexsha):
else None,
"stage": {
name: format_hexsha(
- o.get_promotions(registered_only=registered_only)[name].version
+ o.get_assignments(
+ registered_only=registered_only, last_stage=last_stage
+ )[name][0].version
+ )
+ if name
+ in o.get_assignments(
+ registered_only=registered_only, last_stage=last_stage
)
- if name in o.get_promotions(registered_only=registered_only)
else None
for name in stages
},
@@ -242,13 +252,14 @@ def format_hexsha(hexsha):
return result, headers
-def _show_versions(
+def _show_versions( # pylint: disable=too-many-locals
repo: Union[str, Repo],
name: str,
raw: bool = False,
all_branches=False,
all_commits=False,
registered_only=False,
+ last_stage: bool = False,
table: bool = False,
truncate_hexsha: bool = False,
):
@@ -277,8 +288,14 @@ def format_hexsha(hexsha):
versions_ = []
for v in versions:
v["version"] = format_hexsha(v["name"])
- if v["stage"]:
- v["stage"] = v["stage"]["stage"]
+ v["stage"] = ", ".join(
+ distinct(
+ s["stage"]
+ for s in (
+ v["assignments"][::-1][:1] if last_stage else v["assignments"][::-1]
+ )
+ )
+ )
v["commit_hexsha"] = format_hexsha(v["commit_hexsha"])
v["ref"] = v["tag"] or v["commit_hexsha"]
for key in (
@@ -289,6 +306,7 @@ def format_hexsha(hexsha):
"name",
"message",
"author_email",
+ "assignments",
):
v.pop(key)
# v["enrichments"] = [e["source"] for e in v["enrichments"]]
@@ -340,7 +358,7 @@ def format_hexsha(hexsha):
reg.repo.commit(v.commit_hexsha).committed_date
),
artifact=o.name,
- event="commit",
+ event=Event.COMMIT,
commit=format_hexsha(v.commit_hexsha),
author=reg.repo.commit(v.commit_hexsha).author.name,
author_email=reg.repo.commit(v.commit_hexsha).author.email,
@@ -354,7 +372,7 @@ def format_hexsha(hexsha):
OrderedDict(
timestamp=v.created_at,
artifact=o.name,
- event="registration",
+ event=Event.REGISTRATION,
version=format_hexsha(v.name),
commit=format_hexsha(v.commit_hexsha),
author=v.author,
@@ -366,11 +384,11 @@ def format_hexsha(hexsha):
for v in o.get_versions()
]
- promotion = [
+ assignment = [
OrderedDict(
timestamp=p.created_at,
artifact=o.name,
- event="promotion",
+ event=Event.ASSIGNMENT,
version=format_hexsha(p.version),
stage=p.stage,
commit=format_hexsha(p.commit_hexsha),
@@ -383,12 +401,12 @@ def format_hexsha(hexsha):
]
events_order = {
- "commit": 1,
- "registration": 2,
- "promotion": 3,
+ Event.COMMIT: 1,
+ Event.REGISTRATION: 2,
+ Event.ASSIGNMENT: 3,
}
events = sorted(
- commits + registration + promotion,
+ commits + registration + assignment,
key=lambda x: (x["timestamp"], events_order[x["event"]]),
)
if not ascending:
diff --git a/gto/base.py b/gto/base.py
index 20bf2eb5..466c0050 100644
--- a/gto/base.py
+++ b/gto/base.py
@@ -12,7 +12,7 @@
from .exceptions import ArtifactNotFound, ManyVersions, VersionRequired
-class BasePromotion(BaseModel):
+class BaseAssignment(BaseModel):
artifact: str
version: str
stage: str
@@ -34,7 +34,7 @@ class BaseVersion(BaseModel):
commit_hexsha: str
discovered: bool = False
tag: Optional[str] = None
- promotions: List[BasePromotion] = []
+ assignments: List[BaseAssignment] = []
enrichments: List[Enrichment] = []
@property
@@ -47,14 +47,15 @@ def version(self):
# TODO: this should be read from config, how to pass it down here?
return SemVer(self.name)
- @property
- def stage(self):
- promotions = sorted(self.promotions, key=lambda p: p.created_at)
- return promotions[-1] if promotions else None
+ def add_assignment(self, assignment: BaseAssignment):
+ self.assignments.append(assignment)
+ self.assignments.sort(key=lambda p: p.created_at)
def dict_status(self):
- version = self.dict(exclude={"promotions"})
- version["stage"] = self.stage.dict() if self.stage else None
+ version = self.dict(exclude={"assignments"})
+ version["assignments"] = (
+ [a.dict() for a in self.assignments] if self.assignments else None
+ )
return version
@@ -103,12 +104,14 @@ class BaseArtifact(BaseModel):
@property
def stages(self):
return [
- promotion for version in self.versions for promotion in version.promotions
+ assignment
+ for version in self.versions
+ for assignment in version.assignments
]
@property
def unique_stages(self):
- return {promotion.stage for promotion in self.stages}
+ return {assignment.stage for assignment in self.stages}
def __repr__(self) -> str:
versions = ", ".join(f"'{v.name}'" for v in self.versions)
@@ -141,32 +144,35 @@ def get_latest_version(
return versions[0]
return None
- def get_promotions(
- self, all=False, registered_only=False, sort=VersionSort.SemVer
- ) -> Dict[str, Union[BasePromotion, List[BasePromotion]]]:
+ def get_assignments(
+ self,
+ registered_only=False,
+ last_stage=False,
+ sort=VersionSort.SemVer,
+ ) -> Dict[str, Union[BaseAssignment, List[BaseAssignment]]]:
versions = self.get_versions(
include_non_explicit=not registered_only, sort=sort
)
if sort == VersionSort.Timestamp:
# for this sort we need to sort not versions, as above ^
- # but promotions themselves
+ # but assignments themselves
raise NotImplementedError("Sorting by timestamp is not implemented yet")
- stages = {} # type: ignore
+ stages: Dict[str, List[BaseAssignment]] = {}
for version in versions:
- promotion = version.stage
- if promotion:
- stages[promotion.stage] = stages.get(promotion.stage, []) + [promotion]
- if all:
- return stages
- return {stage: promotions[0] for stage, promotions in stages.items()}
+ for a in (
+ version.assignments[::-1][:1] if last_stage else version.assignments
+ ):
+ if a.stage not in stages:
+ stages[a.stage] = []
+ if a.version not in [i.version for i in stages[a.stage]]:
+ stages[a.stage].append(a)
+ return stages # type: ignore
def add_version(self, version: BaseVersion):
self.versions.append(version)
- def add_promotion(self, promotion: BasePromotion):
- self.find_version(name=promotion.version).promotions.append( # type: ignore
- promotion
- )
+ def add_assignment(self, assignment: BaseAssignment):
+ self.find_version(name=assignment.version).add_assignment(assignment) # type: ignore
def update_enrichments(self, version, enrichments):
self.find_version(
@@ -288,11 +294,11 @@ def which(
self, name, stage, raise_if_not_found=True, all=False, registered_only=False
):
"""Return stage active in specific stage"""
- promoted = self.find_artifact(name).get_promotions(
- all=all, registered_only=registered_only
+ assigned = self.find_artifact(name).get_assignments(
+ registered_only=registered_only
)
- if stage in promoted:
- return promoted[stage]
+ if stage in assigned:
+ return assigned[stage] if all else assigned[stage][0]
if raise_if_not_found:
raise ValueError(f"Stage {stage} not found for {name}")
return None
@@ -323,7 +329,7 @@ def update_state(
# def register(self, name, version, ref, message):
# raise NotImplementedError
- # def promote(self, name, stage, ref, message):
+ # def assign(self, name, stage, ref, message):
# raise NotImplementedError
def check_ref(self, ref: str, state: BaseRegistryState):
diff --git a/gto/cli.py b/gto/cli.py
index 36b1d08b..6b9e5e51 100644
--- a/gto/cli.py
+++ b/gto/cli.py
@@ -183,7 +183,7 @@ def GTOGroupSection(section):
arg_name = Argument(..., help="Artifact name")
arg_version = Argument(..., help="Artifact version")
-arg_stage = Argument(..., help="Stage to promote to")
+arg_stage = Argument(..., help="Stage to assign")
option_rev = Option(None, "--rev", help="Repo revision to use", show_default=True)
option_repo = Option(".", "-r", "--repo", help="Repository to use", show_default=True)
@@ -272,10 +272,13 @@ def gto_callback(
):
"""\b
Git Tag Ops. Turn your Git repository into an Artifact Registry:
- * Register new versions of artifacts marking releases/significant changes
- * Promote versions to ordered, named stages to track their lifecycles
- * GitOps: signal CI/CD automation or downstream systems to act upon these actions
- * Maintain and query artifact metadata / additional info with Enrichments machinery
+ * Registry: Track new artifacts and their versions for releases and significant
+ changes.
+ * Lifecycle Management: Create actionable stages for versions marking status of
+ artifact or it's readiness to be consumed by a specific environment.
+ * GitOps: Signal CI/CD automation or other downstream systems to act upon these
+ new versions and lifecycle updates.
+ * Enrichments: Annotate and query artifact metadata with additional information.
"""
if ctx.invoked_subcommand is None and show_version:
with cli_echo():
@@ -445,12 +448,12 @@ def register(
)
-@gto_command(section=CommandGroups.modifying)
-def promote(
+@gto_command(section=CommandGroups.modifying, aliases=["promote"])
+def assign(
repo: str = option_repo,
name: str = arg_name,
stage: str = arg_stage,
- ref: Optional[str] = Argument(None, help="Git reference to promote"),
+ ref: Optional[str] = Argument(None, help="Git reference to use"),
version: Optional[str] = Option(
None,
"--version",
@@ -466,7 +469,7 @@ def promote(
help="Use simple notation, e.g. rf#prod instead of rf#prod-5",
),
force: bool = Option(
- False, help="Promote even if version is already in required Stage"
+ False, help="Assign even if version is already in required Stage"
),
skip_registration: bool = Option(
False,
@@ -479,33 +482,32 @@ def promote(
"""Move an artifact version to a specific stage
Examples:
- Promote "nn" to "prod" at specific ref:
- $ gto promote nn prod abcd123
+ Assign "nn" to "prod" at specific ref:
+ $ gto assign nn prod abcd123
- Promote specific version:
- $ gto promote nn prod --version v1.0.0
+ Assign specific version:
+ $ gto assign nn prod --version v1.0.0
- Promote at specific ref and name version explicitly:
- $ gto promote nn prod abcd123 --version v1.0.0
+ Assign at specific ref and name version explicitly:
+ $ gto assign nn prod abcd123 --version v1.0.0
- Promote without increment
- $ gto promote nn prod HEAD --simple
+ Assign without increment
+ $ gto assign nn prod HEAD --simple
"""
if ref is not None:
name_version = version
- promote_version = None
+ version = None
elif version is not None:
name_version = None
- promote_version = version
else:
ref = "HEAD"
name_version = None
- promote_version = None
- gto.api.promote(
+ version = None
+ gto.api.assign(
repo,
name,
stage,
- promote_version,
+ version,
ref,
name_version,
message=message,
@@ -550,7 +552,7 @@ def which(
Examples:
$ gto which nn prod
- Print git tag that did the promotion:
+ Print git tag that did the assignment:
$ gto which nn prod --ref
"""
version = gto.api.find_versions_in_stage(
@@ -592,8 +594,8 @@ def check_ref(
registration: bool = Option(
False, "--registration", is_flag=True, help="Check if ref registers a version"
),
- promotion: bool = Option(
- False, "--promotion", is_flag=True, help="Check if ref promotes a version"
+ assignment: bool = Option(
+ False, "--assignment", is_flag=True, help="Check if ref assigns a stage"
),
name: bool = Option(False, "--name", is_flag=True, help="Output artifact name"),
version: bool = Option(
@@ -601,7 +603,7 @@ def check_ref(
),
stage: bool = Option(False, "--stage", is_flag=True, help="Output artifact stage"),
):
- """Find out the artifact version registered/promoted with ref
+ """Find out the artifact version registered/assigned with ref
Examples:
$ gto check-ref rf@v1.0.0
@@ -609,7 +611,8 @@ def check_ref(
$ gto check-ref rf#prod --version
"""
assert (
- sum(bool(i) for i in (json, registration, promotion, name, version, stage)) <= 1
+ sum(bool(i) for i in (json, registration, assignment, name, version, stage))
+ <= 1
), "Only one output formatting flags is allowed"
result = {
action: {name: version.dict() for name, version in found.items()}
@@ -632,19 +635,19 @@ def check_ref(
elif stage:
if version_dict:
raise NotImplementedInGTO(
- "--stage is not implemented for version promotion git tag"
+ "--stage is not implemented for version assignment git tag"
)
if stage_dict:
echo(stage_dict["stage"])
- elif (registration or not promotion) and version_dict:
+ elif (registration or not assignment) and version_dict:
echo(
f"""{EMOJI_OK} Version "{version_dict["name"]}" of artifact """
f""""{version_dict["artifact"]}" was registered"""
)
- elif (promotion or not registration) and stage_dict:
+ elif (assignment or not registration) and stage_dict:
echo(
- f"""{EMOJI_OK} Version "{stage_dict["version"]}" of artifact """
- f""""{stage_dict["artifact"]}" was promoted to "{stage_dict["stage"]}" stage"""
+ f"""{EMOJI_OK} Stage "{stage_dict["stage"]}" was assigned to version"""
+ f'''"{stage_dict["version"]}" of artifact "{stage_dict["artifact"]}"'''
)
@@ -658,6 +661,9 @@ def show(
plain: bool = option_plain,
name_only: bool = option_name_only,
registered_only: bool = option_registered_only,
+ last_stage: bool = Option(
+ False, "--ls", "--last-stage", help="Show only the last stage for each version"
+ ),
):
"""Show the registry state
@@ -682,6 +688,7 @@ def show(
all_branches=all_branches,
all_commits=all_commits,
registered_only=registered_only,
+ last_stage=last_stage,
table=False,
)
if name_only:
@@ -696,6 +703,7 @@ def show(
all_branches=all_branches,
all_commits=all_commits,
registered_only=registered_only,
+ last_stage=last_stage,
table=True,
truncate_hexsha=True,
),
diff --git a/gto/constants.py b/gto/constants.py
index 198e4b3c..0182814e 100644
--- a/gto/constants.py
+++ b/gto/constants.py
@@ -17,7 +17,13 @@
class Action(Enum):
REGISTER = "register"
- PROMOTE = "promote"
+ ASSIGN = "assign"
+
+
+class Event(Enum):
+ COMMIT = "commit"
+ REGISTRATION = "registration"
+ ASSIGNMENT = "assignment"
class VersionSort(Enum):
diff --git a/gto/registry.py b/gto/registry.py
index f91d9bc0..5a94f0c6 100644
--- a/gto/registry.py
+++ b/gto/registry.py
@@ -5,7 +5,7 @@
from git import InvalidGitRepositoryError, NoSuchPathError, Repo
from pydantic import BaseModel
-from gto.base import BasePromotion, BaseRegistryState
+from gto.base import BaseAssignment, BaseRegistryState
from gto.config import (
CONFIG_FILE_NAME,
RegistryConfig,
@@ -124,7 +124,7 @@ def register( # pylint: disable=too-many-locals
if version_args > 1:
raise WrongArgs("Need to specify either version or single bump argument")
ref = self.repo.commit(ref).hexsha
- # TODO: add the same check for other actions, to promote and etc
+ # TODO: add the same check for other actions, to assign 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
@@ -175,12 +175,12 @@ def register( # pylint: disable=too-many-locals
)
return registered_version
- def promote( # pylint: disable=too-many-locals
+ def assign( # pylint: disable=too-many-locals
self,
name,
stage,
- promote_version=None,
- promote_ref=None,
+ version=None,
+ ref=None,
name_version=None,
message=None,
simple=False,
@@ -189,64 +189,62 @@ def promote( # pylint: disable=too-many-locals
stdout=False,
author: Optional[str] = None,
author_email: Optional[str] = None,
- ) -> BasePromotion:
+ ) -> BaseAssignment:
"""Assign stage to specific artifact version"""
assert_name_is_valid(name)
self.config.assert_stage(stage)
- if not (promote_version is None) ^ (promote_ref is None):
+ if not (version is None) ^ (ref is None):
raise WrongArgs("One and only one of (version, ref) must be specified.")
if name_version and skip_registration:
raise WrongArgs(
"You either need to supply version name or skip registration"
)
- if promote_ref:
- promote_ref = self.repo.commit(promote_ref).hexsha
+ if ref:
+ ref = self.repo.commit(ref).hexsha
found_artifact = self.find_artifact(name, create_new=True)
- if promote_version:
+ if version:
found_version = found_artifact.find_version(
- name=promote_version, raise_if_not_found=False
+ name=version, raise_if_not_found=False
)
if not found_version:
- raise WrongArgs(f"Version '{promote_version}' is not registered")
- promote_ref = self.find_commit(name, promote_version)
+ raise WrongArgs(f"Version '{version}' is not registered")
+ ref = self.find_commit(name, version)
else:
- found_version = found_artifact.find_version(commit_hexsha=promote_ref)
+ found_version = found_artifact.find_version(commit_hexsha=ref)
if found_version:
if name_version:
raise WrongArgs(
f"Can't register '{SemVer(name_version).version}', since '{found_version.name}' is registered already at this ref"
)
elif not skip_registration:
- self.register(
- name, version=name_version, ref=promote_ref, stdout=stdout
- )
+ self.register(name, version=name_version, ref=ref, stdout=stdout)
if (
not force
and found_version
- and found_version.stage
- and found_version.stage.stage == stage
+ and found_version.assignments
+ and any(a.stage == stage for a in found_version.assignments)
):
raise WrongArgs(f"Version is already in stage '{stage}'")
# TODO: getting tag name as a result and using it
# is leaking implementation details in base module
# it's roughly ok to have until we add other implementations
- # beside tag-based promotions
- tag = self.stage_manager.promote( # type: ignore
+ # beside tag-based assignments
+ tag = self.stage_manager.assign( # type: ignore
name,
stage,
- ref=promote_ref,
+ ref=ref,
message=message
- or f"Promoting {name} version {promote_version} to stage {stage}",
+ or f"Assigning stage {stage} to artifact {name} version {version}",
simple=simple,
author=author,
author_email=author_email,
)
- promotion = self.stage_manager.check_ref(tag, self.get_state())[name]
+ assignment = self.stage_manager.check_ref(tag, self.get_state())[name]
if stdout:
echo(
- f"Created git tag '{promotion.tag}' that promotes '{promotion.version}'"
+ f"Created git tag '{assignment.tag}' that assigns '{stage}' to '{assignment.version}'"
)
- return promotion
+ return assignment
def _check_ref(self, ref, state):
if ref.startswith("refs/tags/"):
@@ -259,7 +257,7 @@ def _check_ref(self, ref, state):
}
def check_ref(self, ref: str):
- "Find out what was registered/promoted in this ref"
+ "Find out what was registered/assigned in this ref"
state = self.get_state()
return self._check_ref(ref, state)
diff --git a/gto/tag.py b/gto/tag.py
index 180d1ee9..b4ecf69a 100644
--- a/gto/tag.py
+++ b/gto/tag.py
@@ -10,8 +10,8 @@
from .base import (
BaseArtifact,
+ BaseAssignment,
BaseManager,
- BasePromotion,
BaseRegistryState,
BaseVersion,
)
@@ -27,7 +27,7 @@
ActionSign = {
Action.REGISTER: "@",
- Action.PROMOTE: "#",
+ Action.ASSIGN: "#",
}
@@ -42,7 +42,7 @@ def name_tag(
if action == Action.REGISTER:
return f"{name}{ActionSign[action]}{version}"
- if action == Action.PROMOTE:
+ if action == Action.ASSIGN:
if simple:
return f"{name}{ActionSign[action]}{stage}"
if repo is None:
@@ -53,7 +53,7 @@ def name_tag(
if (
parsed
and (parsed[NAME] == name)
- and (parsed[ACTION] == Action.PROMOTE)
+ and (parsed[ACTION] == Action.ASSIGN)
and (NUMBER in parsed)
):
numbers.append(parsed[NUMBER])
@@ -80,8 +80,8 @@ def _parse_register(name: str, raise_on_fail: bool = True):
return {}
-def _parse_promote(name: str, raise_on_fail: bool = True):
- parsed = name.split(ActionSign[Action.PROMOTE])
+def _parse_assign(name: str, raise_on_fail: bool = True):
+ parsed = name.split(ActionSign[Action.ASSIGN])
if (
check_name_is_valid(parsed[0])
and check_name_is_valid(parsed[1])
@@ -89,13 +89,13 @@ def _parse_promote(name: str, raise_on_fail: bool = True):
):
if len(parsed) == 2:
return {
- ACTION: Action.PROMOTE,
+ ACTION: Action.ASSIGN,
NAME: parsed[0],
STAGE: parsed[1],
}
if len(parsed) == 3:
return {
- ACTION: Action.PROMOTE,
+ ACTION: Action.ASSIGN,
NAME: parsed[0],
STAGE: parsed[1],
NUMBER: int(parsed[2]),
@@ -113,8 +113,8 @@ def parse_name(name: str, raise_on_fail: bool = True):
if ActionSign[Action.REGISTER] in name:
return _parse_register(name, raise_on_fail=raise_on_fail)
- if ActionSign[Action.PROMOTE] in name:
- return _parse_promote(name, raise_on_fail=raise_on_fail)
+ if ActionSign[Action.ASSIGN] in name:
+ return _parse_assign(name, raise_on_fail=raise_on_fail)
if raise_on_fail:
raise InvalidTagName(name)
@@ -225,9 +225,9 @@ def version_from_tag(tag: git.Tag) -> BaseVersion:
)
-def promotion_from_tag(
+def assignment_from_tag(
artifact: BaseArtifact, tag: git.Tag, version_required: bool
-) -> BasePromotion:
+) -> BaseAssignment:
mtag = parse_tag(tag)
if version_required:
version = artifact.find_version(
@@ -250,7 +250,7 @@ def promotion_from_tag(
)
)
version = tag.commit.hexsha
- return BasePromotion(
+ return BaseAssignment(
artifact=mtag.name,
version=version,
stage=mtag.stage,
@@ -269,8 +269,8 @@ def index_tag(
mtag = parse_name(tag.tag.tag)
if mtag["action"] == Action.REGISTER:
artifact.add_version(version_from_tag(tag))
- if mtag["action"] == Action.PROMOTE:
- artifact.add_promotion(promotion_from_tag(artifact, tag, version_required))
+ if mtag["action"] == Action.ASSIGN:
+ artifact.add_assignment(assignment_from_tag(artifact, tag, version_required))
return artifact
@@ -331,9 +331,9 @@ def check_ref(self, ref: str, state: BaseRegistryState):
class TagStageManager(TagManager):
- actions: FrozenSet[Action] = frozenset((Action.PROMOTE,))
+ actions: FrozenSet[Action] = frozenset((Action.ASSIGN,))
- def promote(
+ def assign(
self,
name,
stage,
@@ -343,7 +343,7 @@ def promote(
author: Optional[str] = None,
author_email: Optional[str] = None,
) -> str:
- tag = name_tag(Action.PROMOTE, name, stage=stage, repo=self.repo, simple=simple)
+ tag = name_tag(Action.ASSIGN, name, stage=stage, repo=self.repo, simple=simple)
create_tag(
self.repo,
tag,
@@ -360,13 +360,10 @@ def check_ref(self, ref: str, state: BaseRegistryState):
_ = parse_name(ref)[STAGE]
art_name = parse_name(ref)[NAME]
except (KeyError, ValueError, IndexError):
- # logging.warning(
- # "Provided ref doesn't exist or it is not a tag that promotes to an stage"
- # )
return {}
return {
- name: promotion
+ name: assignment
for name, artifact in state.get_artifacts().items()
- for promotion in artifact.stages
- if name == art_name and promotion.tag == tag.name
+ for assignment in artifact.stages
+ if name == art_name and assignment.tag == tag.name
}
diff --git a/tests/conftest.py b/tests/conftest.py
index a4206d35..6ea4d232 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -85,13 +85,13 @@ def showcase(
# bump version automatically
gto.api.register(path, "rf", "HEAD")
- gto.api.promote(path, "nn", "staging", promote_version=nn_vname)
- gto.api.promote(path, "rf", "production", promote_version=rf_vname)
- sleep(1) # this is needed to ensure right order of promotions in later checks
+ gto.api.assign(path, "nn", "staging", version=nn_vname)
+ gto.api.assign(path, "rf", "production", version=rf_vname)
+ sleep(1) # this is needed to ensure right order of assignments in later checks
# the problem is git tags doesn't have miliseconds precision, so we need to wait a bit
- gto.api.promote(path, "rf", "staging", promote_ref="HEAD")
+ gto.api.assign(path, "rf", "staging", ref="HEAD")
sleep(1)
- gto.api.promote(path, "rf", "production", promote_ref=repo.head.ref.commit.hexsha)
+ gto.api.assign(path, "rf", "production", ref=repo.head.ref.commit.hexsha)
sleep(1)
- gto.api.promote(path, "rf", "production", promote_version=rf_vname, force=True)
+ gto.api.assign(path, "rf", "production", version=rf_vname, force=True)
return path, repo, write_file, first_commit, second_commit
diff --git a/tests/test_api.py b/tests/test_api.py
index 2c61bd29..e4894ca8 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -77,6 +77,15 @@ def repo_with_artifact(init_showcase_semver):
return repo, name
+def test_api_info_commands_repo_with_artifact(
+ repo_with_artifact: Tuple[git.Repo, Callable]
+):
+ repo, write_file = repo_with_artifact
+ gto.api.show(repo.working_dir)
+ gto.api.show(repo.working_dir, "new-artifact")
+ gto.api.history(repo.working_dir)
+
+
def test_register(repo_with_artifact):
repo, name = repo_with_artifact
vname1, vname2 = "v1.0.0", "v1.0.1"
@@ -109,7 +118,7 @@ def test_register(repo_with_artifact):
assert latest.author_email == author_email
-def test_promote(repo_with_artifact: Tuple[git.Repo, str]):
+def test_assign(repo_with_artifact: Tuple[git.Repo, str]):
repo, name = repo_with_artifact
stage = "staging"
repo.create_tag("v1.0.0")
@@ -117,19 +126,19 @@ def test_promote(repo_with_artifact: Tuple[git.Repo, str]):
message = "some msg"
author = "GTO"
author_email = "gto@iterative.ai"
- gto.api.promote(
+ gto.api.assign(
repo.working_dir,
name,
stage,
- promote_ref="HEAD",
+ ref="HEAD",
name_version="v0.0.1",
message=message,
author=author,
author_email=author_email,
)
- promotion = gto.api.find_versions_in_stage(repo.working_dir, name, stage)
+ assignment = gto.api.find_versions_in_stage(repo.working_dir, name, stage)
_check_obj(
- promotion,
+ assignment,
dict(
artifact=name,
version="v0.0.1",
@@ -139,39 +148,37 @@ def test_promote(repo_with_artifact: Tuple[git.Repo, str]):
message=message,
commit_hexsha=repo.commit().hexsha,
),
- {"created_at", "promotions", "tag"},
+ {"created_at", "assignments", "tag"},
)
-def test_promote_skip_registration(repo_with_artifact):
+def test_assign_skip_registration(repo_with_artifact: Tuple[git.Repo, str]):
repo, name = repo_with_artifact
stage = "staging"
with pytest.raises(WrongArgs):
- gto.api.promote(
+ gto.api.assign(
repo.working_dir,
name,
stage,
- promote_ref="HEAD",
+ ref="HEAD",
name_version="v0.0.1",
skip_registration=True,
)
- gto.api.promote(
- repo.working_dir, name, stage, promote_ref="HEAD", skip_registration=True
- )
- promotion = gto.api.find_versions_in_stage(repo.working_dir, name, stage)
- assert not SemVer.is_valid(promotion.version)
+ gto.api.assign(repo.working_dir, name, stage, ref="HEAD", skip_registration=True)
+ assignment = gto.api.find_versions_in_stage(repo.working_dir, name, stage)
+ assert not SemVer.is_valid(assignment.version)
-def test_promote_force_is_needed(repo_with_artifact):
+def test_assign_force_is_needed(repo_with_artifact: Tuple[git.Repo, str]):
repo, name = repo_with_artifact
- gto.api.promote(repo, name, "staging", promote_ref="HEAD")
- gto.api.promote(repo, name, "staging", promote_ref="HEAD^1")
+ gto.api.assign(repo, name, "staging", ref="HEAD")
+ gto.api.assign(repo, name, "staging", ref="HEAD^1")
with pytest.raises(WrongArgs):
- gto.api.promote(repo, name, "staging", promote_ref="HEAD")
+ gto.api.assign(repo, name, "staging", ref="HEAD")
with pytest.raises(WrongArgs):
- gto.api.promote(repo, name, "staging", promote_ref="HEAD^1")
- gto.api.promote(repo, name, "staging", promote_ref="HEAD", force=True)
- gto.api.promote(repo, name, "staging", promote_ref="HEAD^1", force=True)
+ gto.api.assign(repo, name, "staging", ref="HEAD^1")
+ gto.api.assign(repo, name, "staging", ref="HEAD", force=True)
+ gto.api.assign(repo, name, "staging", ref="HEAD^1", force=True)
@contextmanager
@@ -215,7 +222,7 @@ def test_check_ref_detailed(repo_with_artifact: Tuple[git.Repo, Callable]):
"author_email": GIT_COMMITTER_EMAIL,
"discovered": False,
"tag": f"{NAME}@{SEMVER}",
- "promotions": [],
+ "assignments": [],
"enrichments": [],
},
skip_keys={"commit_hexsha", "created_at", "message"},
@@ -237,7 +244,7 @@ def test_check_ref_multiple_showcase(showcase):
info = list(gto.api.check_ref(repo, tag.name)[VERSION].values())[0]
assert info.tag == tag.name
- tags = find(repo=repo, action=Action.PROMOTE)
+ tags = find(repo=repo, action=Action.ASSIGN)
for tag in tags:
info = list(gto.api.check_ref(repo, tag.name)[STAGE].values())[0]
assert info.tag == tag.name
@@ -247,15 +254,15 @@ def test_check_ref_catch_the_bug(repo_with_artifact: Tuple[git.Repo, Callable]):
repo, name = repo_with_artifact
NAME = "artifact"
gto.api.register(repo, NAME, "HEAD")
- promotion1 = gto.api.promote(repo, NAME, "staging", promote_ref="HEAD")
- promotion2 = gto.api.promote(repo, NAME, "prod", promote_ref="HEAD")
- promotion3 = gto.api.promote(repo, NAME, "dev", promote_ref="HEAD")
- for promotion, tag in zip(
- [promotion1, promotion2, promotion3],
+ assignment1 = gto.api.assign(repo, NAME, "staging", ref="HEAD")
+ assignment2 = gto.api.assign(repo, NAME, "prod", ref="HEAD")
+ assignment3 = gto.api.assign(repo, NAME, "dev", ref="HEAD")
+ for assignment, tag in zip(
+ [assignment1, assignment2, assignment3],
[f"{NAME}#staging#1", f"{NAME}#prod#2", f"{NAME}#dev#3"],
):
info = gto.api.check_ref(repo, tag)[STAGE][NAME]
- assert info.tag == promotion.tag == tag
+ assert info.tag == assignment.tag == tag
def test_is_not_gto_repo(empty_git_repo):
diff --git a/tests/test_cli.py b/tests/test_cli.py
index c8622c51..3e921f7b 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -121,6 +121,11 @@ def test_commands(showcase):
["-r", path, "rf", "production", "--all"],
"v1.2.4\nv1.2.3\n",
)
+ _check_successful_cmd(
+ "which",
+ ["-r", path, "rf", "staging", "--all"],
+ "v1.2.4\n",
+ )
_check_successful_cmd(
"which",
["-r", path, "rf", "production", "--ref"],
@@ -158,7 +163,7 @@ def test_commands(showcase):
_check_successful_cmd(
"check-ref",
["-r", path, "rf#production#3", "--version"],
- "v1.2.4\n", # since this version was promoted
+ "v1.2.4\n",
)
_check_successful_cmd(
"check-ref",
@@ -167,8 +172,8 @@ def test_commands(showcase):
)
_check_successful_cmd(
"check-ref",
- ["-r", path, "rf@v1.2.4", "--promotion"],
- "", # since this tag doesn't promote to any stage
+ ["-r", path, "rf@v1.2.4", "--assignment"],
+ "", # since this tag doesn't assign any stage
)
_check_successful_cmd(
"check-ref",
@@ -182,8 +187,8 @@ def test_commands(showcase):
)
_check_successful_cmd(
"check-ref",
- ["-r", path, "rf#production#3", "--promotion"],
- '✅ Version "v1.2.4" of artifact "rf" was promoted to "production" stage\n',
+ ["-r", path, "rf#production#3", "--assignment"],
+ '✅ Stage "production" was assigned to version"v1.2.4" of artifact "rf"\n',
)
@@ -285,25 +290,25 @@ def test_register(repo_with_commit: Tuple[git.Repo, Callable]):
)
-def test_promote(repo_with_commit: Tuple[git.Repo, Callable]):
+def test_assign(repo_with_commit: Tuple[git.Repo, Callable]):
repo, write_file = repo_with_commit
_check_successful_cmd(
- "promote",
+ "assign",
["-r", repo.working_dir, "nn1", "prod", "HEAD"],
"Created git tag 'nn1@v0.0.1' that registers a new version\n"
- "Created git tag 'nn1#prod#1' that promotes 'v0.0.1'\n",
+ "Created git tag 'nn1#prod#1' that assigns 'prod' to 'v0.0.1'\n",
)
- # this check depends on the previous promotion
+ # this check depends on the previous assignment
_check_failing_cmd(
- "promote",
+ "assign",
["-r", repo.working_dir, "nn1", "stage", "HEAD", "--version", "v1.0.0"],
"❌ Can't register 'v1.0.0', since 'v0.0.1' is registered already at this ref\n",
)
_check_failing_cmd(
- "promote",
+ "assign",
["-r", repo.working_dir, "nn2", "prod", "HEAD", "--version", "1.0.0"],
"❌ Version '1.0.0' is not valid. Example of valid version: 'v1.0.0'\n",
)
diff --git a/tests/test_config.py b/tests/test_config.py
index cb98bcff..ade24e5c 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -5,7 +5,7 @@
import pytest
from typer.testing import CliRunner
-from gto.api import annotate, get_stages, promote, register
+from gto.api import annotate, assign, get_stages, register
from gto.cli import app
from gto.config import CONFIG_FILE_NAME, check_name_is_valid
from gto.exceptions import UnknownType, ValidationError
@@ -81,14 +81,14 @@ def test_register_incorrect_version(init_repo):
register(init_repo, "model", ref="HEAD", version="###")
-def test_promote_incorrect_name(init_repo):
+def test_assign_incorrect_name(init_repo):
with pytest.raises(ValidationError):
- promote(init_repo, "###", promote_ref="HEAD", stage="dev")
+ assign(init_repo, "###", ref="HEAD", stage="dev")
-def test_promote_incorrect_stage(init_repo):
+def test_assign_incorrect_stage(init_repo):
with pytest.raises(ValidationError):
- promote(init_repo, "model", promote_ref="HEAD", stage="###")
+ assign(init_repo, "model", ref="HEAD", stage="###")
def test_config_is_not_needed(empty_git_repo: Tuple[git.Repo, Callable], request):
@@ -119,9 +119,9 @@ def test_prohibit_config_type(init_repo_prohibit):
@pytest.mark.xfail
-def test_prohibit_config_promote_incorrect_stage(init_repo):
+def test_prohibit_config_assign_incorrect_stage(init_repo):
with pytest.raises(ValidationError):
- promote(init_repo, "model", promote_ref="HEAD", stage="dev")
+ assign(init_repo, "model", ref="HEAD", stage="dev")
def test_empty_config_type(empty_git_repo):
diff --git a/tests/test_registry.py b/tests/test_registry.py
index 3d9154ae..bf53d39b 100644
--- a/tests/test_registry.py
+++ b/tests/test_registry.py
@@ -16,7 +16,7 @@
"commit_hexsha": "d1d973669cade722f2900e75379cee42fe6b0244",
"discovered": False,
"tag": "nn@v0.0.1",
- "promotions": [
+ "assignments": [
{
"artifact": "nn",
"version": "v0.0.1",
@@ -55,7 +55,7 @@
"commit_hexsha": "d1d973669cade722f2900e75379cee42fe6b0244",
"discovered": False,
"tag": "rf@v1.2.3",
- "promotions": [
+ "assignments": [
{
"artifact": "rf",
"version": "v1.2.3",
@@ -100,7 +100,7 @@
"commit_hexsha": "16b7b77f1219ea3c10ae5beeb8473fb49cbd8c13",
"discovered": False,
"tag": "rf@v1.2.4",
- "promotions": [
+ "assignments": [
{
"artifact": "rf",
"version": "v1.2.4",
@@ -172,8 +172,8 @@ def _check_state(appread_state, expected_state, exclude):
appread_state["artifacts"][name]["versions"],
expected_state["artifacts"][name]["versions"],
):
- for a, e in zip(appeared["promotions"], expected["promotions"]):
- _check_dict(a, e, exclude["promotions"])
+ for a, e in zip(appeared["assignments"], expected["assignments"]):
+ _check_dict(a, e, exclude["assignments"])
def test_registry_state_tag_tag(showcase):
@@ -189,11 +189,11 @@ def test_registry_state_tag_tag(showcase):
"author_email",
"created_at",
"commit_hexsha",
- "promotions",
+ "assignments",
"enrichments",
"message",
],
- "promotions": [
+ "assignments": [
"author",
"author_email",
"created_at",
diff --git a/tests/test_showcase.py b/tests/test_showcase.py
index da3436b3..13157912 100644
--- a/tests/test_showcase.py
+++ b/tests/test_showcase.py
@@ -1,7 +1,7 @@
"""TODO: break this file into multiple test/files"""
# pylint: disable=unused-variable, too-many-locals, too-many-statements
import gto
-from gto.base import BaseArtifact, BasePromotion, BaseVersion
+from gto.base import BaseArtifact, BaseAssignment, BaseVersion
from tests.utils import _check_obj
@@ -14,6 +14,12 @@ def test_api(showcase):
second_commit,
) = showcase
+ gto.api.show(repo)
+ gto.api.history(repo)
+ for name in "nn", "rf", "features":
+ gto.api.show(repo, name)
+ gto.api.history(repo, name)
+
artifacts = gto.api._get_state(path).artifacts # pylint: disable=protected-access
assert set(artifacts.keys()) == {"nn", "rf", "features"}
# assert isinstance(artifacts["features"], BaseArtifact)
@@ -33,12 +39,12 @@ def test_api(showcase):
skip_keys_registration = {
"created_at",
- "promotions",
+ "assignments",
"enrichments",
"tag",
"message",
}
- skip_keys_promotion = {"created_at", "tag", "message"}
+ skip_keys_assignment = {"created_at", "tag", "message"}
_check_obj(
nn_version,
@@ -53,10 +59,10 @@ def test_api(showcase):
skip_keys=skip_keys_registration,
)
assert len(nn_artifact.stages) == 1
- nn_promotion = nn_artifact.stages[0]
- assert isinstance(nn_promotion, BasePromotion)
+ nn_assignment = nn_artifact.stages[0]
+ assert isinstance(nn_assignment, BaseAssignment)
_check_obj(
- nn_promotion,
+ nn_assignment,
dict(
artifact="nn",
version="v0.0.1",
@@ -65,7 +71,7 @@ def test_api(showcase):
author_email=author_email,
commit_hexsha=first_commit.hexsha,
),
- skip_keys=skip_keys_promotion,
+ skip_keys=skip_keys_assignment,
)
rf_artifact = artifacts["rf"]
@@ -101,9 +107,9 @@ def test_api(showcase):
)
assert len(rf_artifact.stages) == 4
- assert all(isinstance(p, BasePromotion) for p in rf_artifact.stages)
- rf_l1, _ = rf_ver1.promotions
- rf_l3, rf_l4 = rf_ver2.promotions
+ assert all(isinstance(p, BaseAssignment) for p in rf_artifact.stages)
+ rf_l1, _ = rf_ver1.assignments
+ rf_l3, rf_l4 = rf_ver2.assignments
_check_obj(
rf_l1,
@@ -115,7 +121,7 @@ def test_api(showcase):
author_email=author_email,
commit_hexsha=first_commit.hexsha,
),
- skip_keys=skip_keys_promotion,
+ skip_keys=skip_keys_assignment,
)
_check_obj(
rf_l4,
@@ -127,7 +133,7 @@ def test_api(showcase):
author_email=author_email,
commit_hexsha=second_commit.hexsha,
),
- skip_keys=skip_keys_promotion,
+ skip_keys=skip_keys_assignment,
)
_check_obj(
rf_l3,
@@ -139,5 +145,6 @@ def test_api(showcase):
author_email=author_email,
commit_hexsha=second_commit.hexsha,
),
- skip_keys=skip_keys_promotion,
+ skip_keys=skip_keys_assignment,
)
+ assert gto.api.find_versions_in_stage(repo, "rf", "staging", all=True) == [rf_l3]
diff --git a/tests/test_tag.py b/tests/test_tag.py
index e6ab7ada..38ef3361 100644
--- a/tests/test_tag.py
+++ b/tests/test_tag.py
@@ -16,12 +16,12 @@ def test_name_tag(empty_git_repo):
== f"myartifact{ActionSign[Action.REGISTER]}v1"
)
assert (
- name_tag(Action.PROMOTE, "myartifact", stage="stage", simple=True)
- == f"myartifact{ActionSign[Action.PROMOTE]}stage"
+ name_tag(Action.ASSIGN, "myartifact", stage="stage", simple=True)
+ == f"myartifact{ActionSign[Action.ASSIGN]}stage"
)
assert (
- name_tag(Action.PROMOTE, "myartifact", repo=repo, stage="stage", simple=False)
- == f"myartifact{ActionSign[Action.PROMOTE]}stage{ActionSign[Action.PROMOTE]}1"
+ name_tag(Action.ASSIGN, "myartifact", repo=repo, stage="stage", simple=False)
+ == f"myartifact{ActionSign[Action.ASSIGN]}stage{ActionSign[Action.ASSIGN]}1"
)
@@ -29,12 +29,12 @@ def test_parse_name():
assert parse_name(f"path{ActionSign[Action.REGISTER]}v1.2.3") == dict(
name="path", version="v1.2.3", action=Action.REGISTER
)
- assert parse_name(f"path{ActionSign[Action.PROMOTE]}stage") == dict(
- name="path", action=Action.PROMOTE, stage="stage"
+ assert parse_name(f"path{ActionSign[Action.ASSIGN]}stage") == dict(
+ name="path", action=Action.ASSIGN, stage="stage"
)
assert parse_name(
- f"path{ActionSign[Action.PROMOTE]}stage{ActionSign[Action.PROMOTE]}1"
- ) == dict(name="path", action=Action.PROMOTE, stage="stage", number=1)
+ f"path{ActionSign[Action.ASSIGN]}stage{ActionSign[Action.ASSIGN]}1"
+ ) == dict(name="path", action=Action.ASSIGN, stage="stage", number=1)
@pytest.mark.parametrize(