streamlit-octostar-utils 0.5.2.dev1__tar.gz → 0.5.3__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.2.dev1 → streamlit_octostar_utils-0.5.3}/PKG-INFO +1 -1
  2. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/pyproject.toml +1 -1
  3. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/celery.py +68 -32
  4. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/nifi.py +38 -3
  5. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/LICENSE +0 -0
  6. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/README.md +0 -0
  7. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/__init__.py +0 -0
  8. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/__init__.py +0 -0
  9. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/contents.py +0 -0
  10. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/fastapi.py +0 -0
  11. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parallelism.py +0 -0
  12. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/__init__.py +0 -0
  13. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/combine_fields.py +0 -0
  14. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/entities_parser.py +0 -0
  15. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/generics.py +0 -0
  16. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/info.py +0 -0
  17. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/linkchart_functions.py +0 -0
  18. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/matches.py +0 -0
  19. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/parameters.py +0 -0
  20. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/rules.py +0 -0
  21. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/api_crafter/parser/signals.py +0 -0
  22. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/core/__init__.py +0 -0
  23. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/core/dict.py +0 -0
  24. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/core/filetypes.py +0 -0
  25. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/core/threading/__init__.py +0 -0
  26. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/core/threading/key_queue.py +0 -0
  27. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/core/timestamp.py +0 -0
  28. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/nlp/__init__.py +0 -0
  29. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/nlp/custom_recognizers.py +0 -0
  30. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/nlp/language.py +0 -0
  31. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/nlp/ner.py +0 -0
  32. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/octostar/__init__.py +0 -0
  33. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/octostar/client.py +0 -0
  34. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/octostar/context.py +0 -0
  35. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/octostar/permissions.py +0 -0
  36. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/ontology/__init__.py +0 -0
  37. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/ontology/inheritance.py +0 -0
  38. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/ontology/relationships.py +0 -0
  39. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/ontology/validation.py +0 -0
  40. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/style/__init__.py +0 -0
  41. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/style/common.py +0 -0
  42. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/threading/__init__.py +0 -0
  43. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/threading/async_task_manager.py +0 -0
  44. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/streamlit_octostar_utils/threading/session_callback_manager.py +0 -0
  45. {streamlit_octostar_utils-0.5.2.dev1 → streamlit_octostar_utils-0.5.3}/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.2.dev1
3
+ Version: 0.5.3
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.2-dev.1"
8
+ version = "0.5.3"
9
9
  description = ""
10
10
  license = "MIT"
11
11
  authors = ["Octostar"]
@@ -48,6 +48,65 @@ class RedisFileLock:
48
48
  return self.lock.__exit__(exc_type, exc_val, exc_tb)
49
49
 
50
50
 
51
+ def _install_queue_limit_patch(queue_limits):
52
+ """Monkey-patch kombu's Redis Channel._put for atomic queue limits.
53
+
54
+ The original _put does a bare LPUSH. The patched version wraps
55
+ limited queues in a Lua script (LLEN check + LPUSH) so the
56
+ "is there room?" test and the "enqueue" are a single atomic
57
+ Redis operation. Queues without a configured limit are
58
+ forwarded to the original _put unchanged.
59
+ """
60
+ from kombu.transport.redis import Channel
61
+ from kombu.utils.json import dumps as kombu_dumps
62
+
63
+ _ATOMIC_ENQUEUE_LUA = """
64
+ local total = 0
65
+ for i = 1, #KEYS do
66
+ total = total + redis.call('LLEN', KEYS[i])
67
+ end
68
+ if total >= tonumber(ARGV[1]) then
69
+ return 0
70
+ end
71
+ redis.call('LPUSH', KEYS[1], ARGV[2])
72
+ return 1
73
+ """
74
+
75
+ if not hasattr(Channel, "_queue_limits"):
76
+ Channel._queue_limits = {}
77
+ Channel._queue_limits.update(queue_limits)
78
+
79
+ if getattr(Channel, "_queue_limit_patched", False):
80
+ return
81
+
82
+ _original_put = Channel._put
83
+
84
+ def _put_with_limit(self, queue, message, **kwargs):
85
+ max_tasks = Channel._queue_limits.get(queue)
86
+ if max_tasks is not None:
87
+ pri = self._get_message_priority(message, reverse=False)
88
+ target_key = self._q_for_pri(queue, pri)
89
+ all_keys = [self._q_for_pri(queue, p) for p in self.priority_steps]
90
+ if target_key in all_keys:
91
+ all_keys.remove(target_key)
92
+ all_keys.insert(0, target_key)
93
+ with self.conn_or_acquire() as client:
94
+ accepted = client.eval(
95
+ _ATOMIC_ENQUEUE_LUA, len(all_keys), *all_keys,
96
+ max_tasks, kombu_dumps(message),
97
+ )
98
+ if not accepted:
99
+ from streamlit_octostar_utils.api_crafter.celery import CeleryExecutor
100
+ raise CeleryExecutor.QueueFullException(
101
+ f"Queue '{queue}' has reached its limit of {max_tasks} tasks!"
102
+ )
103
+ else:
104
+ _original_put(self, queue, message, **kwargs)
105
+
106
+ Channel._put = _put_with_limit
107
+ Channel._queue_limit_patched = True
108
+
109
+
51
110
  class CeleryQueueConfig:
52
111
  def __init__(
53
112
  self,
@@ -201,10 +260,11 @@ class CeleryExecutor(object):
201
260
  self.get_thread_pool = None
202
261
  self.set_thread_pool = None
203
262
  self.io_thread_pool = None
204
- self.queue_semaphores = {
205
- k: threading.Semaphore(v.max_tasks_in_queue) if v.max_tasks_in_queue else None
206
- for k, v in self.queue_config.items()
207
- }
263
+
264
+ # Atomic queue-depth limits
265
+ queue_limits = {k: v.max_tasks_in_queue for k, v in self.queue_config.items() if v.max_tasks_in_queue}
266
+ if queue_limits:
267
+ _install_queue_limit_patch(queue_limits)
208
268
 
209
269
  # Folder setup
210
270
  self.base_folder = Path(base_folder).resolve()
@@ -720,17 +780,6 @@ class CeleryExecutor(object):
720
780
  self.preload_on_worker_init()
721
781
  self.app.conf.dev_preload = True
722
782
 
723
- def _check_queue_llen(queue_name):
724
- if self._queue_stalled.get(queue_name, False):
725
- raise CeleryExecutor.QueueStalledException(
726
- f"Queue '{queue_name}' is stalled. Service temporarily unavailable."
727
- )
728
- if self.redis_client.llen(queue_name) >= self.queue_config[queue_name].max_tasks_in_queue:
729
- raise CeleryExecutor.QueueFullException(
730
- f"Queue '{queue_name}' has reached its limit of "
731
- f"{self.queue_config[queue_name].max_tasks_in_queue} tasks!"
732
- )
733
-
734
783
  def _write_task_data(in_folder, task_args, task_kwargs, task_id):
735
784
  serialized_data = CelerySerialized(
736
785
  folder=in_folder,
@@ -754,22 +803,12 @@ class CeleryExecutor(object):
754
803
  queue_name = getattr(task_fn, "queue", queue_name)
755
804
  queue_name = options.get("queue", queue_name)
756
805
 
757
- sem = self.queue_semaphores.get(queue_name)
758
- acquired = False
759
- if sem is not None:
760
- if not sem.acquire(blocking=False):
761
- raise CeleryExecutor.QueueFullException(
762
- f"Queue '{queue_name}' has reached its limit of "
763
- f"{self.queue_config[queue_name].max_tasks_in_queue} tasks!"
764
- )
765
- acquired = True
806
+ if self._queue_stalled.get(queue_name, False):
807
+ raise CeleryExecutor.QueueStalledException(
808
+ f"Queue '{queue_name}' is stalled. Service temporarily unavailable."
809
+ )
766
810
 
767
811
  try:
768
- if acquired:
769
- await asyncio.get_running_loop().run_in_executor(
770
- self.set_thread_pool, _check_queue_llen, queue_name
771
- )
772
-
773
812
  if part is not None:
774
813
  await self._write_task_data_with_part(
775
814
  task_id, args, kwargs, part
@@ -800,9 +839,6 @@ class CeleryExecutor(object):
800
839
  except Exception:
801
840
  pass
802
841
  raise
803
- finally:
804
- if acquired:
805
- sem.release()
806
842
  return task_id
807
843
 
808
844
  async def _write_task_data_with_part(self, task_id, args, kwargs, part):
@@ -17,6 +17,7 @@ from starlette.exceptions import HTTPException as StarletteHTTPException
17
17
 
18
18
  from octostar.utils.workspace import upsert_entities
19
19
  from octostar.utils.ontology import fetch_ontology_data
20
+ from octostar.utils.workspace.permissions import get_permissions, PermissionLevel
20
21
  from octostar.utils.pipeline import update_processing_status
21
22
 
22
23
  from octostar.client import make_client
@@ -84,6 +85,7 @@ class NifiEntityModel(BaseModel):
84
85
  is_temporary: bool = False
85
86
  exception: dict = Field(default_factory=dict)
86
87
  last_processor_name: Optional[str] = None
88
+ fallback_os_workspace: Optional[str] = None
87
89
 
88
90
  class RecordModel(BaseModel):
89
91
  model_config = ConfigDict(extra="allow")
@@ -473,6 +475,7 @@ class NifiContextManager(object):
473
475
  def __init__(self, json_data, lazy_sync=True):
474
476
  if not json_data:
475
477
  raise ValueError("Nifi context manager received list of 0 entities")
478
+ self.permissions = {}
476
479
  self.in_batches = None
477
480
  self.out_entities = None
478
481
  self.nonlazy_sync_ids = set()
@@ -549,6 +552,16 @@ class NifiContextManager(object):
549
552
  def __enter__(self):
550
553
  return self
551
554
 
555
+ def get_workspaces_permissions(self, workspace_ids):
556
+ permissions_to_fetch = list(set(workspace_ids).difference(set(list(self.permissions.keys()))))
557
+ if permissions_to_fetch:
558
+ permissions = get_permissions.sync(permissions_to_fetch, client=self.client)
559
+ self.permissions.update(permissions)
560
+ permissions = {}
561
+ for k in workspace_ids:
562
+ permissions[k] = self.permissions.get(k, PermissionLevel.NONE)
563
+ return permissions
564
+
552
565
  def request_entity_sync(
553
566
  self,
554
567
  entity,
@@ -957,7 +970,29 @@ class NifiEntity(object):
957
970
 
958
971
  @property
959
972
  def write_os_workspace(self):
960
- return self.record.get("os_workspace")
973
+ permissions = self.context.get_workspaces_permissions(
974
+ [
975
+ e
976
+ for e in [
977
+ self.record.get("os_workspace"),
978
+ self.request.get("fallback_os_workspace"),
979
+ ]
980
+ if e
981
+ ]
982
+ )
983
+ if (
984
+ self.record.get("os_workspace")
985
+ and (permissions.get(self.record.get("os_workspace")) or PermissionLevel.NONE) >= PermissionLevel.WRITE
986
+ ):
987
+ return self.record["os_workspace"]
988
+ elif (
989
+ self.request.get("fallback_os_workspace")
990
+ and (permissions.get(self.request.get("fallback_os_workspace")) or PermissionLevel.NONE)
991
+ >= PermissionLevel.WRITE
992
+ ):
993
+ return self.request["fallback_os_workspace"]
994
+ else:
995
+ return None
961
996
 
962
997
  @property
963
998
  def label(self):
@@ -1175,6 +1210,7 @@ class NifiEntity(object):
1175
1210
  "is_temporary": True,
1176
1211
  "exception": {},
1177
1212
  "last_processor_name": None,
1213
+ "fallback_os_workspace": self.request["fallback_os_workspace"],
1178
1214
  }
1179
1215
  child_entity = NifiEntity(
1180
1216
  self.context,
@@ -1400,7 +1436,6 @@ class NifiEntity(object):
1400
1436
  os_entity_uid=None,
1401
1437
  os_relationship_uid=None,
1402
1438
  os_entity_type=FRAGMENT_ENTITY_NAME,
1403
- os_parent_uid=None,
1404
1439
  previous_fragment_uid=None,
1405
1440
  previous_fragment_relationship_uid=None,
1406
1441
  previous_fragment_relationship=PREVIOUS_FRAGMENT_RELATIONSHIP,
@@ -1413,7 +1448,7 @@ class NifiEntity(object):
1413
1448
  fields = {
1414
1449
  **{k: v for k, v in self.record.items() if k.startswith("fragment") and v is not None},
1415
1450
  **fields,
1416
- "os_parent_uid": os_parent_uid or self.record["os_entity_uid"],
1451
+ "os_parent_uid": self.record["os_entity_uid"],
1417
1452
  "source_entity_uid": source_entity_uid,
1418
1453
  "previous_entity_uid": previous_fragment_uid,
1419
1454
  "next_entity_uid": next_fragment_uid,