fmu-sumo 2.4.10__py3-none-any.whl → 2.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '2.4.10'
21
- __version_tuple__ = version_tuple = (2, 4, 10)
31
+ __version__ = version = '2.6.0'
32
+ __version_tuple__ = version_tuple = (2, 6, 0)
33
+
34
+ __commit_id__ = commit_id = None
@@ -21,7 +21,7 @@ class Child(Document):
21
21
  self._sumo = sumo
22
22
  self._blob = blob
23
23
 
24
- def __repr__(self):
24
+ def __str__(self):
25
25
  if self.stage == "case" and self.__class__.__name__ != "Case":
26
26
  return (
27
27
  f"<{self.__class__.__name__}: {self.name} {self.uuid}(uuid) "
@@ -46,7 +46,10 @@ class Child(Document):
46
46
  f"in asset {self.asset}>"
47
47
  )
48
48
  else:
49
- return super().__repr__()
49
+ return super().__str__()
50
+
51
+ def __repr__(self):
52
+ return self.__str__()
50
53
 
51
54
  @property
52
55
  def blob(self) -> BytesIO:
@@ -1,6 +1,5 @@
1
1
  """Contains class for one document"""
2
2
 
3
- import json
4
3
  import re
5
4
  from typing import Any, Dict, List, Union
6
5
 
@@ -20,14 +19,14 @@ class Document:
20
19
  self._metadata = metadata["_source"]
21
20
 
22
21
  def __str__(self):
23
- return f"{json.dumps(self.metadata, indent=4)}"
24
-
25
- def __repr__(self):
26
22
  return (
27
23
  f"<{self.__class__.__name__}: {self.name} {self.uuid}(uuid) "
28
24
  f"in asset {self.asset}>"
29
25
  )
30
26
 
27
+ def __repr__(self):
28
+ return self.__str__()
29
+
31
30
  @property
32
31
  def uuid(self):
33
32
  """Return uuid
@@ -272,3 +272,56 @@ class Metrics:
272
272
  "percentiles", field=field, percents=percents
273
273
  )
274
274
  )["values"]
275
+
276
+ def _fnv1a_script(self, field):
277
+ return {
278
+ "init_script": """
279
+ state.h = state.count = state.total = 0L;
280
+ """,
281
+ "map_script": f"""
282
+ state.total++;
283
+ if (doc['{field}'].size() == 0) return;
284
+ def s = doc.get('{field}').value;
285
+ long h = -3750763034362895579L;
286
+ for (int i = 0; i < s.length(); i++) {{
287
+ h ^= (long) s.charAt(i);
288
+ h *= 1099511628211L;
289
+ }}
290
+ state.h ^= h;
291
+ state.count++;
292
+ """,
293
+ "combine_script": """
294
+ return state;
295
+ """,
296
+ "reduce_script": """
297
+ long h = 0, c = 0, t = 0;
298
+ for (st in states) {
299
+ h ^= st.h; c += st.count; t += st.total
300
+ }
301
+ return ['checksum': Long.toHexString(h), 'docs_in_checksum': c, 'docs_total': t];
302
+ """,
303
+ }
304
+
305
+ def fnv1a(self, field):
306
+ """Compute the 64-bit FNV-1a checksum for field over the current set of objects.
307
+
308
+ Arguments:
309
+ - field (str): the name of a property in the metadata.
310
+
311
+ Returns:
312
+ - a dict with the keys "docs_all", "docs_seen" and "xor_fnv64_hex".
313
+ """
314
+ return self._aggregate("scripted_metric", **self._fnv1a_script(field))
315
+
316
+ async def fnv1a_async(self, field):
317
+ """Compute the 64-bit FNV-1a checksum for field over the current set of objects.
318
+
319
+ Arguments:
320
+ - field (str): the name of a property in the metadata.
321
+
322
+ Returns:
323
+ - a dict with the keys "docs_all", "docs_seen" and "xor_fnv64_hex".
324
+ """
325
+ return await self._aggregate_async(
326
+ "scripted_metric", **self._fnv1a_script(field)
327
+ )
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  import warnings
5
4
  from datetime import datetime
6
5
  from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union
@@ -299,14 +298,6 @@ class SearchContext:
299
298
  return
300
299
 
301
300
  def __str__(self):
302
- length = len(self)
303
- if length == 0:
304
- return "None"
305
- else:
306
- preview = [self[i].metadata for i in range(min(5, length))]
307
- return f"Data Preview:\n{json.dumps(preview, indent=4)}"
308
-
309
- def __repr__(self):
310
301
  cls = self.__class__.__name__
311
302
  length = len(self)
312
303
  if length == 0:
@@ -317,6 +308,9 @@ class SearchContext:
317
308
  else:
318
309
  return f"<{cls}: {length} objects of types {self.classes}>"
319
310
 
311
+ def __repr__(self):
312
+ return self.__str__()
313
+
320
314
  @property
321
315
  def _query(self):
322
316
  must = self._must[:]
@@ -581,6 +575,66 @@ class SearchContext:
581
575
  self._cache.clear()
582
576
  return self
583
577
 
578
+ def _ensemble_or_realization_query(self, uuid: str) -> dict:
579
+ return {
580
+ "query": {
581
+ "bool": {
582
+ "minimum_should_match": 1,
583
+ "should": [
584
+ {"term": {"fmu.ensemble.uuid.keyword": uuid}},
585
+ {"term": {"fmu.iteration.uuid.keyword": uuid}},
586
+ {"term": {"fmu.realization.uuid.keyword": uuid}},
587
+ ],
588
+ }
589
+ },
590
+ "size": 1,
591
+ "_source": {
592
+ "includes": [
593
+ "$schema",
594
+ "class",
595
+ "source",
596
+ "version",
597
+ "access",
598
+ "masterdata",
599
+ "fmu.case",
600
+ "fmu.iteration",
601
+ "fmu.ensemble",
602
+ "fmu.realization",
603
+ ],
604
+ },
605
+ }
606
+
607
+ def _patch_ensemble_or_realization(self, uuid, hits):
608
+ if len(hits) == 1:
609
+ obj = hits[0]["_source"]
610
+ if obj["fmu"]["ensemble"]["uuid"] == uuid:
611
+ obj["class"] = "ensemble"
612
+ elif obj["fmu"]["iteration"]["uuid"] == uuid:
613
+ obj["class"] = "iteration"
614
+ elif obj["fmu"]["realization"]["uuid"] == uuid:
615
+ obj["class"] = "realization"
616
+ if (
617
+ obj["class"] in ["iteration", "ensemble"]
618
+ and "realization" in obj["fmu"]
619
+ ):
620
+ del obj["fmu"]["realization"]
621
+
622
+ def _get_ensemble_or_realization(self, uuid: str) -> List[Dict]:
623
+ query = self._ensemble_or_realization_query(uuid)
624
+ res = self._sumo.post("/search", json=query)
625
+ hits = res.json()["hits"]["hits"]
626
+ self._patch_ensemble_or_realization(uuid, hits)
627
+ return hits
628
+
629
+ async def _get_ensemble_or_realization_async(
630
+ self, uuid: str
631
+ ) -> List[Dict]:
632
+ query = self._ensemble_or_realization_query(uuid)
633
+ res = await self._sumo.post_async("/search", json=query)
634
+ hits = res.json()["hits"]["hits"]
635
+ self._patch_ensemble_or_realization(uuid, hits)
636
+ return hits
637
+
584
638
  def get_object(self, uuid: str) -> objects.Document:
585
639
  """Get metadata object by uuid
586
640
 
@@ -603,7 +657,10 @@ class SearchContext:
603
657
  hits = res.json()["hits"]["hits"]
604
658
 
605
659
  if len(hits) == 0:
606
- raise Exception(f"Document not found: {uuid}")
660
+ hits = self._get_ensemble_or_realization(uuid)
661
+ if len(hits) == 0:
662
+ raise Exception(f"Document not found: {uuid}")
663
+ pass
607
664
  obj = hits[0]
608
665
  self._cache.put(uuid, obj)
609
666
 
@@ -632,7 +689,10 @@ class SearchContext:
632
689
  hits = res.json()["hits"]["hits"]
633
690
 
634
691
  if len(hits) == 0:
635
- raise Exception(f"Document not found: {uuid}")
692
+ hits = await self._get_ensemble_or_realization_async(uuid)
693
+ if len(hits) == 0:
694
+ raise Exception(f"Document not found: {uuid}")
695
+ pass
636
696
  obj = hits[0]
637
697
  self._cache.put(uuid, obj)
638
698
 
@@ -869,6 +929,26 @@ class SearchContext:
869
929
  """
870
930
  return self.get_field_values(field)
871
931
 
932
+ def match_field_values(self, field: str, patterns: list[str]) -> list[str]:
933
+ query = {
934
+ "query": self._query,
935
+ "size": 0,
936
+ "aggs": {
937
+ "values": {
938
+ "terms": {
939
+ "field": field,
940
+ "include": "|".join(patterns),
941
+ "size": 1000,
942
+ }
943
+ }
944
+ },
945
+ }
946
+ res = self._sumo.post("/search", json=query).json()
947
+ return [
948
+ bucket["key"]
949
+ for bucket in res["aggregations"]["values"]["buckets"]
950
+ ]
951
+
872
952
  async def get_field_values_and_counts_async(
873
953
  self, field: str
874
954
  ) -> Dict[str, int]:
@@ -918,6 +998,28 @@ class SearchContext:
918
998
  """
919
999
  return await self.get_field_values_async(field)
920
1000
 
1001
+ async def match_field_values_async(
1002
+ self, field: str, patterns: list[str]
1003
+ ) -> list[str]:
1004
+ query = {
1005
+ "query": self._query,
1006
+ "size": 0,
1007
+ "aggs": {
1008
+ "values": {
1009
+ "terms": {
1010
+ "field": field,
1011
+ "include": "|".join(patterns),
1012
+ "size": 1000,
1013
+ }
1014
+ }
1015
+ },
1016
+ }
1017
+ res = (await self._sumo.post_async("/search", json=query)).json()
1018
+ return [
1019
+ bucket["key"]
1020
+ for bucket in res["aggregations"]["values"]["buckets"]
1021
+ ]
1022
+
921
1023
  _timestamp_query = {
922
1024
  "bool": {
923
1025
  "must": [{"exists": {"field": "data.time.t0"}}],
@@ -1012,6 +1114,22 @@ class SearchContext:
1012
1114
  )
1013
1115
  return objects.Realizations(self, uuids)
1014
1116
 
1117
+ @property
1118
+ def reference_realizations(self):
1119
+ """Reference realizations from current selection."""
1120
+ return self.filter(
1121
+ cls="realization",
1122
+ complex={"term": {"fmu.realization.is_reference": True}},
1123
+ )
1124
+
1125
+ @property
1126
+ async def reference_realizations_async(self):
1127
+ """Reference realizations from current selection."""
1128
+ return self.filter(
1129
+ cls="realization",
1130
+ complex={"term": {"fmu.realization.is_reference": True}},
1131
+ )
1132
+
1015
1133
  @property
1016
1134
  def template_paths(self) -> List[str]:
1017
1135
  return {obj.template_path for obj in self}
@@ -1236,24 +1354,6 @@ class SearchContext:
1236
1354
  """
1237
1355
  return await self._get_object_by_class_and_uuid_async("case", uuid)
1238
1356
 
1239
- def _iteration_query(self, uuid):
1240
- return {
1241
- "query": {"term": {"fmu.iteration.uuid.keyword": {"value": uuid}}},
1242
- "size": 1,
1243
- "_source": {
1244
- "includes": [
1245
- "$schema",
1246
- "class",
1247
- "source",
1248
- "version",
1249
- "access",
1250
- "masterdata",
1251
- "fmu.case",
1252
- "fmu.iteration",
1253
- ],
1254
- },
1255
- }
1256
-
1257
1357
  def get_iteration_by_uuid(self, uuid: str) -> objects.Iteration:
1258
1358
  """Get iteration object by uuid
1259
1359
 
@@ -1262,23 +1362,9 @@ class SearchContext:
1262
1362
 
1263
1363
  Returns: iteration object
1264
1364
  """
1265
- try:
1266
- obj = self.get_object(uuid)
1267
- assert isinstance(obj, objects.Iteration)
1268
- return obj
1269
- except Exception:
1270
- res = self._sumo.post(
1271
- "/search", json=self._iteration_query(uuid)
1272
- ).json()
1273
- hits = res["hits"]["hits"]
1274
- if len(hits) == 0:
1275
- raise Exception(f"Document not found: {uuid}")
1276
- obj = hits[0]
1277
- obj["_id"] = uuid
1278
- obj["_source"]["class"] = "iteration"
1279
- ret = self._to_sumo(obj)
1280
- self._cache.put(uuid, ret)
1281
- return ret
1365
+ obj = self.get_object(uuid)
1366
+ assert isinstance(obj, objects.Iteration)
1367
+ return obj
1282
1368
 
1283
1369
  async def get_iteration_by_uuid_async(
1284
1370
  self, uuid: str
@@ -1290,43 +1376,9 @@ class SearchContext:
1290
1376
 
1291
1377
  Returns: iteration object
1292
1378
  """
1293
- try:
1294
- obj = await self.get_object_async(uuid)
1295
- assert isinstance(obj, objects.Iteration)
1296
- return obj
1297
- except Exception:
1298
- res = (
1299
- await self._sumo.post_async(
1300
- "/search", json=self._iteration_query(uuid)
1301
- )
1302
- ).json()
1303
- hits = res["hits"]["hits"]
1304
- if len(hits) == 0:
1305
- raise Exception(f"Document not found: {uuid}")
1306
- obj = hits[0]
1307
- obj["_id"] = uuid
1308
- obj["_source"]["class"] = "iteration"
1309
- ret = self._to_sumo(obj)
1310
- self._cache.put(uuid, ret)
1311
- return ret
1312
-
1313
- def _ensemble_query(self, uuid):
1314
- return {
1315
- "query": {"term": {"fmu.ensemble.uuid.keyword": {"value": uuid}}},
1316
- "size": 1,
1317
- "_source": {
1318
- "includes": [
1319
- "$schema",
1320
- "class",
1321
- "source",
1322
- "version",
1323
- "access",
1324
- "masterdata",
1325
- "fmu.case",
1326
- "fmu.ensemble",
1327
- ],
1328
- },
1329
- }
1379
+ obj = await self.get_object_async(uuid)
1380
+ assert isinstance(obj, objects.Iteration)
1381
+ return obj
1330
1382
 
1331
1383
  def get_ensemble_by_uuid(self, uuid: str) -> objects.Ensemble:
1332
1384
  """Get ensemble object by uuid
@@ -1336,23 +1388,9 @@ class SearchContext:
1336
1388
 
1337
1389
  Returns: ensemble object
1338
1390
  """
1339
- try:
1340
- obj = self.get_object(uuid)
1341
- assert isinstance(obj, objects.Ensemble)
1342
- return obj
1343
- except Exception:
1344
- res = self._sumo.post(
1345
- "/search", json=self._ensemble_query(uuid)
1346
- ).json()
1347
- hits = res["hits"]["hits"]
1348
- if len(hits) == 0:
1349
- raise Exception(f"Document not found: {uuid}")
1350
- obj = hits[0]
1351
- obj["_id"] = uuid
1352
- obj["_source"]["class"] = "ensemble"
1353
- ret = self._to_sumo(obj)
1354
- self._cache.put(uuid, ret)
1355
- return ret
1391
+ obj = self.get_object(uuid)
1392
+ assert isinstance(obj, objects.Ensemble)
1393
+ return obj
1356
1394
 
1357
1395
  async def get_ensemble_by_uuid_async(self, uuid: str) -> objects.Ensemble:
1358
1396
  """Get ensemble object by uuid
@@ -1362,47 +1400,9 @@ class SearchContext:
1362
1400
 
1363
1401
  Returns: ensemble object
1364
1402
  """
1365
- try:
1366
- obj = await self.get_object_async(uuid)
1367
- assert isinstance(obj, objects.Ensemble)
1368
- return obj
1369
- except Exception:
1370
- res = (
1371
- await self._sumo.post_async(
1372
- "/search", json=self._ensemble_query(uuid)
1373
- )
1374
- ).json()
1375
- hits = res["hits"]["hits"]
1376
- if len(hits) == 0:
1377
- raise Exception(f"Document not found: {uuid}")
1378
- obj = hits[0]
1379
- obj["_id"] = uuid
1380
- obj["_source"]["class"] = "ensemble"
1381
- ret = self._to_sumo(obj)
1382
- self._cache.put(uuid, ret)
1383
- return ret
1384
-
1385
- def _realization_query(self, uuid) -> Dict:
1386
- return {
1387
- "query": {
1388
- "term": {"fmu.realization.uuid.keyword": {"value": uuid}}
1389
- },
1390
- "size": 1,
1391
- "_source": {
1392
- "includes": [
1393
- "$schema",
1394
- "class",
1395
- "source",
1396
- "version",
1397
- "access",
1398
- "masterdata",
1399
- "fmu.case",
1400
- "fmu.iteration",
1401
- "fmu.ensemble",
1402
- "fmu.realization",
1403
- ],
1404
- },
1405
- }
1403
+ obj = await self.get_object_async(uuid)
1404
+ assert isinstance(obj, objects.Ensemble)
1405
+ return obj
1406
1406
 
1407
1407
  def get_realization_by_uuid(self, uuid: str) -> objects.Realization:
1408
1408
  """Get realization object by uuid
@@ -1412,21 +1412,9 @@ class SearchContext:
1412
1412
 
1413
1413
  Returns: realization object
1414
1414
  """
1415
- try:
1416
- obj = self.get_object(uuid)
1417
- assert isinstance(obj, objects.Realization)
1418
- return obj
1419
- except Exception:
1420
- res = self._sumo.post(
1421
- "/search", json=self._realization_query(uuid)
1422
- ).json()
1423
- hits = res["hits"]["hits"]
1424
- if len(hits) == 0:
1425
- raise Exception(f"Document not found: {uuid}")
1426
- obj = hits[0]
1427
- obj["_id"] = uuid
1428
- obj["_source"]["class"] = "realization"
1429
- return self._to_sumo(obj)
1415
+ obj = self.get_object(uuid)
1416
+ assert isinstance(obj, objects.Realization)
1417
+ return obj
1430
1418
 
1431
1419
  async def get_realization_by_uuid_async(
1432
1420
  self, uuid: str
@@ -1438,23 +1426,9 @@ class SearchContext:
1438
1426
 
1439
1427
  Returns: realization object
1440
1428
  """
1441
- try:
1442
- obj = await self.get_object_async(uuid)
1443
- assert isinstance(obj, objects.Realization)
1444
- return obj
1445
- except Exception:
1446
- res = (
1447
- await self._sumo.post_async(
1448
- "/search", json=self._realization_query(uuid)
1449
- )
1450
- ).json()
1451
- hits = res["hits"]["hits"]
1452
- if len(hits) == 0:
1453
- raise Exception(f"Document not found: {uuid}")
1454
- obj = hits[0]
1455
- obj["_id"] = uuid
1456
- obj["_source"]["class"] = "realization"
1457
- return self._to_sumo(obj)
1429
+ obj = await self.get_object_async(uuid)
1430
+ assert isinstance(obj, objects.Realization)
1431
+ return obj
1458
1432
 
1459
1433
  def get_surface_by_uuid(self, uuid: str) -> objects.Surface:
1460
1434
  """Get surface object by uuid
@@ -1543,7 +1517,7 @@ class SearchContext:
1543
1517
 
1544
1518
  def __verify_aggregation_operation(
1545
1519
  self, sres
1546
- ) -> Tuple[str, str, str, str]:
1520
+ ) -> Tuple[str, str, str, str, str]:
1547
1521
  tot_hits = sres["hits"]["total"]["value"]
1548
1522
  if tot_hits == 0:
1549
1523
  raise Exception("No matching realizations found.")
@@ -1570,18 +1544,38 @@ class SearchContext:
1570
1544
  "key"
1571
1545
  ]
1572
1546
  classname = sres["aggregations"]["class"]["buckets"][0]["key"]
1573
- return caseuuid, classname, entityuuid, ensemblename
1547
+
1548
+ return caseuuid, classname, entityuuid, ensemblename, tot_hits
1574
1549
 
1575
1550
  def _verify_aggregation_operation(
1576
- self, columns, operation
1551
+ self, columns
1577
1552
  ) -> Tuple[str, str, str, str]:
1578
- assert columns is None or len(columns) == 1, (
1579
- "Exactly one column required for collection aggregation."
1580
- )
1581
1553
  sc = self if columns is None else self.filter(column=columns)
1582
1554
  query = sc.__prepare_verify_aggregation_query()
1583
1555
  sres = sc._sumo.post("/search", json=query).json()
1584
- return sc.__verify_aggregation_operation(sres)
1556
+ caseuuid, classname, entityuuid, ensemblename, tot_hits = (
1557
+ sc.__verify_aggregation_operation(sres)
1558
+ )
1559
+
1560
+ if (
1561
+ classname != "surface"
1562
+ and isinstance(columns, list)
1563
+ and len(columns) == 1
1564
+ ):
1565
+ sc = SearchContext(
1566
+ sumo=self._sumo,
1567
+ ).filter(
1568
+ cls=classname,
1569
+ realization=True,
1570
+ entity=entityuuid,
1571
+ ensemble=ensemblename,
1572
+ )
1573
+
1574
+ if len(sc) != tot_hits:
1575
+ raise Exception(
1576
+ "Filtering on realization is not allowed for table and parameter aggregation."
1577
+ )
1578
+ return caseuuid, classname, entityuuid, ensemblename
1585
1579
 
1586
1580
  def __prepare_aggregation_spec(
1587
1581
  self, caseuuid, classname, entityuuid, ensemblename, operation, columns
@@ -1598,9 +1592,11 @@ class SearchContext:
1598
1592
  spec["columns"] = columns
1599
1593
  return spec
1600
1594
 
1601
- def _aggregate(self, columns=None, operation=None) -> objects.Child:
1595
+ def _aggregate(
1596
+ self, columns=None, operation=None, no_wait=False
1597
+ ) -> objects.Child | httpx.Response:
1602
1598
  caseuuid, classname, entityuuid, ensemblename = (
1603
- self._verify_aggregation_operation(columns, operation)
1599
+ self._verify_aggregation_operation(columns)
1604
1600
  )
1605
1601
  spec = self.__prepare_aggregation_spec(
1606
1602
  caseuuid, classname, entityuuid, ensemblename, operation, columns
@@ -1612,36 +1608,91 @@ class SearchContext:
1612
1608
  print(ex.response.reason_phrase)
1613
1609
  print(ex.response.text)
1614
1610
  raise ex
1611
+ if no_wait:
1612
+ return res
1613
+ # ELSE
1615
1614
  res = self._sumo.poll(res).json()
1616
1615
  return self._to_sumo(res)
1617
1616
 
1618
- def aggregate(self, columns=None, operation=None) -> objects.Child:
1617
+ def aggregate(
1618
+ self, columns=None, operation=None, no_wait=False
1619
+ ) -> objects.Child | httpx.Response:
1620
+ assert columns is None or len(columns) == 1, (
1621
+ "Exactly one column required for collection aggregation."
1622
+ )
1619
1623
  sc = self.filter(realization=True, column=columns)
1620
1624
  if len(sc.hidden) > 0:
1621
- return sc.hidden._aggregate(columns=columns, operation=operation)
1622
- else:
1623
- return sc.visible._aggregate(columns=columns, operation=operation)
1625
+ sc = sc.hidden
1626
+ return sc._aggregate(
1627
+ columns=columns, operation=operation, no_wait=no_wait
1628
+ )
1629
+
1630
+ def batch_aggregate(self, columns=None, operation=None, no_wait=False):
1631
+ """Aggregate one or more columns for the current context.
1632
+
1633
+ Args:
1634
+ columns: list of column names or regular expressions for column names.
1635
+ operation: must be "collection"
1636
+ no_wait: set to True if the client handles polling itself.
1637
+
1638
+ Returns:
1639
+ list of column names that occur in the current context and match the names/patterns.
1640
+ """
1641
+ assert operation == "collection"
1642
+ assert type(columns) is list and len(columns) > 0
1643
+ assert len(columns) < 1000, (
1644
+ "Maximum 1000 columns allowed for a single call to batch_aggregate."
1645
+ )
1646
+ sc = self.filter(realization=True, column=columns)
1647
+ if len(sc.hidden) > 0:
1648
+ sc = sc.hidden
1649
+ res = sc._aggregate(columns=columns, operation=operation, no_wait=True)
1650
+ assert type(res) is httpx.Response
1651
+ if no_wait:
1652
+ return res
1653
+ # ELSE
1654
+ return self._sumo.poll(res)
1624
1655
 
1625
1656
  async def _verify_aggregation_operation_async(
1626
- self, columns, operation
1657
+ self, columns
1627
1658
  ) -> Tuple[str, str, str, str]:
1628
- assert columns is None or len(columns) == 1, (
1629
- "Exactly one column required for collection aggregation."
1630
- )
1631
1659
  sc = self if columns is None else self.filter(column=columns)
1632
1660
  query = sc.__prepare_verify_aggregation_query()
1633
1661
  sres = (await self._sumo.post_async("/search", json=query)).json()
1634
- return sc.__verify_aggregation_operation(sres)
1662
+ caseuuid, classname, entityuuid, ensemblename, tot_hits = (
1663
+ sc.__verify_aggregation_operation(sres)
1664
+ )
1665
+
1666
+ if (
1667
+ classname != "surface"
1668
+ and isinstance(columns, list)
1669
+ and len(columns) == 1
1670
+ ):
1671
+ sc = SearchContext(
1672
+ sumo=self._sumo,
1673
+ ).filter(
1674
+ cls=classname,
1675
+ realization=True,
1676
+ entity=entityuuid,
1677
+ ensemble=ensemblename,
1678
+ )
1679
+
1680
+ tot_reals = await sc.length_async()
1681
+ if tot_reals != tot_hits:
1682
+ raise Exception(
1683
+ "Filtering on realization is not allowed for table and parameter aggregation."
1684
+ )
1685
+ return caseuuid, classname, entityuuid, ensemblename
1635
1686
 
1636
1687
  async def _aggregate_async(
1637
- self, columns=None, operation=None
1638
- ) -> objects.Child:
1688
+ self, columns=None, operation=None, no_wait=False
1689
+ ) -> objects.Child | httpx.Response:
1639
1690
  (
1640
1691
  caseuuid,
1641
1692
  classname,
1642
1693
  entityuuid,
1643
1694
  ensemblename,
1644
- ) = await self._verify_aggregation_operation_async(columns, operation)
1695
+ ) = await self._verify_aggregation_operation_async(columns)
1645
1696
  spec = self.__prepare_aggregation_spec(
1646
1697
  caseuuid, classname, entityuuid, ensemblename, operation, columns
1647
1698
  )
@@ -1652,24 +1703,59 @@ class SearchContext:
1652
1703
  print(ex.response.reason_phrase)
1653
1704
  print(ex.response.text)
1654
1705
  raise ex
1706
+ if no_wait:
1707
+ return res
1708
+ # ELSE
1655
1709
  res = (await self._sumo.poll_async(res)).json()
1656
1710
  return self._to_sumo(res)
1657
1711
 
1658
1712
  async def aggregate_async(
1659
- self, columns=None, operation=None
1660
- ) -> objects.Child:
1713
+ self, columns=None, operation=None, no_wait=False
1714
+ ) -> objects.Child | httpx.Response:
1715
+ assert columns is None or len(columns) == 1, (
1716
+ "Exactly one column required for collection aggregation."
1717
+ )
1661
1718
  sc = self.filter(realization=True, column=columns)
1662
1719
  length_hidden = await sc.hidden.length_async()
1663
1720
  if length_hidden > 0:
1664
- return await sc.hidden._aggregate_async(
1665
- columns=columns, operation=operation
1666
- )
1667
- else:
1668
- return await sc.visible._aggregate_async(
1669
- columns=columns, operation=operation
1670
- )
1721
+ sc = sc.hidden
1722
+ return await sc._aggregate_async(
1723
+ columns=columns, operation=operation, no_wait=no_wait
1724
+ )
1725
+
1726
+ async def batch_aggregate_async(
1727
+ self, columns=None, operation=None, no_wait=False
1728
+ ):
1729
+ """Aggregate one or more columns for the current context.
1730
+
1731
+ Args:
1732
+ columns: list of column names or regular expressions for column names.
1733
+ operation: must be "collection"
1734
+ no_wait: set to True if the client handles polling itself.
1735
+
1736
+ Returns:
1737
+ list of column names that occur in the current context and match the names/patterns.
1738
+ """
1739
+ assert operation == "collection"
1740
+ assert type(columns) is list and len(columns) > 0
1741
+ assert len(columns) < 1000, (
1742
+ "Maximum 1000 columns allowed for a single call to batch_aggregate_async."
1743
+ )
1744
+ sc = self.filter(realization=True, column=columns)
1745
+ if len(sc.hidden) > 0:
1746
+ sc = sc.hidden
1747
+ res = await sc._aggregate_async(
1748
+ columns=columns, operation=operation, no_wait=True
1749
+ )
1750
+ assert type(res) is httpx.Response
1751
+ if no_wait:
1752
+ return res
1753
+ # ELSE
1754
+ return await self._sumo.poll_async(res)
1671
1755
 
1672
- def aggregation(self, column=None, operation=None) -> objects.Child:
1756
+ def aggregation(
1757
+ self, column=None, operation=None, no_wait=False
1758
+ ) -> objects.Child | httpx.Response:
1673
1759
  assert operation is not None
1674
1760
  assert column is None or isinstance(column, str)
1675
1761
  sc = self.filter(aggregation=operation, column=column)
@@ -1691,11 +1777,12 @@ class SearchContext:
1691
1777
  return self.filter(realization=True).aggregate(
1692
1778
  columns=[column] if column is not None else None,
1693
1779
  operation=operation,
1780
+ no_wait=no_wait,
1694
1781
  )
1695
1782
 
1696
1783
  async def aggregation_async(
1697
- self, column=None, operation=None
1698
- ) -> objects.Child:
1784
+ self, column=None, operation=None, no_wait=False
1785
+ ) -> objects.Child | httpx.Response:
1699
1786
  assert operation is not None
1700
1787
  assert column is None or isinstance(column, str)
1701
1788
  sc = self.filter(aggregation=operation, column=column)
@@ -1720,6 +1807,7 @@ class SearchContext:
1720
1807
  return await self.filter(realization=True).aggregate_async(
1721
1808
  columns=[column] if column is not None else None,
1722
1809
  operation=operation,
1810
+ no_wait=no_wait,
1723
1811
  )
1724
1812
 
1725
1813
  @deprecation.deprecated(
@@ -23,13 +23,16 @@ class Ensemble(Document, SearchContext):
23
23
  )
24
24
  pass
25
25
 
26
- def __repr__(self):
26
+ def __str__(self):
27
27
  return (
28
28
  f"<{self.__class__.__name__}: {self.name} {self.uuid}(uuid) "
29
29
  f"in case {self.casename} "
30
30
  f"in asset {self.asset}>"
31
31
  )
32
32
 
33
+ def __repr__(self):
34
+ return self.__str__()
35
+
33
36
  @property
34
37
  def field(self) -> str:
35
38
  """Case field"""
@@ -74,3 +77,23 @@ class Ensemble(Document, SearchContext):
74
77
  def uuid(self) -> str:
75
78
  """FMU ensemble uuid"""
76
79
  return self.get_property("fmu.ensemble.uuid")
80
+
81
+ @property
82
+ def reference_realizations(self):
83
+ """Reference realizations in ensemble. If none, return
84
+ realizations 0 and 1, if they exist."""
85
+ sc = super().reference_realizations
86
+ if len(sc) > 0:
87
+ return sc
88
+ else:
89
+ return self.filter(realization=[0, 1]).realizations
90
+
91
+ @property
92
+ async def reference_realizations_async(self):
93
+ """Reference realizations in ensemble. If none, return
94
+ realizations 0 and 1, if they exist."""
95
+ sc = await super().reference_realizations_async
96
+ if await sc.length_async() > 0:
97
+ return sc
98
+ else:
99
+ return self.filter(realization=[0, 1]).realizations
@@ -1,6 +1,6 @@
1
1
  """Module for searchcontext for collection of ensembles."""
2
2
 
3
- from typing import Dict, List
3
+ from typing import List
4
4
 
5
5
  from ._search_context import SearchContext
6
6
 
@@ -25,42 +25,6 @@ class Ensembles(SearchContext):
25
25
  async def _maybe_prefetch_async(self, index):
26
26
  return
27
27
 
28
- def get_object(self, uuid: str) -> Dict:
29
- """Get metadata object by uuid
30
-
31
- Args:
32
- uuid (str): uuid of metadata object
33
- select (List[str]): list of metadata fields to return
34
-
35
- Returns:
36
- Dict: a metadata object
37
- """
38
- obj = self._cache.get(uuid)
39
- if obj is None:
40
- obj = self.get_ensemble_by_uuid(uuid)
41
- self._cache.put(uuid, obj)
42
- pass
43
-
44
- return obj
45
-
46
- async def get_object_async(self, uuid: str) -> Dict:
47
- """Get metadata object by uuid
48
-
49
- Args:
50
- uuid (str): uuid of metadata object
51
- select (List[str]): list of metadata fields to return
52
-
53
- Returns:
54
- Dict: a metadata object
55
- """
56
-
57
- obj = self._cache.get(uuid)
58
- if obj is None:
59
- obj = await self.get_ensemble_by_uuid_async(uuid)
60
- self._cache.put(uuid, obj)
61
-
62
- return obj
63
-
64
28
  def filter(self, **kwargs):
65
29
  sc = super().filter(**kwargs)
66
30
  uuids = sc.get_field_values("fmu.ensemble.uuid.keyword")
@@ -23,13 +23,16 @@ class Iteration(Document, SearchContext):
23
23
  )
24
24
  pass
25
25
 
26
- def __repr__(self):
26
+ def __str__(self):
27
27
  return (
28
28
  f"<{self.__class__.__name__}: {self.name} {self.uuid}(uuid) "
29
29
  f"in case {self.casename} "
30
30
  f"in asset {self.asset}>"
31
31
  )
32
32
 
33
+ def __repr__(self):
34
+ return self.__str__()
35
+
33
36
  @property
34
37
  def field(self) -> str:
35
38
  """Case field"""
@@ -74,3 +77,23 @@ class Iteration(Document, SearchContext):
74
77
  def uuid(self) -> str:
75
78
  """FMU iteration uuid"""
76
79
  return self.get_property("fmu.iteration.uuid")
80
+
81
+ @property
82
+ def reference_realizations(self):
83
+ """Reference realizations in iteration. If none, return
84
+ realizations 0 and 1, if they exist."""
85
+ sc = super().reference_realizations
86
+ if len(sc) > 0:
87
+ return sc
88
+ else:
89
+ return self.filter(realization=[0, 1]).realizations
90
+
91
+ @property
92
+ async def reference_realizations_async(self):
93
+ """Reference realizations in iteration. If none, return
94
+ realizations 0 and 1, if they exist."""
95
+ sc = await super().reference_realizations_async
96
+ if await sc.length_async() > 0:
97
+ return sc
98
+ else:
99
+ return self.filter(realization=[0, 1]).realizations
@@ -1,6 +1,6 @@
1
1
  """Module for searchcontext for collection of iterations."""
2
2
 
3
- from typing import Dict, List
3
+ from typing import List
4
4
 
5
5
  from ._search_context import SearchContext
6
6
 
@@ -25,42 +25,6 @@ class Iterations(SearchContext):
25
25
  async def _maybe_prefetch_async(self, index):
26
26
  return
27
27
 
28
- def get_object(self, uuid: str) -> Dict:
29
- """Get metadata object by uuid
30
-
31
- Args:
32
- uuid (str): uuid of metadata object
33
- select (List[str]): list of metadata fields to return
34
-
35
- Returns:
36
- Dict: a metadata object
37
- """
38
- obj = self._cache.get(uuid)
39
- if obj is None:
40
- obj = self.get_iteration_by_uuid(uuid)
41
- self._cache.put(uuid, obj)
42
- pass
43
-
44
- return obj
45
-
46
- async def get_object_async(self, uuid: str) -> Dict:
47
- """Get metadata object by uuid
48
-
49
- Args:
50
- uuid (str): uuid of metadata object
51
- select (List[str]): list of metadata fields to return
52
-
53
- Returns:
54
- Dict: a metadata object
55
- """
56
-
57
- obj = self._cache.get(uuid)
58
- if obj is None:
59
- obj = await self.get_iteration_by_uuid_async(uuid)
60
- self._cache.put(uuid, obj)
61
-
62
- return obj
63
-
64
28
  def filter(self, **kwargs):
65
29
  sc = super().filter(**kwargs)
66
30
  uuids = sc.get_field_values("fmu.iteration.uuid.keyword")
@@ -23,7 +23,7 @@ class Realization(Document, SearchContext):
23
23
  )
24
24
  pass
25
25
 
26
- def __repr__(self):
26
+ def __str__(self):
27
27
  return (
28
28
  f"<{self.__class__.__name__}: {self.realizationid} {self.uuid}(uuid) "
29
29
  f"in iteration {self.iterationname} "
@@ -31,6 +31,9 @@ class Realization(Document, SearchContext):
31
31
  f"in asset {self.asset}>"
32
32
  )
33
33
 
34
+ def __repr__(self):
35
+ return self.__str__()
36
+
34
37
  @property
35
38
  def field(self) -> str:
36
39
  """Case field"""
@@ -80,3 +83,8 @@ class Realization(Document, SearchContext):
80
83
  def realizationid(self) -> int:
81
84
  """FMU realization id"""
82
85
  return self.get_property("fmu.realization.id")
86
+
87
+ @property
88
+ def is_reference(self) -> bool:
89
+ """Check if reference realization."""
90
+ return self.get_property("fmu.realization.is_reference") is True
@@ -1,6 +1,6 @@
1
1
  """Module for searchcontext for collection of realizations."""
2
2
 
3
- from typing import Dict
3
+ from typing import List
4
4
 
5
5
  from ._search_context import SearchContext
6
6
 
@@ -17,43 +17,26 @@ class Realizations(SearchContext):
17
17
  async def _maybe_prefetch_async(self, index):
18
18
  return
19
19
 
20
- def get_object(self, uuid: str) -> Dict:
21
- """Get metadata object by uuid
22
-
23
- Args:
24
- uuid (str): uuid of metadata object
25
- select (List[str]): list of metadata fields to return
26
-
27
- Returns:
28
- Dict: a metadata object
29
- """
30
- obj = self._cache.get(uuid)
31
- if obj is None:
32
- obj = self.get_realization_by_uuid(uuid)
33
- self._cache.put(uuid, obj)
34
- pass
35
-
36
- return obj
37
-
38
- async def get_object_async(self, uuid: str) -> Dict:
39
- """Get metadata object by uuid
40
-
41
- Args:
42
- uuid (str): uuid of metadata object
43
- select (List[str]): list of metadata fields to return
44
-
45
- Returns:
46
- Dict: a metadata object
47
- """
48
-
49
- obj = self._cache.get(uuid)
50
- if obj is None:
51
- obj = await self.get_realization_by_uuid_async(uuid)
52
- self._cache.put(uuid, obj)
53
-
54
- return obj
55
-
56
20
  def filter(self, **kwargs):
57
21
  sc = super().filter(**kwargs)
58
22
  uuids = sc.get_field_values("fmu.realization.uuid.keyword")
59
23
  return Realizations(self, uuids)
24
+
25
+ @property
26
+ def classes(self) -> List[str]:
27
+ return ["realization"]
28
+
29
+ @property
30
+ async def classes_async(self) -> List[str]:
31
+ return ["realization"]
32
+
33
+ @property
34
+ def realizationids(self) -> List[int]:
35
+ return [self.get_object(uuid).realizationid for uuid in self._hits]
36
+
37
+ @property
38
+ async def realizationids_async(self) -> List[int]:
39
+ return [
40
+ (await self.get_object_async(uuid)).realizationid
41
+ for uuid in self._hits
42
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmu-sumo
3
- Version: 2.4.10
3
+ Version: 2.6.0
4
4
  Summary: Python package for interacting with Sumo in an FMU setting
5
5
  Author: Equinor
6
6
  License: Apache License
@@ -1,33 +1,33 @@
1
1
  fmu/__init__.py,sha256=ftS-xRPSH-vU7fIHlnZQaCTWbNvs4owJivNW65kzsIM,85
2
2
  fmu/sumo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  fmu/sumo/explorer/__init__.py,sha256=Bc1wd1lQO3HP3tsVyPbqaesf2boZwGdtookWp8lmG-k,317
4
- fmu/sumo/explorer/_version.py,sha256=BuCMiZ5EohpF0751Q0YbLQjmp1ReIXzBLo5Je8Kxg5w,513
4
+ fmu/sumo/explorer/_version.py,sha256=2GXF52IIutfiInnoWGItHWB6xM45HGVQYJ3J77-iUV0,704
5
5
  fmu/sumo/explorer/cache.py,sha256=uvz8TciwBnDEwJIHa9wneC0WVWuzhUqyF3dzk4kvGNk,1037
6
6
  fmu/sumo/explorer/explorer.py,sha256=_3nUTO1E_nf6jqpivjgjKcX6rX1fx_mIG76YOM8xb-8,2931
7
7
  fmu/sumo/explorer/filters.py,sha256=_t2PmHeTY9XiBvQeEGM-BpudWUaxIfyUSdNyG70xfRU,875
8
8
  fmu/sumo/explorer/timefilter.py,sha256=AQHa18vkCz2BzH7X9GR1ypmNxfvI4gExh_jxAVYrDjc,6260
9
9
  fmu/sumo/explorer/objects/__init__.py,sha256=72G0yfWWMTXA-oZw5GMRkrWvQqAYfadjerEwsyndCzc,637
10
- fmu/sumo/explorer/objects/_child.py,sha256=RrH3W3GF5Nhje7VnhmlEwG_jhX0z6CloJgdGJJzk3po,6035
11
- fmu/sumo/explorer/objects/_document.py,sha256=uPAkNzcOk8U4LCtXthWkm7kfU9W4HYHrrqV9HERDhe8,1835
12
- fmu/sumo/explorer/objects/_metrics.py,sha256=WITAFAV-3VRd-AqTg0tUwWpOChW4YFfF8ffT2fvOrAo,8144
13
- fmu/sumo/explorer/objects/_search_context.py,sha256=RA2KoEmnnHs6xdwLWbvTX7JXgYXhR5fCsqzzR8TnPY8,63357
10
+ fmu/sumo/explorer/objects/_child.py,sha256=I-TVxC5JhmcuCAkj1A_dK-cUzvbQarbonX1zq59d0ZU,6088
11
+ fmu/sumo/explorer/objects/_document.py,sha256=UR607n9N33vYaTSsYQoMmJUgnmS3To_uVyRSf7Vxulo,1797
12
+ fmu/sumo/explorer/objects/_metrics.py,sha256=Z7iJ8qmvH3iY5dsSf6At_AFIzITyM0rDfum_oGmUFG8,9989
13
+ fmu/sumo/explorer/objects/_search_context.py,sha256=C_HaiqqnERr_WV9sRX4P9MQdGl2rEco1FpxtM32j24M,66500
14
14
  fmu/sumo/explorer/objects/case.py,sha256=fKp7X43ETLE1RaH3rMYxZiIuduRmf0JSnJ5gRoUgNPE,3813
15
15
  fmu/sumo/explorer/objects/cases.py,sha256=i2bnvk7NWIkzbdWMs3BXU7TCqD5tH2r7pg1m1QXUj3o,561
16
16
  fmu/sumo/explorer/objects/cpgrid.py,sha256=nuRgZ6FVEOPZT1ibd-rJhlbYYZ6BuUxXZPzovcH0kVc,2548
17
17
  fmu/sumo/explorer/objects/cpgrid_property.py,sha256=PqqR05oKKKiTTG0iDO9V6TADdHY7VUsLHjai6SqahVo,2694
18
18
  fmu/sumo/explorer/objects/cube.py,sha256=6pJLDajex-mblkt9YRZxtcK1XHcRZ8mlPPqJ-yDGEbA,1948
19
19
  fmu/sumo/explorer/objects/dictionary.py,sha256=9Nt8Br7H4TgXO6qc46HtV1vB40LsEQb6WjWYDT-Ve0g,1191
20
- fmu/sumo/explorer/objects/ensemble.py,sha256=PqHUKMxIyT8pMVtPjvgUNS5P3gTeDYI52CqFGB56HUA,2009
21
- fmu/sumo/explorer/objects/ensembles.py,sha256=Z20a9Vlx-P9Z484ikL_0X_xm5pudVjghmB0tq-_smwE,1718
22
- fmu/sumo/explorer/objects/iteration.py,sha256=ASZXjsxASEoVgbS-ZNpBNm3qKr1pPG0v7CAlFyiYOk4,2024
23
- fmu/sumo/explorer/objects/iterations.py,sha256=VzoOQSnqVRXJDCPe9nJCWNtPyVtJEraZPTJsPwXF_j4,1726
20
+ fmu/sumo/explorer/objects/ensemble.py,sha256=MoGiXrtyVL8_cQt5vjh0rIA5bMMGRahPb284teQEqfs,2767
21
+ fmu/sumo/explorer/objects/ensembles.py,sha256=uFtnWDgk316NRM_JvD1C7PK20gDOPQVQjCygPFhe3ZE,772
22
+ fmu/sumo/explorer/objects/iteration.py,sha256=vXaH6G93pNPvufgliqRSs4fpqgNvgxa7QI0G0ucgr_U,2784
23
+ fmu/sumo/explorer/objects/iterations.py,sha256=ZRQOxPl6MpX7JV7lfvtXp8mGLHl37pl3-F9YAXYfRgc,778
24
24
  fmu/sumo/explorer/objects/polygons.py,sha256=k5BKuXHsLxzhMR5KId6Fly4FNygTOcShFCCMXvhjWg4,1187
25
- fmu/sumo/explorer/objects/realization.py,sha256=Th3zr_nT2I1HYHkBojOAKnf_RSIPc-TiDtW0X39eMRw,2259
26
- fmu/sumo/explorer/objects/realizations.py,sha256=4agtQFJ5pxLyWMeIzdJbkBMc8__Md-tZOj8Un4ol22c,1562
25
+ fmu/sumo/explorer/objects/realization.py,sha256=HK47WyX6kwe6ZoHaGHeTWEno86Wkh9THLOtEzOt1FGE,2483
26
+ fmu/sumo/explorer/objects/realizations.py,sha256=ojyQDZNEGubRE97z7c5WHK1ZnO267jsIureNeIzouAw,1120
27
27
  fmu/sumo/explorer/objects/surface.py,sha256=zHBtjLCIfkRHBv39OeJjA9lq3puLTfTII6TndZTtxVI,1627
28
28
  fmu/sumo/explorer/objects/table.py,sha256=vLor3YTddHkDWZSMyWPQsddFNQ2_VXE_O-stmPIWIaQ,4900
29
- fmu_sumo-2.4.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
30
- fmu_sumo-2.4.10.dist-info/METADATA,sha256=Q1_QvBNu-n_Zfo_lyZajdYVvEIE-aX9zg2yLCucwiUE,14782
31
- fmu_sumo-2.4.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- fmu_sumo-2.4.10.dist-info/top_level.txt,sha256=Z-FIY3pxn0UK2Wxi9IJ7fKoLSraaxuNGi1eokiE0ShM,4
33
- fmu_sumo-2.4.10.dist-info/RECORD,,
29
+ fmu_sumo-2.6.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
30
+ fmu_sumo-2.6.0.dist-info/METADATA,sha256=56_A-Dt1Bk1Q9ZfnIGVTQrTetphpp3kgI-bkquj2IFw,14781
31
+ fmu_sumo-2.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
+ fmu_sumo-2.6.0.dist-info/top_level.txt,sha256=Z-FIY3pxn0UK2Wxi9IJ7fKoLSraaxuNGi1eokiE0ShM,4
33
+ fmu_sumo-2.6.0.dist-info/RECORD,,