streamlit-octostar-utils 0.5.0.dev1__tar.gz → 0.5.0.dev2__tar.gz

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.
Files changed (45) hide show
  1. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/PKG-INFO +1 -1
  2. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/pyproject.toml +1 -1
  3. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/nifi.py +210 -17
  4. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/LICENSE +0 -0
  5. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/README.md +0 -0
  6. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/__init__.py +0 -0
  7. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/__init__.py +0 -0
  8. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/celery.py +0 -0
  9. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/contents.py +0 -0
  10. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/fastapi.py +0 -0
  11. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parallelism.py +0 -0
  12. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/__init__.py +0 -0
  13. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/combine_fields.py +0 -0
  14. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/entities_parser.py +0 -0
  15. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/generics.py +0 -0
  16. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/info.py +0 -0
  17. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/linkchart_functions.py +0 -0
  18. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/matches.py +0 -0
  19. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/parameters.py +0 -0
  20. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/rules.py +0 -0
  21. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/api_crafter/parser/signals.py +0 -0
  22. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/core/__init__.py +0 -0
  23. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/core/dict.py +0 -0
  24. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/core/filetypes.py +0 -0
  25. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/core/threading/__init__.py +0 -0
  26. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/core/threading/key_queue.py +0 -0
  27. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/core/timestamp.py +0 -0
  28. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/nlp/__init__.py +0 -0
  29. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/nlp/custom_recognizers.py +0 -0
  30. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/nlp/language.py +0 -0
  31. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/nlp/ner.py +0 -0
  32. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/octostar/__init__.py +0 -0
  33. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/octostar/client.py +0 -0
  34. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/octostar/context.py +0 -0
  35. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/octostar/permissions.py +0 -0
  36. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/ontology/__init__.py +0 -0
  37. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/ontology/inheritance.py +0 -0
  38. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/ontology/relationships.py +0 -0
  39. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/ontology/validation.py +0 -0
  40. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/style/__init__.py +0 -0
  41. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/style/common.py +0 -0
  42. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/threading/__init__.py +0 -0
  43. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/threading/async_task_manager.py +0 -0
  44. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/threading/session_callback_manager.py +0 -0
  45. {streamlit_octostar_utils-0.5.0.dev1 → streamlit_octostar_utils-0.5.0.dev2}/streamlit_octostar_utils/threading/session_state_hot_swapper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streamlit-octostar-utils
3
- Version: 0.5.0.dev1
3
+ Version: 0.5.0.dev2
4
4
  Summary:
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -5,7 +5,7 @@ include = '\.pyi?$'
5
5
 
6
6
  [tool.poetry]
7
7
  name = "streamlit-octostar-utils"
8
- version = "0.5.0-dev.1"
8
+ version = "0.5.0-dev.2"
9
9
  description = ""
10
10
  license = "MIT"
11
11
  authors = ["Octostar"]
@@ -238,6 +238,156 @@ class NifiFragmenter(object):
238
238
  pointer.get("merge_params") or {}, defragmenter_config, lambda _, v2: v2
239
239
  )
240
240
 
241
+ _REQUIRED_FRAGMENT_FIELDS = ("index", "count", "identifier")
242
+
243
+ @staticmethod
244
+ def get_fragment_info(entity, fragmenter_keylist):
245
+ """Read fragment metadata (identifier, count, index, root_uid, merge_params)
246
+ for a given fragmenter level. Read-only -- does not mutate the entity.
247
+
248
+ Args:
249
+ entity: A NifiEntity or NifiEntityProxy.
250
+ fragmenter_keylist: Dot-separated key path into the fragment config
251
+ (e.g. "document_pages" or "audio_split").
252
+
253
+ Returns:
254
+ dict with keys like identifier, count, index, root_uid, merge_params.
255
+ Empty dict if fragmenter_keylist is empty or intermediate keys are
256
+ missing (entity not fragmented at this level).
257
+
258
+ Raises:
259
+ KeyError: If the final fragment key is missing from the config.
260
+ ValueError: If the fragment info exists but lacks required fields
261
+ (index, count, identifier).
262
+ """
263
+ if not fragmenter_keylist:
264
+ return {}
265
+ pointer = entity.request["config"]["fragment"]
266
+ for k in fragmenter_keylist.split(".")[:-1]:
267
+ if not pointer.get(k):
268
+ return {}
269
+ pointer = pointer[k]
270
+ info = pointer[fragmenter_keylist.split(".")[-1]]
271
+ missing = [f for f in NifiFragmenter._REQUIRED_FRAGMENT_FIELDS if f not in info]
272
+ if missing:
273
+ raise RuntimeError(
274
+ f"Fragment info for '{fragmenter_keylist}' is missing required "
275
+ f"field(s): {', '.join(missing)}"
276
+ )
277
+ return info
278
+
279
+ @staticmethod
280
+ def identify_fragment_groups(nifi_batches):
281
+ """Find all fragmented entities grouped by their active fragmenter level.
282
+
283
+ Args:
284
+ nifi_batches: List of NifiEntityBatch objects.
285
+
286
+ Returns:
287
+ dict mapping fragmenter_keylist to list of entities at that level.
288
+ Empty dict if no fragments found. Callers use get_fragment_info()
289
+ to fetch metadata per entity, and filter by index==0 to find roots.
290
+ """
291
+ all_entities = list(itertools.chain(*[b.entities for b in nifi_batches]))
292
+ groups = {}
293
+ for e in all_entities:
294
+ stack = e.request["config"].get("fragment", {}).get("fragments_stack", [])
295
+ if stack:
296
+ groups.setdefault(stack[0], []).append(e)
297
+ return groups
298
+
299
+ @staticmethod
300
+ def build_fragment_tree_from_children_entities(root_entity, fragmenter_keylist):
301
+ """Recursively build a tree from a root fragment entity by walking
302
+ its children_entities.
303
+
304
+ Args:
305
+ root_entity: The root entity (index 0) to start from.
306
+ fragmenter_keylist: The fragmenter level to build for.
307
+
308
+ Returns:
309
+ Nested dict with keys:
310
+ "entity": NifiEntity/NifiEntityProxy
311
+ "index": int
312
+ "merge_params": dict or None
313
+ "children": list of child trees
314
+ """
315
+ info = NifiFragmenter.get_fragment_info(root_entity, fragmenter_keylist)
316
+ child_fragments = []
317
+ for e in root_entity.children_entities:
318
+ try:
319
+ child_info = NifiFragmenter.get_fragment_info(e, fragmenter_keylist)
320
+ if child_info:
321
+ child_fragments.append(e)
322
+ except (AttributeError, KeyError):
323
+ pass
324
+ return {
325
+ "entity": root_entity,
326
+ "index": info.get("index"),
327
+ "merge_params": info.get("merge_params"),
328
+ "children": [
329
+ NifiFragmenter.build_fragment_tree_from_children_entities(child, fragmenter_keylist)
330
+ for child in child_fragments
331
+ ],
332
+ }
333
+
334
+ @staticmethod
335
+ def extract_tree_entities(tree):
336
+ """Flatten a fragment tree into a list of all entities (pre-order).
337
+
338
+ Args:
339
+ tree: Fragment tree node (from build_fragment_tree_from_children_entities).
340
+
341
+ Returns:
342
+ List of entities in pre-order traversal.
343
+ """
344
+ entities = [tree["entity"]]
345
+ for child in tree.get("children", []):
346
+ entities.extend(NifiFragmenter.extract_tree_entities(child))
347
+ return entities
348
+
349
+ @staticmethod
350
+ def iterate_fragments_tree(tree, order="post"):
351
+ """Yield tree nodes in traversal order.
352
+
353
+ Args:
354
+ tree: Fragment tree node (from build_fragment_tree_from_children_entities).
355
+ order: "post" (children first, default) or "pre" (parent first).
356
+
357
+ Yields:
358
+ dict nodes with "entity", "index", "merge_params", "children" keys.
359
+ """
360
+ children = sorted(tree.get("children", []), key=lambda x: x["index"])
361
+ if order == "pre":
362
+ yield tree
363
+ for child in children:
364
+ yield from NifiFragmenter.iterate_fragments_tree(child, order)
365
+ if order == "post":
366
+ yield tree
367
+
368
+ @staticmethod
369
+ def reduce_fragments_tree(tree, leaf_fn, parent_fn):
370
+ """Bottom-up tree reduction. Processes leaves first, then folds results up.
371
+
372
+ Args:
373
+ tree: Fragment tree node (from build_fragment_tree_from_children_entities).
374
+ leaf_fn: Callable(node) -> result, called on nodes with no children.
375
+ parent_fn: Callable(node, child_results) -> result, called on
376
+ nodes with children. child_results is a list of results from
377
+ child nodes, sorted by index.
378
+
379
+ Returns:
380
+ The result from the root node.
381
+ """
382
+ children = sorted(tree.get("children", []), key=lambda x: x["index"])
383
+ if not children:
384
+ return leaf_fn(tree)
385
+ child_results = [
386
+ NifiFragmenter.reduce_fragments_tree(child, leaf_fn, parent_fn)
387
+ for child in children
388
+ ]
389
+ return parent_fn(tree, child_results)
390
+
241
391
 
242
392
  class NifiEntityBatch(object):
243
393
  def __init__(self, entities, config, config_key):
@@ -777,25 +927,68 @@ class NifiEntity(object):
777
927
  entity_type = self.record["entity_type"]
778
928
  return entity_type == type or type in self.request["ontology_info"]["parents"]
779
929
 
780
- def is_fragmented(self) -> bool:
781
- return bool(self.request["config"].get("fragment", {}).get("fragments_stack"))
782
-
783
- def is_root_fragment(self, entity) -> bool:
784
- def _is_sub_fragment_recursive(fragment: dict) -> bool:
785
- if not isinstance(fragment, dict):
786
- return False
787
- if all(k in fragment for k in ["index", "count", "identifier"]):
788
- return fragment.get("index", 0) != 0
789
- for value in fragment.values():
790
- if isinstance(value, dict):
791
- if _is_sub_fragment_recursive(value):
792
- return True
793
- return False
794
-
930
+ def is_fragmented(self, fragment_name_or_idx=None) -> bool:
931
+ """Check whether this entity is part of a fragmentation.
932
+
933
+ Args:
934
+ fragment_name_or_idx: If None (default), returns True if the entity
935
+ belongs to any fragmentation level. If an int, checks whether the
936
+ fragments_stack has an entry at that index. If a string, checks
937
+ whether that fragmenter keylist is present in the stack.
938
+
939
+ Returns:
940
+ True if the entity is fragmented (at the specified level, if given).
941
+ """
942
+ stack = self.request["config"].get("fragment", {}).get("fragments_stack", [])
943
+ if fragment_name_or_idx is None:
944
+ return bool(stack)
945
+ if isinstance(fragment_name_or_idx, int):
946
+ return abs(fragment_name_or_idx) <= len(stack)
947
+ return fragment_name_or_idx in stack
948
+
949
+ def is_root_fragment(self, fragment_name_or_idx=-1, recurse=True) -> bool:
950
+ """Check whether this entity is a root fragment (index == 0).
951
+
952
+ Args:
953
+ fragment_name_or_idx: Which fragmentation level to check. An int
954
+ indexes into fragments_stack (default -1 = oldest level),
955
+ a string matches by fragmenter keylist name.
956
+ recurse: If True (default), check from the starting level towards
957
+ index 0 (most recent) and return True only if the entity is
958
+ root at every checked level. If False, only check the single
959
+ specified level.
960
+
961
+ Returns:
962
+ True if the entity is a root fragment at the specified level(s),
963
+ or if the entity is not fragmented at all.
964
+ """
795
965
  if not self.is_fragmented():
796
966
  return True
797
- fragment = entity.request.get("config", {}).get("fragment", {})
798
- return not _is_sub_fragment_recursive(fragment)
967
+ fragments_stack = self.request["config"]["fragment"]["fragments_stack"]
968
+
969
+ if isinstance(fragment_name_or_idx, int):
970
+ try:
971
+ resolved = (
972
+ fragment_name_or_idx
973
+ if fragment_name_or_idx >= 0
974
+ else len(fragments_stack) + fragment_name_or_idx
975
+ )
976
+ key = fragments_stack[resolved]
977
+ except (IndexError, ValueError):
978
+ return True
979
+ else:
980
+ key = fragment_name_or_idx
981
+ if key not in fragments_stack:
982
+ return True
983
+ resolved = fragments_stack.index(key)
984
+
985
+ if recurse:
986
+ keys_to_check = fragments_stack[:resolved + 1]
987
+ return all(
988
+ NifiFragmenter.get_fragment_info(self, k).get("index", 0) == 0
989
+ for k in keys_to_check
990
+ )
991
+ return NifiFragmenter.get_fragment_info(self, key).get("index", 0) == 0
799
992
 
800
993
  def get_fragment_root_uid(self, fragment_name_or_idx) -> str:
801
994
  fragment_config = self.request.get("config", {}).get("fragment", {})