clearml-server/apiserver/bll/project/sub_projects.py

199 lines
5.9 KiB
Python
Raw Normal View History

2021-05-03 14:42:10 +00:00
import itertools
from datetime import datetime
from typing import Tuple, Optional, Sequence, Mapping
from apiserver import database
from apiserver.apierrors import errors
2022-05-17 13:06:34 +00:00
from apiserver.database.model import EntityVisibility
2021-05-03 14:42:10 +00:00
from apiserver.database.model.project import Project
name_separator = "/"
def _get_project_depth(project_name: str) -> int:
return len(list(filter(None, project_name.split(name_separator))))
2021-05-03 14:42:10 +00:00
def _validate_project_name(project_name: str, raise_if_empty=True) -> Tuple[str, str]:
2021-05-03 14:42:10 +00:00
"""
Remove redundant '/' characters. Ensure that the project name is not empty
Return the cleaned up project name and location
"""
name_parts = [p.strip() for p in project_name.split(name_separator) if p]
2021-05-03 14:42:10 +00:00
if not name_parts:
if raise_if_empty:
raise errors.bad_request.InvalidProjectName(name=project_name)
return "", ""
2021-05-03 14:42:10 +00:00
return name_separator.join(name_parts), name_separator.join(name_parts[:-1])
2022-03-15 14:28:59 +00:00
def _ensure_project(
company: str, user: str, name: str, creation_params: dict = None
) -> Optional[Project]:
2021-05-03 14:42:10 +00:00
"""
Makes sure that the project with the given name exists
If needed auto-create the project and all the missing projects in the path to it
Return the project
"""
name, location = _validate_project_name(name, raise_if_empty=False)
2021-05-03 14:42:10 +00:00
if not name:
return None
project = _get_writable_project_from_name(company, name)
if project:
return project
now = datetime.utcnow()
project = Project(
id=database.utils.id(),
user=user,
company=company,
created=now,
last_update=now,
name=name,
2022-07-08 14:35:01 +00:00
basename=name.split("/")[-1],
2022-03-15 14:28:59 +00:00
**(creation_params or dict(description="")),
2021-05-03 14:42:10 +00:00
)
2022-03-15 14:28:59 +00:00
parent = _ensure_project(company, user, location, creation_params=creation_params)
2021-05-03 14:42:10 +00:00
_save_under_parent(project=project, parent=parent)
if parent:
parent.update(last_update=now)
return project
def _save_under_parent(project: Project, parent: Optional[Project]):
"""
Save the project under the given parent project or top level (parent=None)
Check that the project location matches the parent name
"""
location, _, _ = project.name.rpartition(name_separator)
if not parent:
if location:
raise ValueError(
f"Project location {location} does not match empty parent name"
)
project.parent = None
project.path = []
project.save()
return
if location != parent.name:
raise ValueError(
f"Project location {location} does not match parent name {parent.name}"
)
project.parent = parent.id
project.path = [*(parent.path or []), parent.id]
project.save()
def _get_writable_project_from_name(
company,
name,
_only: Optional[Sequence[str]] = ("id", "name", "path", "company", "parent"),
) -> Optional[Project]:
"""
Return a project from name. If the project not found then return None
"""
qs = Project.objects(company=company, name=name)
if _only:
qs = qs.only(*_only)
return qs.first()
ProjectsChildren = Mapping[str, Sequence[Project]]
2021-05-03 14:42:10 +00:00
def _get_sub_projects(
2022-05-17 13:06:34 +00:00
project_ids: Sequence[str],
_only: Sequence[str] = ("id", "path"),
search_hidden=True,
2022-07-08 14:35:01 +00:00
allowed_ids: Sequence[str] = None,
) -> ProjectsChildren:
2021-05-03 14:42:10 +00:00
"""
Return the list of child projects of all the levels for the parent project ids
"""
2022-05-17 13:06:34 +00:00
query = dict(path__in=project_ids)
if not search_hidden:
query["system_tags__nin"] = [EntityVisibility.hidden.value]
2022-07-08 14:35:01 +00:00
if allowed_ids:
query["id__in"] = allowed_ids
2022-05-17 13:06:34 +00:00
qs = Project.objects(**query)
2021-05-03 14:42:10 +00:00
if _only:
_only = set(_only) | {"path"}
qs = qs.only(*_only)
subprojects = list(qs)
return {
pid: [s for s in subprojects if pid in (s.path or [])] for pid in project_ids
}
def _ids_with_parents(project_ids: Sequence[str]) -> Sequence[str]:
"""
Return project ids with all the parent projects
"""
projects = Project.objects(id__in=project_ids).only("id", "path")
parent_ids = set(itertools.chain.from_iterable(p.path for p in projects if p.path))
return list({*(p.id for p in projects), *parent_ids})
def _ids_with_children(project_ids: Sequence[str]) -> Sequence[str]:
"""
Return project ids with the ids of all the subprojects
"""
children_ids = Project.objects(path__in=project_ids).scalar("id")
return list({*project_ids, *children_ids})
2021-05-03 14:42:10 +00:00
2021-05-03 14:47:11 +00:00
def _update_subproject_names(
project: Project,
children: Sequence[Project],
2021-05-03 14:47:11 +00:00
old_name: str,
update_path: bool = False,
old_path: Sequence[str] = None,
) -> int:
2021-05-03 14:42:10 +00:00
"""
Update sub project names when the base project name changes
Optionally update the paths
"""
updated = 0
now = datetime.utcnow()
for child in children:
2021-05-03 14:42:10 +00:00
child_suffix = name_separator.join(
child.name.split(name_separator)[len(old_name.split(name_separator)):]
2021-05-03 14:42:10 +00:00
)
updates = {
"name": name_separator.join((project.name, child_suffix)),
"last_update": now,
}
2021-05-03 14:42:10 +00:00
if update_path:
updates["path"] = project.path + child.path[len(old_path):]
2021-05-03 14:42:10 +00:00
updated += child.update(upsert=False, **updates)
return updated
def _reposition_project_with_children(
project: Project, children: Sequence[Project], parent: Project
) -> int:
2021-05-03 14:42:10 +00:00
new_location = parent.name if parent else None
2021-05-03 14:47:11 +00:00
old_name = project.name
old_path = project.path
2021-05-03 14:42:10 +00:00
project.name = name_separator.join(
filter(None, (new_location, project.name.split(name_separator)[-1]))
)
project.last_update = datetime.utcnow()
2021-05-03 14:42:10 +00:00
_save_under_parent(project, parent=parent)
2021-05-03 14:47:11 +00:00
moved = 1 + _update_subproject_names(
project=project,
children=children,
old_name=old_name,
update_path=True,
old_path=old_path,
2021-05-03 14:47:11 +00:00
)
2021-05-03 14:42:10 +00:00
return moved