plexus-python-pulse-common 1.1.76__py3-none-any.whl → 1.1.77__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.
- plexus/pulse/common/utils/flowutils.py +167 -0
- plexus/pulse/common/utils/ormutils.py +38 -1
- {plexus_python_pulse_common-1.1.76.dist-info → plexus_python_pulse_common-1.1.77.dist-info}/METADATA +1 -1
- {plexus_python_pulse_common-1.1.76.dist-info → plexus_python_pulse_common-1.1.77.dist-info}/RECORD +6 -5
- {plexus_python_pulse_common-1.1.76.dist-info → plexus_python_pulse_common-1.1.77.dist-info}/WHEEL +0 -0
- {plexus_python_pulse_common-1.1.76.dist-info → plexus_python_pulse_common-1.1.77.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import enum
|
|
3
|
+
import os
|
|
4
|
+
import pathlib
|
|
5
|
+
import threading
|
|
6
|
+
import typing
|
|
7
|
+
import uuid as py_uuid
|
|
8
|
+
from collections.abc import Generator
|
|
9
|
+
from typing import Self
|
|
10
|
+
|
|
11
|
+
import pydantic as pdt
|
|
12
|
+
import sqlalchemy as sa
|
|
13
|
+
import sqlalchemy.dialects.sqlite as sa_sqlite
|
|
14
|
+
import sqlalchemy.orm as sa_orm
|
|
15
|
+
from plexus.common.utils.dbutils import ConnectionMaker
|
|
16
|
+
from plexus.common.utils.funcutils import Chainable
|
|
17
|
+
from plexus.common.utils.funcutils import chainable, singleton
|
|
18
|
+
from sqlmodel import Field
|
|
19
|
+
|
|
20
|
+
from plexus.pulse.common.utils.ormutils import SQLiteUUID, SequenceModelMixinProtocol
|
|
21
|
+
from plexus.pulse.common.utils.ormutils import clone_sequence_model_instance, make_base_model, make_sequence_model_mixin
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"FlowStageEnum",
|
|
25
|
+
"current_flow_stage",
|
|
26
|
+
"current_flow_run_id",
|
|
27
|
+
"DatasetRef",
|
|
28
|
+
"DatasetRefTable",
|
|
29
|
+
"flow_context_file_path",
|
|
30
|
+
"FlowContext",
|
|
31
|
+
"context",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FlowStageEnum(enum.Enum):
|
|
36
|
+
Preprocessing = "preprocessing"
|
|
37
|
+
Annotation = "annotation"
|
|
38
|
+
Curation = "curation"
|
|
39
|
+
Training = "training"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def current_flow_stage() -> FlowStageEnum:
|
|
43
|
+
stage_str = os.environ.get("PULSE_FLOW_STAGE")
|
|
44
|
+
if stage_str is None:
|
|
45
|
+
raise ValueError("Missing required environment variable 'PULSE_FLOW_STAGE'")
|
|
46
|
+
for stage in list(FlowStageEnum):
|
|
47
|
+
if stage_str == stage.value:
|
|
48
|
+
return stage
|
|
49
|
+
raise ValueError(f"Illegal 'PULSE_FLOW_STAGE' value '{stage_str}'")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def current_flow_run_id() -> str:
|
|
53
|
+
run_id_str = os.environ.get("PULSE_FLOW_RUN_ID")
|
|
54
|
+
if run_id_str is None:
|
|
55
|
+
raise ValueError("Missing required environment variable 'PULSE_FLOW_RUN_ID'")
|
|
56
|
+
return run_id_str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def make_dataset_ref_model_mixin() -> type[pdt.BaseModel]:
|
|
60
|
+
class ModelMixin(pdt.BaseModel):
|
|
61
|
+
dataset_uuid: py_uuid.UUID = Field(
|
|
62
|
+
sa_column=sa.Column(SQLiteUUID(), nullable=False),
|
|
63
|
+
description="UUID of the dataset",
|
|
64
|
+
)
|
|
65
|
+
dataset_name: str | None = Field(
|
|
66
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(128), nullable=True),
|
|
67
|
+
description="Name of the dataset",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return ModelMixin
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
BaseModel = make_base_model()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class DatasetRef(BaseModel, make_dataset_ref_model_mixin()):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class DatasetRefTable(DatasetRef, make_sequence_model_mixin("sqlite"), table=True):
|
|
81
|
+
__tablename__ = "dataset_ref"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if typing.TYPE_CHECKING:
|
|
85
|
+
class DatasetRef(BaseModel):
|
|
86
|
+
dataset_uuid: sa_orm.Mapped[py_uuid.UUID] = ...
|
|
87
|
+
dataset_name: sa_orm.Mapped[str | None] = ...
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class DatasetRefTable(DatasetRef, SequenceModelMixinProtocol):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def is_resolve_dataset_ref_stage(stage: FlowStageEnum | None) -> bool:
|
|
95
|
+
if stage is None:
|
|
96
|
+
stage = current_flow_stage()
|
|
97
|
+
return stage in {FlowStageEnum.Curation}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def is_construct_dataset_ref_stage(stage: FlowStageEnum | None) -> bool:
|
|
101
|
+
if stage is None:
|
|
102
|
+
stage = current_flow_stage()
|
|
103
|
+
return stage in {FlowStageEnum.Training}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def flow_context_file_path() -> pathlib.Path:
|
|
107
|
+
root_dir = os.environ.get("PULSE_FLOW_CONTEXT_ROOT_DIR")
|
|
108
|
+
if root_dir is None:
|
|
109
|
+
raise ValueError("Missing required environment variable 'PULSE_FLOW_CONTEXT_ROOT_DIR'")
|
|
110
|
+
return pathlib.Path(root_dir) / current_flow_run_id() / "flow_context.db"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class FlowContext(object):
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
*,
|
|
117
|
+
file_path: str | os.PathLike[str] | None = None,
|
|
118
|
+
fail_if_exists: bool = False,
|
|
119
|
+
):
|
|
120
|
+
self.file_path = pathlib.Path(file_path or flow_context_file_path())
|
|
121
|
+
if fail_if_exists and self.file_path.exists():
|
|
122
|
+
raise FileExistsError(f"flow context file '{self.file_path}' already exists")
|
|
123
|
+
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
|
|
125
|
+
self.conn_maker = ConnectionMaker.from_url(f"sqlite:///{self.file_path.absolute()}",
|
|
126
|
+
engine_opts=dict(connect_args={"check_same_thread": False}))
|
|
127
|
+
|
|
128
|
+
self.thread_lock = threading.Lock()
|
|
129
|
+
|
|
130
|
+
BaseModel.metadata.create_all(self.conn_maker.engine)
|
|
131
|
+
|
|
132
|
+
@contextlib.contextmanager
|
|
133
|
+
def make_session(self) -> Generator[sa_orm.Session, None, None]:
|
|
134
|
+
with self.thread_lock:
|
|
135
|
+
with self.conn_maker.make_session(expire_on_commit=False) as session:
|
|
136
|
+
try:
|
|
137
|
+
yield session
|
|
138
|
+
session.commit()
|
|
139
|
+
except Exception:
|
|
140
|
+
session.rollback()
|
|
141
|
+
raise
|
|
142
|
+
|
|
143
|
+
def count_dataset_ref(self) -> int:
|
|
144
|
+
with self.make_session() as session:
|
|
145
|
+
return session.query(DatasetRefTable).count()
|
|
146
|
+
|
|
147
|
+
def list_dataset_refs(self) -> list[DatasetRefTable]:
|
|
148
|
+
with self.make_session() as session:
|
|
149
|
+
return session.query(DatasetRefTable).all()
|
|
150
|
+
|
|
151
|
+
def add_dataset_ref(
|
|
152
|
+
self,
|
|
153
|
+
dataset_uuid: py_uuid.UUID,
|
|
154
|
+
dataset_name: str | None = None,
|
|
155
|
+
) -> Chainable[Self, DatasetRefTable]:
|
|
156
|
+
with self.make_session() as session:
|
|
157
|
+
dataset_ref = DatasetRef(dataset_uuid=dataset_uuid, dataset_name=dataset_name)
|
|
158
|
+
db_dataset_ref = clone_sequence_model_instance(DatasetRefTable, dataset_ref)
|
|
159
|
+
session.add(db_dataset_ref)
|
|
160
|
+
session.commit()
|
|
161
|
+
|
|
162
|
+
return chainable(self, db_dataset_ref)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@singleton
|
|
166
|
+
def context() -> FlowContext:
|
|
167
|
+
return FlowContext()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import uuid as py_uuid
|
|
2
3
|
from collections.abc import Iterable, Sequence
|
|
3
4
|
from typing import Protocol, Self
|
|
4
5
|
|
|
@@ -28,8 +29,10 @@ __all__ = [
|
|
|
28
29
|
"ChangingModelMixinProtocol",
|
|
29
30
|
"SnapshotModelMixinProtocol",
|
|
30
31
|
"RevisionModelMixinProtocol",
|
|
32
|
+
"SQLiteUUID",
|
|
31
33
|
"SQLiteDateTime",
|
|
32
34
|
"model_sqn_type",
|
|
35
|
+
"model_uuid_type",
|
|
33
36
|
"model_datetime_tz_type",
|
|
34
37
|
"model_revision_type",
|
|
35
38
|
"SequenceModelMixin",
|
|
@@ -395,11 +398,29 @@ class RevisionModelMixinProtocol(SequenceModelMixinProtocol):
|
|
|
395
398
|
...
|
|
396
399
|
|
|
397
400
|
|
|
401
|
+
class SQLiteUUID(sa.TypeDecorator):
|
|
402
|
+
"""
|
|
403
|
+
Custom SQLAlchemy type decorator for handling UUIDs in SQLite.
|
|
404
|
+
"""
|
|
405
|
+
impl = sa.CHAR(36)
|
|
406
|
+
cache_ok = True
|
|
407
|
+
|
|
408
|
+
def process_bind_param(self, value: py_uuid.UUID | None, dialect) -> str | None:
|
|
409
|
+
if value is None:
|
|
410
|
+
return None
|
|
411
|
+
return str(value)
|
|
412
|
+
|
|
413
|
+
def process_result_value(self, value: str | None, dialect) -> py_uuid.UUID | None:
|
|
414
|
+
if value is None:
|
|
415
|
+
return None
|
|
416
|
+
return py_uuid.UUID(value)
|
|
417
|
+
|
|
418
|
+
|
|
398
419
|
class SQLiteDateTime(sa.TypeDecorator):
|
|
399
420
|
"""
|
|
400
421
|
Custom SQLAlchemy type decorator for handling timezone-aware datetimes in SQLite.
|
|
401
422
|
"""
|
|
402
|
-
impl = sa.
|
|
423
|
+
impl = sa.DATETIME
|
|
403
424
|
cache_ok = True
|
|
404
425
|
|
|
405
426
|
def process_bind_param(self, value: datetime.datetime | None, dialect) -> datetime.datetime | None:
|
|
@@ -433,6 +454,22 @@ def model_sqn_type(dialect: str | None = None) -> sa.types.TypeEngine[int]:
|
|
|
433
454
|
raise ValueError(f"unsupported database dialect '{dialect}'")
|
|
434
455
|
|
|
435
456
|
|
|
457
|
+
def model_uuid_type(dialect: str | None = None) -> sa.types.TypeEngine[py_uuid.UUID]:
|
|
458
|
+
"""
|
|
459
|
+
Returns the appropriate SQLAlchemy column type for a UUID based on the specified database dialect.
|
|
460
|
+
|
|
461
|
+
:param dialect: The database dialect to determine the column type for. If None, defaults to PostgreSQL.
|
|
462
|
+
:return: The SQLAlchemy column type to use for the UUID field.
|
|
463
|
+
"""
|
|
464
|
+
if dialect is None:
|
|
465
|
+
dialect = dialects.postgresql
|
|
466
|
+
if dialect == dialects.postgresql:
|
|
467
|
+
return sa_pg.UUID(as_uuid=True)
|
|
468
|
+
if dialect == dialects.sqlite:
|
|
469
|
+
return SQLiteUUID
|
|
470
|
+
raise ValueError(f"unsupported database dialect '{dialect}'")
|
|
471
|
+
|
|
472
|
+
|
|
436
473
|
def model_datetime_tz_type(dialect: str | None = None) -> sa.types.TypeEngine[datetime.datetime]:
|
|
437
474
|
"""
|
|
438
475
|
Returns the appropriate SQLAlchemy column type for a timezone-aware datetime field based on the specified database
|
{plexus_python_pulse_common-1.1.76.dist-info → plexus_python_pulse_common-1.1.77.dist-info}/RECORD
RENAMED
|
@@ -14,16 +14,17 @@ plexus/pulse/common/utils/bagutils.py,sha256=5JGnMgR8Q9OciW5dE7_2U7n1FGVEXMZXv1S
|
|
|
14
14
|
plexus/pulse/common/utils/config.py,sha256=YPtFK5QHTVsXwcFBGV0rlZad0GFtsgwysQNyKYjdzvk,2409
|
|
15
15
|
plexus/pulse/common/utils/datautils.py,sha256=OWlnyFSa6UaPbSSG3HzCJHxdiHyfHAE-FUVHaLfNZoM,9335
|
|
16
16
|
plexus/pulse/common/utils/dockerutils.py,sha256=7DF9yFGz8uHwpYk115xBWqOjs5etz0UE7yh66eDvBzs,5793
|
|
17
|
+
plexus/pulse/common/utils/flowutils.py,sha256=VmbVcCVztH8DH47s_5JbdOZchsEc7hZbq1am95amJzs,5307
|
|
17
18
|
plexus/pulse/common/utils/gisutils.py,sha256=UX_IHPeAx9mbA9p_SsbPFwdsdp7bGeaQhWaCTCzvrBM,11466
|
|
18
19
|
plexus/pulse/common/utils/jsonutils.py,sha256=zSbW94klN2jrkMdBECu4RNJX6zzNaSxauQ3FuihuB6E,3342
|
|
19
|
-
plexus/pulse/common/utils/ormutils.py,sha256=
|
|
20
|
+
plexus/pulse/common/utils/ormutils.py,sha256=JD-hKLEGkFWy7ojc3dC_90qtexe2UnLEvD90tEOHtCg,61897
|
|
20
21
|
plexus/pulse/common/utils/pathutils.py,sha256=S5xk2WEmxP8MiuSKIU3cAEtk4Nk5Lz7IqC5FCd8BEFI,9585
|
|
21
22
|
plexus/pulse/common/utils/s3utils.py,sha256=RArl95rWoyBEFyWU9X0c8WgoQBjP_jOK3OkPoKxIMnA,36025
|
|
22
23
|
plexus/pulse/common/utils/sqlutils.py,sha256=D6kTBjhO5YlNRt3uFlPt6z3uH61m9ajEzPYmsI6NoFc,231
|
|
23
24
|
plexus/pulse/common/utils/strutils.py,sha256=PkmMHCcHN9vALCy_8fJTf00Dg7I2N4QTCXdBmB3t7rk,16440
|
|
24
25
|
plexus/pulse/common/utils/tagutils.py,sha256=smM79dyRCiswKTTcI-At7FLxDH54VN4Id3aFHbDxzYo,61696
|
|
25
26
|
plexus/pulse/common/utils/testutils.py,sha256=hKPRvoN4mg8QGQJH121l59a0uo8jN5rntGQAVYplGBw,6166
|
|
26
|
-
plexus_python_pulse_common-1.1.
|
|
27
|
-
plexus_python_pulse_common-1.1.
|
|
28
|
-
plexus_python_pulse_common-1.1.
|
|
29
|
-
plexus_python_pulse_common-1.1.
|
|
27
|
+
plexus_python_pulse_common-1.1.77.dist-info/METADATA,sha256=Aei_-DjiA9AuGRzaDCZ5Vp3R8dJxcjXJSKFZ7SZwRHQ,1495
|
|
28
|
+
plexus_python_pulse_common-1.1.77.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
29
|
+
plexus_python_pulse_common-1.1.77.dist-info/top_level.txt,sha256=ug_g7CVwaMQuas5UzAXbHUrQvKGCn8ezc6ZNvvRlJOE,7
|
|
30
|
+
plexus_python_pulse_common-1.1.77.dist-info/RECORD,,
|
{plexus_python_pulse_common-1.1.76.dist-info → plexus_python_pulse_common-1.1.77.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|