Coretex
tag.py
1 # Copyright (C) 2023 Coretex LLC
2 
3 # This file is part of Coretex.ai
4 
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
14 
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
17 
18 from enum import IntEnum
19 from typing import Optional, Dict, Any
20 from abc import abstractmethod, ABC
21 
22 import re
23 import random
24 import logging
25 
26 from ..networking import networkManager, NetworkRequestError
27 
28 
29 class EntityTagType(IntEnum):
30 
31  model = 1
32  dataset = 2
33 
34 
35 class Taggable(ABC):
36 
37  id: int
38  projectId: int
39 
40  @property
41  @abstractmethod
42  def entityTagType(self) -> EntityTagType:
43  pass
44 
45  def _getTagId(self, tagName: str) -> Optional[int]:
46  parameters = {
47  "name": tagName,
48  "type": self.entityTagType.value,
49  "project_id": self.projectId
50  }
51 
52  response = networkManager.get("tag", parameters)
53  if response.hasFailed():
54  raise NetworkRequestError(response, "Failed to check existing tags")
55 
56  tags = response.getJson(dict).get("data")
57  if not isinstance(tags, list):
58  raise NetworkRequestError(response, f"Field \"data\" from tag response must be dict, but got {type(tags)} instead")
59 
60  if len(tags) == 0:
61  return None
62 
63  if not isinstance(tags[0], dict):
64  raise NetworkRequestError(response, f"Tag object from response must be dict, but got {type(tags[0])} instead")
65 
66  tagId = tags[0].get("id")
67  if not isinstance(tagId, int):
68  raise NetworkRequestError(response, f"Tag object from response must have field id of type int, but got {type(tagId)} instead")
69 
70  return tagId
71 
72 
73  def addTag(self, tag: str, color: Optional[str] = None) -> None:
74  """
75  Add a tag to this entity
76 
77  Parameters
78  ----------
79  tag : str
80  name of the tag
81  color : Optional[str]
82  a hexadecimal color code for the new tag\n
83  if tag already exists in project, this will be ignored\n
84  if left empty and tag does not already exist, a random color will be picked
85 
86  Raises
87  ------
88  ValueError
89  if tag name or color are invalid
90  NetworkRequestError
91  if request to add tag failed
92  """
93 
94  if re.match(r"^[a-z0-9-]{1,30}$", tag) is None:
95  raise ValueError(">> [Coretex] Tag has to be alphanumeric")
96 
97  if color is None:
98  color = f"#{random.randint(0, 0xFFFFFF):06x}"
99  else:
100  if re.match(r"^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$", color) is None:
101  raise ValueError(">> [Coretex] Tag color has to follow hexadecimal color code")
102 
103  tags: Dict[str, Any] = {}
104 
105  tagId = self._getTagId(tag)
106  if tagId is not None:
107  tags["existing"] = [tagId]
108  else:
109  tags["new"] = [{
110  "name": tag,
111  "color": color
112  }]
113 
114  parameters = {
115  "entity_id": self.id,
116  "type": self.entityTagType.value,
117  "tags": tags
118  }
119 
120  response = networkManager.post("tag/entity", parameters)
121  if response.hasFailed():
122  raise NetworkRequestError(response, "Failed to create tag")
123 
124  def removeTag(self, tag: str) -> None:
125  """
126  Remove tag with provided name from the entity
127 
128  Parameters
129  ----------
130  tag : str
131  name of the tag
132 
133  Raises
134  ------
135  NetworkRequestError
136  if tag removal request failed
137  """
138 
139  tagId = self._getTagId(tag)
140  if tagId is None:
141  logging.error(f">> [Coretex] Tag \"{tag}\" not found on entity id {self.id}")
142  return
143 
144  parameters = {
145  "entity_id": self.id,
146  "tag_id": tagId,
147  "type": self.entityTagType.value
148  }
149 
150  response = networkManager.post("tag/remove", parameters)
151  if response.hasFailed():
152  raise NetworkRequestError(response, "Failed to remove tag")