Add Model query interface

This commit is contained in:
allegroai 2021-04-10 22:18:58 +03:00
parent 5ee6425b4f
commit adf199b43b
3 changed files with 129 additions and 7 deletions

View File

@ -6,6 +6,7 @@ import os
import re import re
import sys import sys
from copy import copy from copy import copy
from datetime import datetime
from enum import Enum from enum import Enum
from multiprocessing import RLock from multiprocessing import RLock
from operator import itemgetter from operator import itemgetter
@ -1703,6 +1704,20 @@ class Task(IdObjectBase, AccessMixin, SetupUploadMixin):
except Exception: except Exception:
return None, None return None, None
def _get_last_update(self):
# type: () -> (Optional[datetime])
if self._offline_mode:
return None
# noinspection PyBroadException
try:
all_tasks = self.send(
tasks.GetAllRequest(id=[self.id], only_fields=['last_update']),
).response.tasks
return all_tasks[0].last_update
except Exception:
return None
def _reload_last_iteration(self): def _reload_last_iteration(self):
# type: () -> () # type: () -> ()
# noinspection PyBroadException # noinspection PyBroadException

View File

@ -8,12 +8,12 @@ import six
from typing import List, Dict, Union, Optional, Mapping, TYPE_CHECKING, Sequence from typing import List, Dict, Union, Optional, Mapping, TYPE_CHECKING, Sequence
from .backend_api import Session from .backend_api import Session
from .backend_api.services import models from .backend_api.services import models, projects
from pathlib2 import Path from pathlib2 import Path
from .utilities.config import config_dict_to_text, text_to_config_dict from .utilities.config import config_dict_to_text, text_to_config_dict
from .backend_interface.util import validate_dict, get_single_result, mutually_exclusive from .backend_interface.util import validate_dict, get_single_result, mutually_exclusive, exact_match_regex
from .debugging.log import get_logger from .debugging.log import get_logger
from .storage.cache import CacheManager from .storage.cache import CacheManager
from .storage.helper import StorageHelper from .storage.helper import StorageHelper
@ -439,6 +439,71 @@ class Model(BaseModel):
def _get_model_data(self): def _get_model_data(self):
return self._get_base_model().data return self._get_base_model().data
@classmethod
def query_models(
cls,
project_name=None, # type: Optional[str]
model_name=None, # type: Optional[str]
tags=None, # type: Optional[Sequence[str]]
only_published=False, # type: bool
include_archived=False, # type: bool
max_results=None, # type: Optional[int]
):
# type: (...) -> List[Model]
"""
Return Model objects from the project artifactory.
Filter based on project-name / model-name / tags.
List is always returned sorted by descending last update time (i.e. latest model is the first in the list)
:param project_name: Optional, filter based project name string, if not given query models from all projects
:param model_name: Optional Model name as shown in the model artifactory
:param tags: Optional filter models based on list of tags, example: ['production', 'verified', '-qa']
Notice use '-' prefix to filter out tags.
:param only_published: If True only return published models.
:param include_archived: If True return archived models.
:param max_results: Optional return the last X models,
sorted by last update time (from the most recent to the least).
:return: ModeList of Models objects
"""
if project_name:
# noinspection PyProtectedMember
res = _Model._get_default_session().send(
projects.GetAllRequest(
name=exact_match_regex(project_name),
only_fields=['id', 'name', 'last_update']
)
)
project = get_single_result(entity='project', query=project_name, results=res.response.projects)
else:
project = None
only_fields = ['id', 'created', 'system_tags']
# noinspection PyProtectedMember
res = _Model._get_default_session().send(
models.GetAllRequest(
project=[project.id] if project else None,
name=model_name or None,
only_fields=only_fields,
tags=tags or None,
system_tags=["-" + cls._archived_tag] if not include_archived else None,
ready=True if only_published else None,
order_by=['-created'],
page=0 if max_results else None,
page_size=max_results or None,
)
)
if not res.response.models:
return []
return [Model(model_id=m.id) for m in res.response.models]
@property
def id(self):
# type: () -> str
return self._base_model_id if self._base_model_id else super(Model, self).id
class InputModel(Model): class InputModel(Model):
""" """
@ -712,12 +777,26 @@ class InputModel(Model):
m._data.labels = label_enumeration m._data.labels = label_enumeration
return this_model return this_model
def __init__(self, model_id): def __init__(self, model_id=None, name=None, project=None, tags=None, only_published=False):
# type: (str) -> None # type: (Optional[str], Optional[str], Optional[str], Optional[Sequence[str]], bool) -> None
""" """
:param str model_id: The ClearML Id (system UUID) of the input model whose metadata the **ClearML Server** Load a model from the Model artifactory,
(backend) stores. based on model_id (uuid) or a model name/projects/tags combination.
:param model_id: The ClearML Id (system UUID) of the input model whose metadata the **ClearML Server**
(backend) stores. If provided all other arguments are ignored
:param name: Model name to search and load
:param project: Model project name to search model in
:param tags: Model tags list to filter by
:param only_published: If True filter out non-published (draft) models
""" """
if not model_id:
models = self.query_models(
project_name=project, model_name=name, tags=tags, only_published=only_published)
if not models:
raise ValueError("Could not locate model with project={} name={} tags={} published={}".format(
project, name, tags, only_published))
model_id = models[0].id
super(InputModel, self).__init__(model_id) super(InputModel, self).__init__(model_id)
@property @property

View File

@ -19,7 +19,7 @@ class ReadOnlyDict(dict):
class Logs: class Logs:
_logs_instances = [] _logs_instances = []
def __init__(self, data={}): def __init__(self, data=None):
self._data = data or {} self._data = data or {}
self._logs_instances.append(self) self._logs_instances.append(self)
@ -124,3 +124,31 @@ def merge_dicts(dict1, dict2):
else: else:
dict1[k] = dict2[k] dict1[k] = dict2[k]
return dict1 return dict1
def hocon_quote_key(a_dict):
""" Recursively quote key with '.' to \"key\" """
if not isinstance(a_dict, dict):
return a_dict
# preserve dict type
new_dict = type(a_dict)()
for v, k in a_dict.items():
if isinstance(k, str) and '.' in k:
new_dict['"{}"'.format(k)] = hocon_quote_key(v)
else:
new_dict[k] = v
return new_dict
def hocon_unquote_key(a_dict):
""" Recursively unquote \"key\" with '.' to key """
if not isinstance(a_dict, dict):
return a_dict
# preserve dict type
new_dict = type(a_dict)()
for v, k in a_dict.items():
if isinstance(k, str) and k[0] == '"' and k[-1] == '"' and '.' in k:
new_dict[k[1:-1]] = hocon_unquote_key(v)
else:
new_dict[k] = v
return new_dict