plexus-python-pulse-common 1.1.77__tar.gz → 1.1.78__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.
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/PKG-INFO +2 -1
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/pyproject.toml +2 -0
- plexus_python_pulse_common-1.1.78/src/plexus/pulse/common/utils/flowutils.py +291 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/tagutils.py +36 -1
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus_python_pulse_common.egg-info/PKG-INFO +2 -1
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus_python_pulse_common.egg-info/requires.txt +1 -0
- plexus_python_pulse_common-1.1.78/test/plexus_tests/pulse/common/utils/flowutils_test.py +48 -0
- plexus_python_pulse_common-1.1.77/src/plexus/pulse/common/utils/flowutils.py +0 -167
- plexus_python_pulse_common-1.1.77/test/plexus_tests/pulse/common/utils/flowutils_test.py +0 -36
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/.editorconfig +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/.github/workflows/pr.yml +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/.github/workflows/push.yml +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/.gitignore +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/MANIFEST.in +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/README.md +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/VERSION +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/0-dummy +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/1-dummy +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/2-dummy +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/pathutils/dummy.txt +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/setup.cfg +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/setup.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/carto/OSMFile.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/carto/OSMNode.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/carto/OSMTags.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/carto/OSMWay.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/carto/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/resources/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/resources/tags/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/resources/tags/unittest-1.0.0.tagset.yaml +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/resources/tags/universal-1.0.0.tagset.yaml +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/apiutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/bagutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/config.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/datautils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/dockerutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/gisutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/jsonutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/ormutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/pathutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/s3utils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/sqlutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/strutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus/pulse/common/utils/testutils.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus_python_pulse_common.egg-info/SOURCES.txt +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus_python_pulse_common.egg-info/dependency_links.txt +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus_python_pulse_common.egg-info/not-zip-safe +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/src/plexus_python_pulse_common.egg-info/top_level.txt +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/carto/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/carto/osm_file_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/carto/osm_tags_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/__init__.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/bagutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/datautils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/dockerutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/gisutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/jsonutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/ormutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/pathutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/s3utils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/strutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/tagutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/test/plexus_tests/pulse/common/utils/testutils_test.py +0 -0
- {plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/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.
|
|
3
|
+
Version: 1.1.78
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.12
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -22,6 +22,7 @@ Requires-Dist: pyparsing>=3.2
|
|
|
22
22
|
Requires-Dist: pyproj>=3.6
|
|
23
23
|
Requires-Dist: pyquaternion>=0.9
|
|
24
24
|
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: requests>=2.0
|
|
25
26
|
Requires-Dist: sqlalchemy>=2.0
|
|
26
27
|
Requires-Dist: s3fs>=2025.10
|
|
27
28
|
Requires-Dist: rich>=13.9
|
|
@@ -24,6 +24,7 @@ dev = [
|
|
|
24
24
|
"pyproj>=3.6",
|
|
25
25
|
"pyquaternion>=0.9",
|
|
26
26
|
"pyyaml>=6.0",
|
|
27
|
+
"requests>=2.0",
|
|
27
28
|
"sqlalchemy>=2.0",
|
|
28
29
|
"s3fs>=2025.10",
|
|
29
30
|
"rich>=13.9",
|
|
@@ -71,6 +72,7 @@ dependencies = [
|
|
|
71
72
|
"pyproj>=3.6",
|
|
72
73
|
"pyquaternion>=0.9",
|
|
73
74
|
"pyyaml>=6.0",
|
|
75
|
+
"requests>=2.0",
|
|
74
76
|
"sqlalchemy>=2.0",
|
|
75
77
|
"s3fs>=2025.10",
|
|
76
78
|
"rich>=13.9",
|
|
@@ -0,0 +1,291 @@
|
|
|
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 requests
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
import sqlalchemy.dialects.sqlite as sa_sqlite
|
|
15
|
+
import sqlalchemy.orm as sa_orm
|
|
16
|
+
from plexus.common.utils.dbutils import ConnectionMaker
|
|
17
|
+
from plexus.common.utils.funcutils import Chainable
|
|
18
|
+
from plexus.common.utils.funcutils import chainable, singleton
|
|
19
|
+
from plexus.common.utils.jsonutils import JsonObject
|
|
20
|
+
from plexus.common.utils.retry import retry
|
|
21
|
+
from sqlmodel import Field
|
|
22
|
+
|
|
23
|
+
from plexus.pulse.common.utils.ormutils import SQLiteUUID, SequenceModelMixinProtocol
|
|
24
|
+
from plexus.pulse.common.utils.ormutils import clone_sequence_model_instance, make_base_model, make_sequence_model_mixin
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"flow_workspace_root_path",
|
|
28
|
+
"current_flow_run_id",
|
|
29
|
+
"current_flow_workspace_root_path",
|
|
30
|
+
"FlowStageEnum",
|
|
31
|
+
"current_flow_stage",
|
|
32
|
+
"stage_can_resolve_dataset_ref",
|
|
33
|
+
"make_dataset_ref_model_mixin",
|
|
34
|
+
"DatasetRef",
|
|
35
|
+
"DatasetRefTable",
|
|
36
|
+
"current_flow_context_file_path",
|
|
37
|
+
"FlowContext",
|
|
38
|
+
"flow_context",
|
|
39
|
+
"flow_api_url",
|
|
40
|
+
"resolve_dataset_ref",
|
|
41
|
+
"resolve_dataset_storage_location",
|
|
42
|
+
"resolve",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
flow_stage_env = "PULSE_FLOW_STAGE"
|
|
46
|
+
flow_run_id_env = "PULSE_FLOW_RUN_ID"
|
|
47
|
+
flow_workspace_root_dir_env = "PULSE_FLOW_WORKSPACE_ROOT_DIR"
|
|
48
|
+
flow_api_url_env = "PULSE_FLOW_API_URL"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def flow_workspace_root_path() -> pathlib.Path:
|
|
52
|
+
value = os.environ.get(flow_workspace_root_dir_env)
|
|
53
|
+
if value is None:
|
|
54
|
+
raise ValueError(f"Missing required environment variable '{flow_workspace_root_dir_env}'")
|
|
55
|
+
return pathlib.Path(value).absolute()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def current_flow_run_id() -> str:
|
|
59
|
+
value = os.environ.get(flow_run_id_env)
|
|
60
|
+
if value is None:
|
|
61
|
+
raise ValueError(f"Missing required environment variable '{flow_run_id_env}'")
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def current_flow_workspace_root_path() -> pathlib.Path:
|
|
66
|
+
return flow_workspace_root_path() / current_flow_run_id()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class FlowStageEnum(enum.Enum):
|
|
70
|
+
Preprocessing = "preprocessing"
|
|
71
|
+
Annotation = "annotation"
|
|
72
|
+
Curation = "curation"
|
|
73
|
+
Training = "training"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def current_flow_stage() -> FlowStageEnum:
|
|
77
|
+
value = os.environ.get(flow_stage_env)
|
|
78
|
+
if value is None:
|
|
79
|
+
raise ValueError(f"Missing required environment variable '{flow_stage_env}'")
|
|
80
|
+
for stage in list(FlowStageEnum):
|
|
81
|
+
if value == stage.value:
|
|
82
|
+
return stage
|
|
83
|
+
raise ValueError(f"Illegal '{flow_stage_env}' value '{value}'")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def stage_can_resolve_dataset_ref(stage: FlowStageEnum | None = None) -> bool:
|
|
87
|
+
if stage is None:
|
|
88
|
+
stage = current_flow_stage()
|
|
89
|
+
return stage in {FlowStageEnum.Curation}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def make_dataset_ref_model_mixin() -> type[pdt.BaseModel]:
|
|
93
|
+
class ModelMixin(pdt.BaseModel):
|
|
94
|
+
dataset_uuid: py_uuid.UUID = Field(
|
|
95
|
+
sa_column=sa.Column(SQLiteUUID(), nullable=False),
|
|
96
|
+
description="UUID of the dataset",
|
|
97
|
+
)
|
|
98
|
+
dataset_name: str = Field(
|
|
99
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(128), nullable=False),
|
|
100
|
+
description="Name of the dataset",
|
|
101
|
+
)
|
|
102
|
+
dataset_props: JsonObject | None = Field(
|
|
103
|
+
sa_column=sa.Column(sa_sqlite.JSON),
|
|
104
|
+
default=None,
|
|
105
|
+
description="Additional properties stored as JSON",
|
|
106
|
+
schema_extra=dict(examples=[{"example_prop_key": "example_prop_value"}]),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return ModelMixin
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
BaseModel = make_base_model()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class DatasetRef(BaseModel, make_dataset_ref_model_mixin()):
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class DatasetRefTable(DatasetRef, make_sequence_model_mixin("sqlite"), table=True):
|
|
120
|
+
__tablename__ = "dataset_ref"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if typing.TYPE_CHECKING:
|
|
124
|
+
class DatasetRef(BaseModel):
|
|
125
|
+
dataset_uuid: sa_orm.Mapped[py_uuid.UUID] = ...
|
|
126
|
+
dataset_name: sa_orm.Mapped[str] = ...
|
|
127
|
+
dataset_props: sa_orm.Mapped[JsonObject | None] = ...
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class DatasetRefTable(DatasetRef, SequenceModelMixinProtocol):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def current_flow_context_file_path() -> pathlib.Path:
|
|
135
|
+
return current_flow_workspace_root_path() / "flow_context.db"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class FlowContext(object):
|
|
139
|
+
def __init__(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
file_path: str | os.PathLike[str] | None = None,
|
|
143
|
+
fail_if_exists: bool = False,
|
|
144
|
+
):
|
|
145
|
+
self.file_path = pathlib.Path(file_path or current_flow_context_file_path())
|
|
146
|
+
if fail_if_exists and self.file_path.exists():
|
|
147
|
+
raise FileExistsError(f"flow context file '{self.file_path}' already exists")
|
|
148
|
+
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
|
|
150
|
+
self.conn_maker = ConnectionMaker.from_url(f"sqlite:///{self.file_path.absolute()}",
|
|
151
|
+
engine_opts=dict(connect_args={"check_same_thread": False}))
|
|
152
|
+
|
|
153
|
+
self.thread_lock = threading.Lock()
|
|
154
|
+
|
|
155
|
+
BaseModel.metadata.create_all(self.conn_maker.engine)
|
|
156
|
+
|
|
157
|
+
@contextlib.contextmanager
|
|
158
|
+
def make_session(self) -> Generator[sa_orm.Session, None, None]:
|
|
159
|
+
with self.thread_lock:
|
|
160
|
+
with self.conn_maker.make_session(expire_on_commit=False) as session:
|
|
161
|
+
try:
|
|
162
|
+
yield session
|
|
163
|
+
session.commit()
|
|
164
|
+
except Exception:
|
|
165
|
+
session.rollback()
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
def count_dataset_ref(self) -> int:
|
|
169
|
+
with self.make_session() as session:
|
|
170
|
+
return session.query(DatasetRefTable).count()
|
|
171
|
+
|
|
172
|
+
def list_dataset_refs(self) -> list[DatasetRefTable]:
|
|
173
|
+
with self.make_session() as session:
|
|
174
|
+
return session.query(DatasetRefTable).all()
|
|
175
|
+
|
|
176
|
+
def query_dataset_ref(
|
|
177
|
+
self,
|
|
178
|
+
dataset_uuid: py_uuid.UUID | None = None,
|
|
179
|
+
dataset_name: str | None = None,
|
|
180
|
+
) -> Chainable[Self, DatasetRefTable | None]:
|
|
181
|
+
if dataset_uuid is None and dataset_name is None:
|
|
182
|
+
raise ValueError("either 'dataset_uuid' or 'dataset_name' is required")
|
|
183
|
+
|
|
184
|
+
with self.make_session() as session:
|
|
185
|
+
query_stmt = session.query(DatasetRefTable)
|
|
186
|
+
if dataset_uuid is not None:
|
|
187
|
+
query_stmt = query_stmt.filter(DatasetRefTable.dataset_uuid == dataset_uuid)
|
|
188
|
+
if dataset_name is not None:
|
|
189
|
+
query_stmt = query_stmt.filter(DatasetRefTable.dataset_name == dataset_name)
|
|
190
|
+
db_dataset_ref = query_stmt.one_or_none()
|
|
191
|
+
|
|
192
|
+
return chainable(self, db_dataset_ref)
|
|
193
|
+
|
|
194
|
+
def add_dataset_ref(
|
|
195
|
+
self,
|
|
196
|
+
dataset_uuid: py_uuid.UUID,
|
|
197
|
+
dataset_name: str,
|
|
198
|
+
dataset_props: JsonObject | None = None,
|
|
199
|
+
) -> Chainable[Self, DatasetRefTable]:
|
|
200
|
+
with self.make_session() as session:
|
|
201
|
+
dataset_ref = DatasetRef(dataset_uuid=dataset_uuid, dataset_name=dataset_name, dataset_props=dataset_props)
|
|
202
|
+
db_dataset_ref = clone_sequence_model_instance(DatasetRefTable, dataset_ref)
|
|
203
|
+
session.add(db_dataset_ref)
|
|
204
|
+
session.commit()
|
|
205
|
+
|
|
206
|
+
return chainable(self, db_dataset_ref)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@singleton
|
|
210
|
+
def flow_context() -> FlowContext:
|
|
211
|
+
return FlowContext()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def flow_api_url() -> str:
|
|
215
|
+
value = os.environ.get(flow_api_url_env)
|
|
216
|
+
if value is None:
|
|
217
|
+
raise ValueError(f"Missing required environment variable '{flow_api_url_env}'")
|
|
218
|
+
return value
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@retry(wait=10, retrials=3)
|
|
222
|
+
def fetch_dataset_ref(
|
|
223
|
+
dataset_uuid: py_uuid.UUID | None = None,
|
|
224
|
+
dataset_name: str | None = None,
|
|
225
|
+
) -> DatasetRef | None:
|
|
226
|
+
response = requests.get(f"{flow_api_url()}/pulse_demeter/dataset_public/query_dataset",
|
|
227
|
+
headers={"accept": "application/json"},
|
|
228
|
+
params={
|
|
229
|
+
"dataset_uuid": str(dataset_uuid),
|
|
230
|
+
"dataset_name": dataset_name,
|
|
231
|
+
})
|
|
232
|
+
response.raise_for_status()
|
|
233
|
+
|
|
234
|
+
response_data = response.json()
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
uuid = py_uuid.UUID(response_data["dataset"]["uuid"])
|
|
238
|
+
name = response_data["dataset"]["name"]
|
|
239
|
+
props = response_data["dataset"].get("props", None)
|
|
240
|
+
return DatasetRef(dataset_uuid=uuid, dataset_name=name, dataset_props=props)
|
|
241
|
+
except KeyError:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def resolve_dataset_ref(
|
|
246
|
+
dataset_uuid: py_uuid.UUID | None = None,
|
|
247
|
+
dataset_name: str | None = None,
|
|
248
|
+
*,
|
|
249
|
+
context: FlowContext | None = None,
|
|
250
|
+
) -> DatasetRefTable:
|
|
251
|
+
if context is None:
|
|
252
|
+
context = flow_context()
|
|
253
|
+
|
|
254
|
+
if dataset_uuid is None and dataset_name is None:
|
|
255
|
+
raise ValueError("either 'dataset_uuid' or 'dataset_name' is required")
|
|
256
|
+
|
|
257
|
+
_, db_dataset_ref = context.query_dataset_ref(dataset_uuid=dataset_uuid, dataset_name=dataset_name)
|
|
258
|
+
if db_dataset_ref is None:
|
|
259
|
+
if not stage_can_resolve_dataset_ref():
|
|
260
|
+
# In some stages, the dataset ref should have been resolved and stored in the flow context.
|
|
261
|
+
# If not found, it's an error.
|
|
262
|
+
raise ValueError(f"cannot resolve dataset ref at flow stage '{current_flow_stage()}'")
|
|
263
|
+
|
|
264
|
+
dataset_ref = fetch_dataset_ref(dataset_uuid=dataset_uuid, dataset_name=dataset_name)
|
|
265
|
+
if dataset_ref is None:
|
|
266
|
+
raise ValueError(f"cannot found dataset with uuid '{str(dataset_uuid)}' or name '{dataset_name}'")
|
|
267
|
+
|
|
268
|
+
_, db_dataset_ref = context.add_dataset_ref(dataset_ref.dataset_uuid,
|
|
269
|
+
dataset_ref.dataset_name,
|
|
270
|
+
dataset_ref.dataset_props)
|
|
271
|
+
|
|
272
|
+
return db_dataset_ref
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def resolve_dataset_storage_location(
|
|
276
|
+
dataset_uuid: str | py_uuid.UUID | None = None,
|
|
277
|
+
dataset_name: str | None = None,
|
|
278
|
+
*,
|
|
279
|
+
context: FlowContext | None = None,
|
|
280
|
+
) -> pathlib.Path:
|
|
281
|
+
if isinstance(dataset_uuid, str):
|
|
282
|
+
return resolve_dataset_storage_location(py_uuid.UUID(dataset_uuid), dataset_name, context=context)
|
|
283
|
+
|
|
284
|
+
db_dataset_ref = resolve_dataset_ref(dataset_uuid, dataset_name, context=context)
|
|
285
|
+
if "storage_location" in (db_dataset_ref.dataset_props or {}):
|
|
286
|
+
return pathlib.Path(db_dataset_ref.dataset_props["storage_location"]).absolute()
|
|
287
|
+
|
|
288
|
+
return current_flow_workspace_root_path() / "datasets" / str(db_dataset_ref.dataset_uuid)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
resolve = resolve_dataset_storage_location
|
|
@@ -26,7 +26,7 @@ from plexus.common.utils.iterutils import dicttree_add, dicttree_remove
|
|
|
26
26
|
from plexus.common.utils.iterutils import dicttree_children, dicttree_lineage, dicttree_subtree
|
|
27
27
|
from plexus.common.utils.jsonutils import JsonObject, JsonType
|
|
28
28
|
from plexus.common.utils.randutils import randomizer
|
|
29
|
-
from plexus.common.utils.strutils import is_blank
|
|
29
|
+
from plexus.common.utils.strutils import is_blank, parse_bool_or
|
|
30
30
|
from sqlmodel import Field
|
|
31
31
|
|
|
32
32
|
from plexus.pulse.common.resources.tags import predefined_tagset_specs
|
|
@@ -47,6 +47,7 @@ __all__ = [
|
|
|
47
47
|
"MutableTagset",
|
|
48
48
|
"populate_tagset",
|
|
49
49
|
"predefined_tagsets",
|
|
50
|
+
"seek_predefined_tagset_of",
|
|
50
51
|
"render_tagset_markdown_readme",
|
|
51
52
|
"make_tag_target_model_mixin",
|
|
52
53
|
"make_tag_record_model_mixin",
|
|
@@ -356,6 +357,23 @@ def predefined_tagsets() -> dict[str, Tagset]:
|
|
|
356
357
|
return tagsets
|
|
357
358
|
|
|
358
359
|
|
|
360
|
+
def seek_predefined_tagset_of(
|
|
361
|
+
tag: str | Tag | BoundTag,
|
|
362
|
+
*,
|
|
363
|
+
tagset_namespace: str | None = None,
|
|
364
|
+
tagset_version: str | None = None,
|
|
365
|
+
) -> Tagset | None:
|
|
366
|
+
if isinstance(tag, BoundTag):
|
|
367
|
+
versioned_namespace = versioned_identifier(tagset_namespace or tag.namespace, tagset_version or tag.version)
|
|
368
|
+
else:
|
|
369
|
+
versioned_namespace = versioned_identifier(tagset_namespace, tagset_version)
|
|
370
|
+
|
|
371
|
+
tagset = predefined_tagsets().get(versioned_namespace)
|
|
372
|
+
if tagset is None or tag not in tagset:
|
|
373
|
+
return None
|
|
374
|
+
return tagset
|
|
375
|
+
|
|
376
|
+
|
|
359
377
|
def render_tagset_markdown_readme(tagset: Tagset) -> str:
|
|
360
378
|
def render_desc(desc: str | RichDesc | None) -> str:
|
|
361
379
|
if desc is None:
|
|
@@ -628,6 +646,16 @@ def tag_cache_file_path() -> pathlib.Path:
|
|
|
628
646
|
return pathlib.Path.home() / ".local" / "plexus" / "tag_cache" / f"{randomizer().random_alphanumeric(7)}.db"
|
|
629
647
|
|
|
630
648
|
|
|
649
|
+
tag_cache_strict_mode_env = "PULSE_TAG_CACHE_STRICT_MODE"
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def is_tag_cache_strict_mode() -> bool:
|
|
653
|
+
value = os.environ.get(tag_cache_strict_mode_env)
|
|
654
|
+
if value is None:
|
|
655
|
+
return False
|
|
656
|
+
return parse_bool_or(value) or False
|
|
657
|
+
|
|
658
|
+
|
|
631
659
|
class TagCache(object):
|
|
632
660
|
def __init__(
|
|
633
661
|
self,
|
|
@@ -1089,6 +1117,13 @@ class TagCache(object):
|
|
|
1089
1117
|
instance, this parameter will be ignored and the version from the instance will be used.
|
|
1090
1118
|
:return: Self instance for chaining along with the added ``TagRecordTable`` instance
|
|
1091
1119
|
"""
|
|
1120
|
+
if is_tag_cache_strict_mode():
|
|
1121
|
+
tagset = seek_predefined_tagset_of(tag, tagset_namespace=tagset_namespace, tagset_version=tagset_version)
|
|
1122
|
+
if tagset is None:
|
|
1123
|
+
raise ValueError(
|
|
1124
|
+
f"cannot find in the predefined tagset the specified tag '{tag}', with namespace '{tagset_namespace}' and version '{tagset_version}'"
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1092
1127
|
with self.make_session() as session:
|
|
1093
1128
|
tag_record = TagRecord(
|
|
1094
1129
|
target_sqn=target_sqn,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plexus-python-pulse-common
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.78
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.12
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -22,6 +22,7 @@ Requires-Dist: pyparsing>=3.2
|
|
|
22
22
|
Requires-Dist: pyproj>=3.6
|
|
23
23
|
Requires-Dist: pyquaternion>=0.9
|
|
24
24
|
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: requests>=2.0
|
|
25
26
|
Requires-Dist: sqlalchemy>=2.0
|
|
26
27
|
Requires-Dist: s3fs>=2025.10
|
|
27
28
|
Requires-Dist: rich>=13.9
|
|
@@ -0,0 +1,48 @@
|
|
|
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_WORKSPACE_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"), "one_dataset")
|
|
26
|
+
.add_dataset_ref(pyuuid.UUID("89abcdef-0123-4567-89ab-cdef01234567"), "another_dataset")
|
|
27
|
+
.add_dataset_ref(pyuuid.UUID("00000000-0000-0000-0000-000000000000"), "zero_dataset")
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
self.assertEqual(context.count_dataset_ref(), 3)
|
|
31
|
+
|
|
32
|
+
one_dataset, another_dataset, zero_dataset = context.list_dataset_refs()
|
|
33
|
+
|
|
34
|
+
self.assertEqual(one_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(zero_dataset.dataset_uuid, pyuuid.UUID("00000000-0000-0000-0000-000000000000"))
|
|
37
|
+
|
|
38
|
+
# This is the same flow context file as above, but we are creating a new `FlowContext` instance
|
|
39
|
+
# via explicit file path which is deducted from the mocked environment variables
|
|
40
|
+
context = FlowContext(file_path=f"{temp_dir}/01234567-89ab-cdef-0123-456789abcdef/flow_context.db")
|
|
41
|
+
|
|
42
|
+
self.assertEqual(context.count_dataset_ref(), 3)
|
|
43
|
+
|
|
44
|
+
one_dataset, another_dataset, zero_dataset = context.list_dataset_refs()
|
|
45
|
+
|
|
46
|
+
self.assertEqual(one_dataset.dataset_uuid, pyuuid.UUID("01234567-89ab-cdef-0123-456789abcdef"))
|
|
47
|
+
self.assertEqual(another_dataset.dataset_uuid, pyuuid.UUID("89abcdef-0123-4567-89ab-cdef01234567"))
|
|
48
|
+
self.assertEqual(zero_dataset.dataset_uuid, pyuuid.UUID("00000000-0000-0000-0000-000000000000"))
|
|
@@ -1,167 +0,0 @@
|
|
|
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,36 +0,0 @@
|
|
|
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"))
|
|
File without changes
|
{plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/.github/workflows/pr.yml
RENAMED
|
File without changes
|
{plexus_python_pulse_common-1.1.77 → plexus_python_pulse_common-1.1.78}/.github/workflows/push.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|