dcicutils 8.7.0.1b36__py3-none-any.whl → 8.7.0.1b37__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dcicutils/portal_object_utils.py +1 -64
- dcicutils/schema_utils.py +1 -85
- {dcicutils-8.7.0.1b36.dist-info → dcicutils-8.7.0.1b37.dist-info}/METADATA +1 -1
- {dcicutils-8.7.0.1b36.dist-info → dcicutils-8.7.0.1b37.dist-info}/RECORD +7 -7
- {dcicutils-8.7.0.1b36.dist-info → dcicutils-8.7.0.1b37.dist-info}/LICENSE.txt +0 -0
- {dcicutils-8.7.0.1b36.dist-info → dcicutils-8.7.0.1b37.dist-info}/WHEEL +0 -0
- {dcicutils-8.7.0.1b36.dist-info → dcicutils-8.7.0.1b37.dist-info}/entry_points.txt +0 -0
dcicutils/portal_object_utils.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
from functools import lru_cache
|
2
|
-
import
|
3
|
-
from typing import Any, Callable, List, Optional, Tuple, Type, Union
|
2
|
+
from typing import List, Optional, Tuple, Type, Union
|
4
3
|
from dcicutils.portal_utils import Portal
|
5
|
-
from dcicutils.schema_utils import Schema
|
6
4
|
|
7
5
|
PortalObject = Type["PortalObject"] # Forward type reference for type hints.
|
8
6
|
|
@@ -112,64 +110,3 @@ class PortalObject:
|
|
112
110
|
except Exception:
|
113
111
|
pass
|
114
112
|
return None, self.identifying_path
|
115
|
-
|
116
|
-
def compare(self, value: Union[dict, PortalObject], consider_link_to: bool = False) -> dict:
|
117
|
-
"""
|
118
|
-
Compares this Portal object against the given Portal object value; noting differences values of properites
|
119
|
-
which they have in common; and properties which are in this Portal object and not in the given Portal object;
|
120
|
-
we do NOT check the converse, i.e. properties in the given Portal object which are not in this Portal object.
|
121
|
-
Returns a dictionary with a description of the differences. If the given consider_link_to flag is True then
|
122
|
-
TODO.
|
123
|
-
"""
|
124
|
-
def are_properties_equal(property_path: str, property_value_a: Any, property_value_b: Any) -> bool:
|
125
|
-
if property_value_a == property_value_b:
|
126
|
-
return True
|
127
|
-
nonlocal self
|
128
|
-
if (schema := self.schema) and (property_type := Schema.get_property_by_path(schema, property_path)):
|
129
|
-
if link_to := property_type.get("linkTo"):
|
130
|
-
"""
|
131
|
-
This works basically except not WRT sub/super-types (e.g. CellCultureSample vs Sample);
|
132
|
-
this is only preferable as it only requires one Portal GET rather than two, as below.
|
133
|
-
if (a := self._portal.get(f"/{link_to}/{property_value_a}")) and (a.status_code == 200):
|
134
|
-
if a_identifying_paths := PortalObject(self._portal, a.json()).identifying_paths:
|
135
|
-
if f"/{link_to}/{property_value_b}" in a_identifying_paths:
|
136
|
-
return True
|
137
|
-
"""
|
138
|
-
if a := self._portal.get(f"/{link_to}/{property_value_a}", raw=True):
|
139
|
-
if (a.status_code == 200) and (a := a.json()):
|
140
|
-
if b := self._portal.get(f"/{link_to}/{property_value_b}", raw=True):
|
141
|
-
if (b.status_code == 200) and (b := b.json()):
|
142
|
-
return a == b
|
143
|
-
return False
|
144
|
-
return PortalObject._compare(self._data, value.data if isinstance(value, PortalObject) else value,
|
145
|
-
compare=are_properties_equal if consider_link_to else None)
|
146
|
-
|
147
|
-
_ARRAY_KEY_REGULAR_EXPRESSION = re.compile(rf"^({Schema._ARRAY_NAME_SUFFIX_CHAR}\d+)$")
|
148
|
-
|
149
|
-
@staticmethod
|
150
|
-
def _compare(a: dict, b: dict, compare: Optional[Callable] = None, _path: Optional[str] = None) -> dict:
|
151
|
-
def key_to_path(key: str) -> Optional[str]: # noqa
|
152
|
-
nonlocal _path
|
153
|
-
if match := PortalObject._ARRAY_KEY_REGULAR_EXPRESSION.search(key):
|
154
|
-
return f"{_path}{match.group(1)}" if _path else match.group(1)
|
155
|
-
return f"{_path}.{key}" if _path else key
|
156
|
-
def list_to_dictionary(value: list) -> dict: # noqa
|
157
|
-
result = {}
|
158
|
-
for index, item in enumerate(sorted(value)): # ignore array order
|
159
|
-
result[f"#{index}"] = item
|
160
|
-
return result
|
161
|
-
diffs = {}
|
162
|
-
for key in a:
|
163
|
-
path = key_to_path(key)
|
164
|
-
if key not in b:
|
165
|
-
diffs[path] = {"value": a[key], "missing_value": True}
|
166
|
-
else:
|
167
|
-
if isinstance(a[key], dict) and isinstance(b[key], dict):
|
168
|
-
diffs.update(PortalObject._compare(a[key], b[key], compare=compare, _path=path))
|
169
|
-
elif isinstance(a[key], list) and isinstance(b[key], list):
|
170
|
-
diffs.update(PortalObject._compare(list_to_dictionary(a[key]),
|
171
|
-
list_to_dictionary(b[key]), compare=compare, _path=path))
|
172
|
-
elif a[key] != b[key]:
|
173
|
-
if not callable(compare) or not compare(path, a[key], b[key]):
|
174
|
-
diffs[path] = {"value": a[key], "differing_value": b[key]}
|
175
|
-
return diffs
|
dcicutils/schema_utils.py
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
import
|
2
|
-
from typing import Any, Dict, List, Optional, Tuple
|
3
|
-
from dcicutils.misc_utils import to_camel_case
|
1
|
+
from typing import Any, Dict, List
|
4
2
|
|
5
3
|
|
6
4
|
class JsonSchemaConstants:
|
@@ -185,85 +183,3 @@ def get_one_of_formats(schema: Dict[str, Any]) -> List[str]:
|
|
185
183
|
for one_of_schema in get_one_of(schema)
|
186
184
|
if get_format(one_of_schema)
|
187
185
|
]
|
188
|
-
|
189
|
-
|
190
|
-
class Schema:
|
191
|
-
|
192
|
-
def __init__(self, schema: dict, schema_type: Optional[str] = None) -> None:
|
193
|
-
self._data = schema if isinstance(schema, dict) else (schema.data if isinstance(schema, Schema) else {})
|
194
|
-
self._type = (isinstance(schema_type, str) and schema_type) or Schema.type_name(self._data.get("title", ""))
|
195
|
-
|
196
|
-
@property
|
197
|
-
def data(self) -> dict:
|
198
|
-
return self._data
|
199
|
-
|
200
|
-
@property
|
201
|
-
def type(self) -> str:
|
202
|
-
return self._type
|
203
|
-
|
204
|
-
@staticmethod
|
205
|
-
def type_name(value: str) -> Optional[str]: # File or other name.
|
206
|
-
if isinstance(value, str) and (value := os.path.basename(value.replace(" ", ""))):
|
207
|
-
return to_camel_case(value[0:dot] if (dot := value.rfind(".")) >= 0 else value)
|
208
|
-
|
209
|
-
def property_by_path(self, property_path: str) -> Optional[dict]:
|
210
|
-
"""
|
211
|
-
Looks for the given property path within this Portal schema and returns its dictionary value.
|
212
|
-
This given property path can be either a simple property name, or a series of dot-separated
|
213
|
-
property names representing nested (object) properties; and/or the property names may be
|
214
|
-
suffixed with a pound (#) characteter, optionally followed by an integer, representing an
|
215
|
-
array type property and its optional array index (this integer, if specified, is ignored
|
216
|
-
for the purposes of this function, but it may have been created by another process/function,
|
217
|
-
for example by PortalObject.compare). If the property is not found then None is returned.
|
218
|
-
"""
|
219
|
-
return Schema.get_property_by_path(self._data, property_path)
|
220
|
-
|
221
|
-
_ARRAY_NAME_SUFFIX_CHAR = "#"
|
222
|
-
_DOTTED_NAME_DELIMITER_CHAR = "."
|
223
|
-
|
224
|
-
@staticmethod
|
225
|
-
def get_property_by_path(schema: dict, property_path: str) -> Optional[dict]:
|
226
|
-
if not isinstance(schema, dict) or not isinstance(property_path, str):
|
227
|
-
return None
|
228
|
-
elif not (schema_properties := schema.get("properties")):
|
229
|
-
return None
|
230
|
-
property_paths = property_path.split(Schema._DOTTED_NAME_DELIMITER_CHAR)
|
231
|
-
for property_index, property_name in enumerate(property_paths):
|
232
|
-
property_name, array_specifiers = Schema._unarrayize_property_name(property_name)
|
233
|
-
if not (property_value := schema_properties.get(property_name)):
|
234
|
-
return None
|
235
|
-
elif (property_type := property_value.get("type")) == "object":
|
236
|
-
property_paths_tail = Schema._DOTTED_NAME_DELIMITER_CHAR.join(property_paths[property_index + 1:])
|
237
|
-
return Schema.get_property_by_path(property_value, property_paths_tail)
|
238
|
-
elif (property_type := property_value.get("type")) == "array":
|
239
|
-
if not array_specifiers:
|
240
|
-
if property_index == len(property_paths) - 1:
|
241
|
-
return property_value
|
242
|
-
return None
|
243
|
-
for array_index in range(len(array_specifiers)):
|
244
|
-
if property_type != "array":
|
245
|
-
return None
|
246
|
-
elif not (array_items := property_value.get("items")):
|
247
|
-
return None
|
248
|
-
property_type = (property_value := array_items).get("type")
|
249
|
-
if property_type == "object":
|
250
|
-
if property_index == len(property_paths) - 1:
|
251
|
-
return property_value
|
252
|
-
property_paths_tail = Schema._DOTTED_NAME_DELIMITER_CHAR.join(property_paths[property_index + 1:])
|
253
|
-
return Schema.get_property_by_path(property_value, property_paths_tail)
|
254
|
-
return property_value
|
255
|
-
|
256
|
-
@staticmethod
|
257
|
-
def _unarrayize_property_name(property_name: str) -> Tuple[str, Optional[List[int]]]:
|
258
|
-
if len(components := (property_name := property_name.strip()).split(Schema._ARRAY_NAME_SUFFIX_CHAR)) < 2:
|
259
|
-
return property_name, None
|
260
|
-
unarrayized_property_name = components[0].strip()
|
261
|
-
array_specifiers = []
|
262
|
-
for component in components[1:]:
|
263
|
-
if component.isdigit():
|
264
|
-
array_specifiers.append(int(component))
|
265
|
-
elif component == "":
|
266
|
-
array_specifiers.append(0)
|
267
|
-
else:
|
268
|
-
return property_name, None
|
269
|
-
return unarrayized_property_name, array_specifiers
|
@@ -44,7 +44,7 @@ dcicutils/log_utils.py,sha256=7pWMc6vyrorUZQf-V-M3YC6zrPgNhuV_fzm9xqTPph0,10883
|
|
44
44
|
dcicutils/misc_utils.py,sha256=bMRWWxdbhuF3PKdCZEH-H4U1ecgT3Nag3EL92D9XGoY,100973
|
45
45
|
dcicutils/obfuscation_utils.py,sha256=fo2jOmDRC6xWpYX49u80bVNisqRRoPskFNX3ymFAmjw,5963
|
46
46
|
dcicutils/opensearch_utils.py,sha256=V2exmFYW8Xl2_pGFixF4I2Cc549Opwe4PhFi5twC0M8,1017
|
47
|
-
dcicutils/portal_object_utils.py,sha256=
|
47
|
+
dcicutils/portal_object_utils.py,sha256=P8KW0ASWhac8u0Ddzt-uuQ2LFKAEwCrb7ORMsQNJ8HI,5199
|
48
48
|
dcicutils/portal_utils.py,sha256=jKYgZUYVdkg6VOs1hsiX4bSULLguOIBJFFRpvvZEklU,26704
|
49
49
|
dcicutils/project_utils.py,sha256=qPdCaFmWUVBJw4rw342iUytwdQC0P-XKpK4mhyIulMM,31250
|
50
50
|
dcicutils/qa_checkers.py,sha256=cdXjeL0jCDFDLT8VR8Px78aS10hwNISOO5G_Zv2TZ6M,20534
|
@@ -52,7 +52,7 @@ dcicutils/qa_utils.py,sha256=TT0SiJWiuxYvbsIyhK9VO4uV_suxhB6CpuC4qPacCzQ,160208
|
|
52
52
|
dcicutils/redis_tools.py,sha256=qkcSNMtvqkpvts-Cm9gWhneK523Q_oHwhNUud1be1qk,7055
|
53
53
|
dcicutils/redis_utils.py,sha256=VJ-7g8pOZqR1ZCtdcjKz3-6as2DMUcs1b1zG6wSprH4,6462
|
54
54
|
dcicutils/s3_utils.py,sha256=LauLFQGvZLfpBJ81tYMikjLd3SJRz2R_FrL1n4xSlyI,28868
|
55
|
-
dcicutils/schema_utils.py,sha256=
|
55
|
+
dcicutils/schema_utils.py,sha256=3Gd9QboOjQ3FHFawerylvYYU8Lor1Ma2pFv4JmezCdg,5501
|
56
56
|
dcicutils/scripts/publish_to_pypi.py,sha256=LFzNHIQK2EXFr88YcfctyA_WKEBFc1ElnSjWrCXedPM,13889
|
57
57
|
dcicutils/scripts/run_license_checker.py,sha256=z2keYnRDZsHQbTeo1XORAXSXNJK5axVzL5LjiNqZ7jE,4184
|
58
58
|
dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19745
|
@@ -66,8 +66,8 @@ dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
|
|
66
66
|
dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
|
67
67
|
dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
|
68
68
|
dcicutils/zip_utils.py,sha256=rnjNv_k6L9jT2SjDSgVXp4BEJYLtz9XN6Cl2Fy-tqnM,2027
|
69
|
-
dcicutils-8.7.0.
|
70
|
-
dcicutils-8.7.0.
|
71
|
-
dcicutils-8.7.0.
|
72
|
-
dcicutils-8.7.0.
|
73
|
-
dcicutils-8.7.0.
|
69
|
+
dcicutils-8.7.0.1b37.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
|
70
|
+
dcicutils-8.7.0.1b37.dist-info/METADATA,sha256=p_b5H768OpaJuXp9IfbDntuVg2MIjyAnjvaZB5T7Vv4,3315
|
71
|
+
dcicutils-8.7.0.1b37.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
|
72
|
+
dcicutils-8.7.0.1b37.dist-info/entry_points.txt,sha256=8wbw5csMIgBXhkwfgsgJeuFcoUc0WsucUxmOyml2aoA,209
|
73
|
+
dcicutils-8.7.0.1b37.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|