|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import copy |
| 4 | +import time |
3 | 5 | import typing |
4 | 6 | from typing import Optional |
5 | | -import copy |
6 | 7 |
|
7 | 8 | from .base import BaseServerAPI |
8 | 9 |
|
9 | 10 | if typing.TYPE_CHECKING: |
10 | 11 | from ayon_api.typing import ( |
| 12 | + AttributeSchemaDict, |
11 | 13 | AttributeSchemaDataDict, |
12 | 14 | AttributesSchemaDict, |
13 | 15 | AttributeScope, |
14 | 16 | ) |
15 | 17 |
|
| 18 | +class _AttributesCache: |
| 19 | + _schema = None |
| 20 | + _last_fetch = 0 |
| 21 | + _timeout = 60 |
| 22 | + _attributes_by_type = {} |
| 23 | + |
| 24 | + def reset_schema(self) -> None: |
| 25 | + self._schema = None |
| 26 | + self._last_fetch = 0 |
| 27 | + self._attributes_by_type = {} |
| 28 | + |
| 29 | + def set_timeout(self, timeout: int) -> None: |
| 30 | + self._timeout = timeout |
| 31 | + |
| 32 | + def get_schema(self) -> AttributesSchemaDict: |
| 33 | + return copy.deepcopy(self._schema) |
| 34 | + |
| 35 | + def set_schema(self, schema: AttributesSchemaDict) -> None: |
| 36 | + self._schema = schema |
| 37 | + self._last_fetch = time.time() |
| 38 | + |
| 39 | + def is_valid(self) -> bool: |
| 40 | + if self._schema is None: |
| 41 | + return False |
| 42 | + return time.time() - self._last_fetch < self._timeout |
| 43 | + |
| 44 | + def invalidate(self) -> None: |
| 45 | + if not self.is_valid(): |
| 46 | + self.reset_schema() |
| 47 | + |
| 48 | + def get_attributes_for_type( |
| 49 | + self, entity_type: AttributeScope |
| 50 | + ) -> list[AttributeSchemaDict]: |
| 51 | + attributes = self._attributes_by_type.get(entity_type) |
| 52 | + if attributes is not None: |
| 53 | + return attributes |
| 54 | + |
| 55 | + attributes_schema = self.get_schema() |
| 56 | + if attributes_schema is None: |
| 57 | + raise ValueError("Attributes schema is not cached.") |
| 58 | + |
| 59 | + attributes = [] |
| 60 | + for attr in attributes_schema["attributes"]: |
| 61 | + if entity_type not in attr["scope"]: |
| 62 | + continue |
| 63 | + attributes.append(attr) |
| 64 | + |
| 65 | + self._attributes_by_type[entity_type] = attributes |
| 66 | + return attributes |
| 67 | + |
| 68 | + |
16 | 69 |
|
17 | 70 | class AttributesAPI(BaseServerAPI): |
18 | | - _attributes_schema = None |
19 | | - _entity_type_attributes_cache = {} |
| 71 | + _attributes_cache = _AttributesCache() |
20 | 72 |
|
21 | 73 | def get_attributes_schema( |
22 | 74 | self, use_cache: bool = True |
23 | 75 | ) -> AttributesSchemaDict: |
24 | 76 | if not use_cache: |
25 | | - self.reset_attributes_schema() |
| 77 | + self._attributes_cache.reset_schema() |
| 78 | + else: |
| 79 | + self._attributes_cache.invalidate() |
26 | 80 |
|
27 | | - if self._attributes_schema is None: |
| 81 | + if not self._attributes_cache.is_valid(): |
28 | 82 | result = self.get("attributes") |
29 | 83 | result.raise_for_status() |
30 | | - self._attributes_schema = result.data |
31 | | - return copy.deepcopy(self._attributes_schema) |
| 84 | + self._attributes_cache.set_schema(result.data) |
| 85 | + return self._attributes_cache.get_schema() |
32 | 86 |
|
33 | 87 | def reset_attributes_schema(self) -> None: |
34 | | - self._attributes_schema = None |
35 | | - self._entity_type_attributes_cache = {} |
| 88 | + """Reset attributes schema cache. |
| 89 | +
|
| 90 | + DEPRECATED: |
| 91 | + Use 'reset_attributes_cache' instead. |
| 92 | +
|
| 93 | + """ |
| 94 | + self.log.warning( |
| 95 | + "Used deprecated function 'reset_attributes_schema'." |
| 96 | + " Please use 'reset_attributes_cache' instead." |
| 97 | + ) |
| 98 | + self.reset_attributes_cache() |
| 99 | + |
| 100 | + def reset_attributes_cache(self) -> None: |
| 101 | + self._attributes_cache.reset_schema() |
| 102 | + |
| 103 | + def set_attributes_cache_timeout(self, timeout: int) -> None: |
| 104 | + self._attributes_cache.set_timeout(timeout) |
36 | 105 |
|
37 | 106 | def set_attribute_config( |
38 | 107 | self, |
@@ -63,12 +132,10 @@ def set_attribute_config( |
63 | 132 | position=position, |
64 | 133 | builtin=builtin |
65 | 134 | ) |
66 | | - if response.status_code != 204: |
67 | | - # TODO raise different exception |
68 | | - raise ValueError( |
69 | | - f"Attribute \"{attribute_name}\" was not created/updated." |
70 | | - f" {response.detail}" |
71 | | - ) |
| 135 | + response.raise_for_status( |
| 136 | + f"Attribute \"{attribute_name}\" was not created/updated." |
| 137 | + f" {response.detail}" |
| 138 | + ) |
72 | 139 |
|
73 | 140 | self.reset_attributes_schema() |
74 | 141 |
|
@@ -128,19 +195,14 @@ def get_attributes_for_type( |
128 | 195 | for entered entity type. |
129 | 196 |
|
130 | 197 | """ |
131 | | - attributes = self._entity_type_attributes_cache.get(entity_type) |
132 | | - if attributes is None: |
133 | | - attributes_schema = self.get_attributes_schema() |
134 | | - attributes = {} |
135 | | - for attr in attributes_schema["attributes"]: |
136 | | - if entity_type not in attr["scope"]: |
137 | | - continue |
138 | | - attr_name = attr["name"] |
139 | | - attributes[attr_name] = attr["data"] |
140 | | - |
141 | | - self._entity_type_attributes_cache[entity_type] = attributes |
142 | | - |
143 | | - return copy.deepcopy(attributes) |
| 198 | + # Make sure attributes are cached |
| 199 | + self.get_attributes_schema() |
| 200 | + return { |
| 201 | + attr["name"]: attr["data"] |
| 202 | + for attr in self._attributes_cache.get_attributes_for_type( |
| 203 | + entity_type |
| 204 | + ) |
| 205 | + } |
144 | 206 |
|
145 | 207 | def get_attributes_fields_for_type( |
146 | 208 | self, entity_type: AttributeScope |
|
0 commit comments