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(