plexus-python-pulse-common 1.1.76__tar.gz → 1.1.77__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 (94) hide show
  1. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/PKG-INFO +1 -1
  2. plexus_python_pulse_common-1.1.77/src/plexus/pulse/common/utils/flowutils.py +167 -0
  3. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/ormutils.py +38 -1
  4. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus_python_pulse_common.egg-info/PKG-INFO +1 -1
  5. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus_python_pulse_common.egg-info/SOURCES.txt +2 -0
  6. plexus_python_pulse_common-1.1.77/test/plexus_tests/pulse/common/utils/flowutils_test.py +36 -0
  7. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/.editorconfig +0 -0
  8. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/.github/workflows/pr.yml +0 -0
  9. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/.github/workflows/push.yml +0 -0
  10. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/.gitignore +0 -0
  11. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/MANIFEST.in +0 -0
  12. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/README.md +0 -0
  13. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/VERSION +0 -0
  14. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/pyproject.toml +0 -0
  15. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  16. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  17. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  18. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/0-dummy +0 -0
  19. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/1-dummy +0 -0
  20. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/2-dummy +0 -0
  21. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
  22. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
  23. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
  24. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
  25. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
  26. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
  27. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
  28. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
  29. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
  30. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
  31. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
  32. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
  33. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/pathutils/dummy.txt +0 -0
  34. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  35. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  36. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  37. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  38. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  39. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  40. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  41. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  42. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  43. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  44. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  45. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  46. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/setup.cfg +0 -0
  47. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/setup.py +0 -0
  48. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/__init__.py +0 -0
  49. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/carto/OSMFile.py +0 -0
  50. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/carto/OSMNode.py +0 -0
  51. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/carto/OSMTags.py +0 -0
  52. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/carto/OSMWay.py +0 -0
  53. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/carto/__init__.py +0 -0
  54. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/resources/__init__.py +0 -0
  55. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/resources/tags/__init__.py +0 -0
  56. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/resources/tags/unittest-1.0.0.tagset.yaml +0 -0
  57. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/resources/tags/universal-1.0.0.tagset.yaml +0 -0
  58. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/__init__.py +0 -0
  59. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/apiutils.py +0 -0
  60. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/bagutils.py +0 -0
  61. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/config.py +0 -0
  62. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/datautils.py +0 -0
  63. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/dockerutils.py +0 -0
  64. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/gisutils.py +0 -0
  65. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/jsonutils.py +0 -0
  66. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/pathutils.py +0 -0
  67. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/s3utils.py +0 -0
  68. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/sqlutils.py +0 -0
  69. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/strutils.py +0 -0
  70. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/tagutils.py +0 -0
  71. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus/pulse/common/utils/testutils.py +0 -0
  72. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus_python_pulse_common.egg-info/dependency_links.txt +0 -0
  73. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus_python_pulse_common.egg-info/not-zip-safe +0 -0
  74. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus_python_pulse_common.egg-info/requires.txt +0 -0
  75. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/src/plexus_python_pulse_common.egg-info/top_level.txt +0 -0
  76. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/__init__.py +0 -0
  77. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/__init__.py +0 -0
  78. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/__init__.py +0 -0
  79. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/carto/__init__.py +0 -0
  80. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/carto/osm_file_test.py +0 -0
  81. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/carto/osm_tags_test.py +0 -0
  82. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/__init__.py +0 -0
  83. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/bagutils_test.py +0 -0
  84. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/datautils_test.py +0 -0
  85. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/dockerutils_test.py +0 -0
  86. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/gisutils_test.py +0 -0
  87. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/jsonutils_test.py +0 -0
  88. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/ormutils_test.py +0 -0
  89. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/pathutils_test.py +0 -0
  90. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/s3utils_test.py +0 -0
  91. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/strutils_test.py +0 -0
  92. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/tagutils_test.py +0 -0
  93. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/plexus_tests/pulse/common/utils/testutils_test.py +0 -0
  94. {plexus_python_pulse_common-1.1.76 → plexus_python_pulse_common-1.1.77}/test/testenv.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-pulse-common
3
- Version: 1.1.76
3
+ Version: 1.1.77
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -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.DateTime
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-pulse-common
3
- Version: 1.1.76
3
+ Version: 1.1.77
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -54,6 +54,7 @@ src/plexus/pulse/common/utils/bagutils.py
54
54
  src/plexus/pulse/common/utils/config.py
55
55
  src/plexus/pulse/common/utils/datautils.py
56
56
  src/plexus/pulse/common/utils/dockerutils.py
57
+ src/plexus/pulse/common/utils/flowutils.py
57
58
  src/plexus/pulse/common/utils/gisutils.py
58
59
  src/plexus/pulse/common/utils/jsonutils.py
59
60
  src/plexus/pulse/common/utils/ormutils.py
@@ -80,6 +81,7 @@ test/plexus_tests/pulse/common/utils/__init__.py
80
81
  test/plexus_tests/pulse/common/utils/bagutils_test.py
81
82
  test/plexus_tests/pulse/common/utils/datautils_test.py
82
83
  test/plexus_tests/pulse/common/utils/dockerutils_test.py
84
+ test/plexus_tests/pulse/common/utils/flowutils_test.py
83
85
  test/plexus_tests/pulse/common/utils/gisutils_test.py
84
86
  test/plexus_tests/pulse/common/utils/jsonutils_test.py
85
87
  test/plexus_tests/pulse/common/utils/ormutils_test.py
@@ -0,0 +1,36 @@
1
+ import os
2
+ import tempfile
3
+ import unittest
4
+ import uuid as pyuuid
5
+ from unittest.mock import patch
6
+
7
+ import ddt
8
+
9
+ from plexus.pulse.common.utils.flowutils import FlowContext
10
+
11
+
12
+ @ddt.ddt
13
+ class FlowUtilsTest(unittest.TestCase):
14
+
15
+ def test_flow_context(self):
16
+ with tempfile.TemporaryDirectory(delete=True) as temp_dir:
17
+ with patch.dict(os.environ,
18
+ {
19
+ "PULSE_FLOW_CONTEXT_ROOT_DIR": temp_dir,
20
+ "PULSE_FLOW_RUN_ID": "01234567-89ab-cdef-0123-456789abcdef",
21
+ },
22
+ clear=True):
23
+ context = (
24
+ FlowContext()
25
+ .add_dataset_ref(pyuuid.UUID("01234567-89ab-cdef-0123-456789abcdef"), "test_dataset")
26
+ .add_dataset_ref(pyuuid.UUID("89abcdef-0123-4567-89ab-cdef01234567"), "another_test_dataset")
27
+ .add_dataset_ref(pyuuid.UUID("00000000-0000-0000-0000-000000000000"))
28
+ )
29
+
30
+ self.assertEqual(context.count_dataset_ref(), 3)
31
+
32
+ dataset, another_dataset, anonymous_dataset = context.list_dataset_refs()
33
+
34
+ self.assertEqual(dataset.dataset_uuid, pyuuid.UUID("01234567-89ab-cdef-0123-456789abcdef"))
35
+ self.assertEqual(another_dataset.dataset_uuid, pyuuid.UUID("89abcdef-0123-4567-89ab-cdef01234567"))
36
+ self.assertEqual(anonymous_dataset.dataset_uuid, pyuuid.UUID("00000000-0000-0000-0000-000000000000"))