plexus-python-common 1.0.65__tar.gz → 1.0.67__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_common-1.0.65 → plexus_python_common-1.0.67}/PKG-INFO +1 -1
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/jsonutils.py +6 -2
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/ormutils.py +69 -35
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/tagutils.py +190 -163
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/testutils.py +2 -1
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/.editorconfig +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/.github/workflows/pr.yml +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/.github/workflows/push.yml +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/.gitignore +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/MANIFEST.in +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/README.md +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/VERSION +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/pyproject.toml +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/0-dummy +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/1-dummy +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/2-dummy +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.txt +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/setup.cfg +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/setup.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/OSMFile.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/OSMNode.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/OSMTags.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/OSMWay.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/resources/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/resources/tags/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/resources/tags/unittest-1.0.0.tagset.yaml +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/resources/tags/universal-1.0.0.tagset.yaml +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/apiutils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/bagutils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/config.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/datautils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/dockerutils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/gisutils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/pathutils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/s3utils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/sqlutils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/strutils.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus_python_common.egg-info/SOURCES.txt +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus_python_common.egg-info/requires.txt +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus_python_common.egg-info/top_level.txt +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/carto/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/__init__.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/datautils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/gisutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/ormutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/pathutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/strutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/tagutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/utils/testutils_test.py +0 -0
- {plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/testenv.py +0 -0
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/jsonutils.py
RENAMED
|
@@ -21,7 +21,9 @@ __all__ = [
|
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def json_datetime_decoder(v: Any) -> datetime.datetime:
|
|
24
|
+
def json_datetime_decoder(v: Any) -> datetime.datetime | None:
|
|
25
|
+
if v is None:
|
|
26
|
+
return None
|
|
25
27
|
if isinstance(v, str):
|
|
26
28
|
return json_datetime_decoder(dt_parse(v, extended_format(with_us=True, with_tz=True)))
|
|
27
29
|
if isinstance(v, datetime.datetime):
|
|
@@ -29,7 +31,9 @@ def json_datetime_decoder(v: Any) -> datetime.datetime:
|
|
|
29
31
|
raise ValueError("unexpected type of value for datetime decoder")
|
|
30
32
|
|
|
31
33
|
|
|
32
|
-
def json_datetime_encoder(v: Any) -> str:
|
|
34
|
+
def json_datetime_encoder(v: Any) -> str | None:
|
|
35
|
+
if v is None:
|
|
36
|
+
return None
|
|
33
37
|
if isinstance(v, str):
|
|
34
38
|
return json_datetime_encoder(dt_parse(v, extended_format(with_us=True, with_tz=True)))
|
|
35
39
|
if isinstance(v, datetime.datetime):
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/ormutils.py
RENAMED
|
@@ -28,6 +28,10 @@ __all__ = [
|
|
|
28
28
|
"ChangingModelMixinProtocol",
|
|
29
29
|
"SnapshotModelMixinProtocol",
|
|
30
30
|
"RevisionModelMixinProtocol",
|
|
31
|
+
"SQLiteDateTime",
|
|
32
|
+
"model_sqn_type",
|
|
33
|
+
"model_datetime_tz_type",
|
|
34
|
+
"model_revision_type",
|
|
31
35
|
"SequenceModelMixin",
|
|
32
36
|
"ChangingModelMixin",
|
|
33
37
|
"SnapshotModelMixin",
|
|
@@ -391,12 +395,26 @@ class RevisionModelMixinProtocol(SequenceModelMixinProtocol):
|
|
|
391
395
|
...
|
|
392
396
|
|
|
393
397
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
398
|
+
class SQLiteDateTime(sa.TypeDecorator):
|
|
399
|
+
"""
|
|
400
|
+
Custom SQLAlchemy type decorator for handling timezone-aware datetimes in SQLite.
|
|
401
|
+
"""
|
|
402
|
+
impl = sa.DateTime
|
|
403
|
+
cache_ok = True
|
|
404
|
+
|
|
405
|
+
def process_bind_param(self, value: datetime.datetime | None, dialect) -> datetime.datetime | None:
|
|
406
|
+
if value is None:
|
|
407
|
+
return None
|
|
408
|
+
if value.tzinfo is None:
|
|
409
|
+
return value.replace(tzinfo=datetime.timezone.utc)
|
|
410
|
+
if value.tzinfo == datetime.timezone.utc:
|
|
411
|
+
return value
|
|
412
|
+
raise ValueError("Only UTC timezone-aware datetimes are supported")
|
|
413
|
+
|
|
414
|
+
def process_result_value(self, value: datetime.datetime | None, dialect) -> datetime.datetime | None:
|
|
415
|
+
if value is None:
|
|
416
|
+
return None
|
|
417
|
+
return value.replace(tzinfo=datetime.timezone.utc)
|
|
400
418
|
|
|
401
419
|
|
|
402
420
|
def model_sqn_type(dialect: str | None = None) -> sa.types.TypeEngine[int]:
|
|
@@ -428,7 +446,7 @@ def model_datetime_tz_type(dialect: str | None = None) -> sa.types.TypeEngine[da
|
|
|
428
446
|
if dialect == dialects.postgresql:
|
|
429
447
|
return sa_pg.TIMESTAMP(timezone=True)
|
|
430
448
|
if dialect == dialects.sqlite:
|
|
431
|
-
return
|
|
449
|
+
return SQLiteDateTime
|
|
432
450
|
raise ValueError(f"unsupported database dialect '{dialect}'")
|
|
433
451
|
|
|
434
452
|
|
|
@@ -448,6 +466,14 @@ def model_revision_type(dialect: str | None = None) -> sa.types.TypeEngine[int]:
|
|
|
448
466
|
raise ValueError(f"unsupported database dialect '{dialect}'")
|
|
449
467
|
|
|
450
468
|
|
|
469
|
+
# At the present time, we cannot express intersection of Protocol and SQLModel directly.
|
|
470
|
+
# Thus, we define union types here for the mixins.
|
|
471
|
+
SequenceModelMixin = SequenceModelMixinProtocol | pdt.BaseModel
|
|
472
|
+
ChangingModelMixin = ChangingModelMixinProtocol | pdt.BaseModel
|
|
473
|
+
SnapshotModelMixin = SnapshotModelMixinProtocol | pdt.BaseModel
|
|
474
|
+
RevisionModelMixin = RevisionModelMixinProtocol | pdt.BaseModel
|
|
475
|
+
|
|
476
|
+
|
|
451
477
|
def make_sequence_model_mixin(dialect: str | None = None) -> type[SequenceModelMixin]:
|
|
452
478
|
"""
|
|
453
479
|
Creates a mixin class for SQLModel models that adds a unique identifier field ``sqn``.
|
|
@@ -457,7 +483,7 @@ def make_sequence_model_mixin(dialect: str | None = None) -> type[SequenceModelM
|
|
|
457
483
|
:return: A mixin class that can be used with SQLModel models to add the ``sqn`` field.
|
|
458
484
|
"""
|
|
459
485
|
|
|
460
|
-
class ModelMixin(
|
|
486
|
+
class ModelMixin(pdt.BaseModel):
|
|
461
487
|
sqn: int | None = Field(
|
|
462
488
|
sa_column=sa.Column(model_sqn_type(dialect), primary_key=True, autoincrement=True),
|
|
463
489
|
default=None,
|
|
@@ -477,7 +503,7 @@ def make_changing_model_mixin(dialect: str | None = None) -> type[ChangingModelM
|
|
|
477
503
|
updatable records.
|
|
478
504
|
"""
|
|
479
505
|
|
|
480
|
-
class ModelMixin(
|
|
506
|
+
class ModelMixin(pdt.BaseModel):
|
|
481
507
|
sqn: int | None = Field(
|
|
482
508
|
sa_column=sa.Column(model_sqn_type(dialect), primary_key=True, autoincrement=True),
|
|
483
509
|
default=None,
|
|
@@ -496,14 +522,14 @@ def make_changing_model_mixin(dialect: str | None = None) -> type[ChangingModelM
|
|
|
496
522
|
|
|
497
523
|
@pdt.field_validator("created_at", mode="after")
|
|
498
524
|
@classmethod
|
|
499
|
-
def validate_created_at(cls, v: datetime.datetime) -> datetime.datetime:
|
|
525
|
+
def validate_created_at(cls, v: datetime.datetime | None) -> datetime.datetime | None:
|
|
500
526
|
if v is not None:
|
|
501
527
|
validate_dt_timezone(v)
|
|
502
528
|
return v
|
|
503
529
|
|
|
504
530
|
@pdt.field_validator("updated_at", mode="after")
|
|
505
531
|
@classmethod
|
|
506
|
-
def validate_updated_at(cls, v: datetime.datetime) -> datetime.datetime:
|
|
532
|
+
def validate_updated_at(cls, v: datetime.datetime | None) -> datetime.datetime | None:
|
|
507
533
|
if v is not None:
|
|
508
534
|
validate_dt_timezone(v)
|
|
509
535
|
return v
|
|
@@ -547,7 +573,7 @@ def make_snapshot_model_mixin(dialect: str | None = None) -> type[SnapshotModelM
|
|
|
547
573
|
record snapshots.
|
|
548
574
|
"""
|
|
549
575
|
|
|
550
|
-
class ModelMixin(
|
|
576
|
+
class ModelMixin(pdt.BaseModel):
|
|
551
577
|
sqn: int | None = Field(
|
|
552
578
|
sa_column=sa.Column(model_sqn_type(dialect), primary_key=True, autoincrement=True),
|
|
553
579
|
default=None,
|
|
@@ -571,14 +597,14 @@ def make_snapshot_model_mixin(dialect: str | None = None) -> type[SnapshotModelM
|
|
|
571
597
|
|
|
572
598
|
@pdt.field_validator("created_at", mode="after")
|
|
573
599
|
@classmethod
|
|
574
|
-
def validate_created_at(cls, v: datetime.datetime) -> datetime.datetime:
|
|
600
|
+
def validate_created_at(cls, v: datetime.datetime | None) -> datetime.datetime | None:
|
|
575
601
|
if v is not None:
|
|
576
602
|
validate_dt_timezone(v)
|
|
577
603
|
return v
|
|
578
604
|
|
|
579
605
|
@pdt.field_validator("expired_at", mode="after")
|
|
580
606
|
@classmethod
|
|
581
|
-
def validate_expired_at(cls, v: datetime.datetime) -> datetime.datetime:
|
|
607
|
+
def validate_expired_at(cls, v: datetime.datetime | None) -> datetime.datetime | None:
|
|
582
608
|
if v is not None:
|
|
583
609
|
validate_dt_timezone(v)
|
|
584
610
|
return v
|
|
@@ -650,7 +676,7 @@ def make_revision_model_mixin(dialect: str | None = None) -> type[RevisionModelM
|
|
|
650
676
|
record revisions.
|
|
651
677
|
"""
|
|
652
678
|
|
|
653
|
-
class ModelMixin(
|
|
679
|
+
class ModelMixin(pdt.BaseModel):
|
|
654
680
|
sqn: int | None = Field(
|
|
655
681
|
sa_column=sa.Column(model_sqn_type(dialect), primary_key=True, autoincrement=True),
|
|
656
682
|
default=None,
|
|
@@ -684,28 +710,28 @@ def make_revision_model_mixin(dialect: str | None = None) -> type[RevisionModelM
|
|
|
684
710
|
|
|
685
711
|
@pdt.field_validator("created_at", mode="after")
|
|
686
712
|
@classmethod
|
|
687
|
-
def validate_created_at(cls, v: datetime.datetime) -> datetime.datetime:
|
|
713
|
+
def validate_created_at(cls, v: datetime.datetime | None) -> datetime.datetime | None:
|
|
688
714
|
if v is not None:
|
|
689
715
|
validate_dt_timezone(v)
|
|
690
716
|
return v
|
|
691
717
|
|
|
692
718
|
@pdt.field_validator("updated_at", mode="after")
|
|
693
719
|
@classmethod
|
|
694
|
-
def validate_updated_at(cls, v: datetime.datetime) -> datetime.datetime:
|
|
720
|
+
def validate_updated_at(cls, v: datetime.datetime | None) -> datetime.datetime | None:
|
|
695
721
|
if v is not None:
|
|
696
722
|
validate_dt_timezone(v)
|
|
697
723
|
return v
|
|
698
724
|
|
|
699
725
|
@pdt.field_validator("expired_at", mode="after")
|
|
700
726
|
@classmethod
|
|
701
|
-
def validate_expired_at(cls, v: datetime.datetime) -> datetime.datetime:
|
|
727
|
+
def validate_expired_at(cls, v: datetime.datetime | None) -> datetime.datetime | None:
|
|
702
728
|
if v is not None:
|
|
703
729
|
validate_dt_timezone(v)
|
|
704
730
|
return v
|
|
705
731
|
|
|
706
732
|
@pdt.field_validator("revision", mode="after")
|
|
707
733
|
@classmethod
|
|
708
|
-
def validate_revision(cls, v: int) -> int:
|
|
734
|
+
def validate_revision(cls, v: int | None) -> int | None:
|
|
709
735
|
if v is not None and not v > 0:
|
|
710
736
|
raise ValueError("revision number must be positive integer")
|
|
711
737
|
return v
|
|
@@ -941,11 +967,12 @@ def clone_sequence_model_instance[SequenceModelT: SequenceModelMixin](
|
|
|
941
967
|
model: type[SequenceModelT],
|
|
942
968
|
instance: SequenceModelMixin,
|
|
943
969
|
*,
|
|
970
|
+
validate_only: bool = False,
|
|
944
971
|
clear_meta_fields: bool = True,
|
|
945
|
-
inplace: bool = False,
|
|
946
972
|
) -> SequenceModelT:
|
|
947
973
|
result = model.model_validate(instance)
|
|
948
|
-
|
|
974
|
+
if validate_only:
|
|
975
|
+
return instance
|
|
949
976
|
if clear_meta_fields:
|
|
950
977
|
result.sqn = None
|
|
951
978
|
return result
|
|
@@ -955,11 +982,12 @@ def clone_changing_model_instance[ChangingModelT: ChangingModelMixin](
|
|
|
955
982
|
model: type[ChangingModelT],
|
|
956
983
|
instance: ChangingModelMixin,
|
|
957
984
|
*,
|
|
985
|
+
validate_only: bool = False,
|
|
958
986
|
clear_meta_fields: bool = True,
|
|
959
|
-
inplace: bool = False,
|
|
960
987
|
) -> ChangingModelT:
|
|
961
988
|
result = model.model_validate(instance)
|
|
962
|
-
|
|
989
|
+
if validate_only:
|
|
990
|
+
return instance
|
|
963
991
|
if clear_meta_fields:
|
|
964
992
|
result.sqn = None
|
|
965
993
|
result.created_at = None
|
|
@@ -971,11 +999,12 @@ def clone_snapshot_model_instance[SnapshotModelT: SnapshotModelMixin](
|
|
|
971
999
|
model: type[SnapshotModelT],
|
|
972
1000
|
instance: SnapshotModelMixin,
|
|
973
1001
|
*,
|
|
1002
|
+
validate_only: bool = False,
|
|
974
1003
|
clear_meta_fields: bool = True,
|
|
975
|
-
inplace: bool = False,
|
|
976
1004
|
) -> SnapshotModelT:
|
|
977
1005
|
result = model.model_validate(instance)
|
|
978
|
-
|
|
1006
|
+
if validate_only:
|
|
1007
|
+
return instance
|
|
979
1008
|
if clear_meta_fields:
|
|
980
1009
|
result.sqn = None
|
|
981
1010
|
result.created_at = None
|
|
@@ -988,11 +1017,12 @@ def clone_revision_model_instance[RevisionModelT: RevisionModelMixin](
|
|
|
988
1017
|
model: type[RevisionModelT],
|
|
989
1018
|
instance: RevisionModelMixin,
|
|
990
1019
|
*,
|
|
1020
|
+
validate_only: bool = False,
|
|
991
1021
|
clear_meta_fields: bool = True,
|
|
992
|
-
inplace: bool = False,
|
|
993
1022
|
) -> RevisionModelT:
|
|
994
1023
|
result = model.model_validate(instance)
|
|
995
|
-
|
|
1024
|
+
if validate_only:
|
|
1025
|
+
return instance
|
|
996
1026
|
if clear_meta_fields:
|
|
997
1027
|
result.sqn = None
|
|
998
1028
|
result.created_at = None
|
|
@@ -1081,7 +1111,7 @@ def db_update_sequence_model[SequenceModelT: SequenceModelMixin](
|
|
|
1081
1111
|
raise sa_exc.NoResultFound(f"'{model_name_of(model)}' of specified sqn '{sqn}' not found")
|
|
1082
1112
|
|
|
1083
1113
|
db_instance = model_copy_from(db_instance, clone_sequence_model_instance(model, instance), exclude_none=True)
|
|
1084
|
-
|
|
1114
|
+
clone_sequence_model_instance(model, db_instance, validate_only=True)
|
|
1085
1115
|
db.flush()
|
|
1086
1116
|
|
|
1087
1117
|
return db_instance
|
|
@@ -1146,7 +1176,7 @@ def db_update_changing_model[ChangingModelT: ChangingModelMixin](
|
|
|
1146
1176
|
|
|
1147
1177
|
db_instance = model_copy_from(db_instance, clone_changing_model_instance(model, instance), exclude_none=True)
|
|
1148
1178
|
db_instance.updated_at = updated_at
|
|
1149
|
-
|
|
1179
|
+
clone_changing_model_instance(model, db_instance, validate_only=True)
|
|
1150
1180
|
db.flush()
|
|
1151
1181
|
|
|
1152
1182
|
return db_instance
|
|
@@ -1304,13 +1334,14 @@ def db_update_snapshot_model[SnapshotModelT: SnapshotModelMixin](
|
|
|
1304
1334
|
raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
|
|
1305
1335
|
|
|
1306
1336
|
db_instance.expired_at = updated_at
|
|
1307
|
-
|
|
1337
|
+
clone_snapshot_model_instance(model, db_instance, validate_only=True)
|
|
1308
1338
|
db.flush()
|
|
1309
1339
|
|
|
1310
1340
|
db_new_instance = clone_snapshot_model_instance(model, instance)
|
|
1311
1341
|
db_new_instance.record_sqn = record_sqn
|
|
1312
1342
|
db_new_instance.created_at = updated_at
|
|
1313
1343
|
db_new_instance.expired_at = None
|
|
1344
|
+
clone_snapshot_model_instance(model, db_new_instance, validate_only=True)
|
|
1314
1345
|
db.add(db_new_instance)
|
|
1315
1346
|
db.flush()
|
|
1316
1347
|
|
|
@@ -1333,7 +1364,7 @@ def db_expire_snapshot_model[SnapshotModelT: SnapshotModelMixin](
|
|
|
1333
1364
|
raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
|
|
1334
1365
|
|
|
1335
1366
|
db_instance.expired_at = updated_at
|
|
1336
|
-
|
|
1367
|
+
clone_snapshot_model_instance(model, db_instance, validate_only=True)
|
|
1337
1368
|
db.flush()
|
|
1338
1369
|
|
|
1339
1370
|
return db_instance
|
|
@@ -1364,9 +1395,10 @@ def db_activate_snapshot_model[SnapshotModelT: SnapshotModelMixin](
|
|
|
1364
1395
|
db_new_instance.record_sqn = record_sqn
|
|
1365
1396
|
db_new_instance.created_at = db_instance.expired_at
|
|
1366
1397
|
db_new_instance.expired_at = updated_at
|
|
1367
|
-
|
|
1398
|
+
clone_snapshot_model_instance(model, db_new_instance, validate_only=True)
|
|
1368
1399
|
db_new_instance.created_at = updated_at
|
|
1369
1400
|
db_new_instance.expired_at = None
|
|
1401
|
+
clone_snapshot_model_instance(model, db_new_instance, validate_only=True)
|
|
1370
1402
|
db.add(db_new_instance)
|
|
1371
1403
|
db.flush()
|
|
1372
1404
|
|
|
@@ -1529,7 +1561,7 @@ def db_update_revision_model[RevisionModelT: RevisionModelMixin](
|
|
|
1529
1561
|
raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
|
|
1530
1562
|
|
|
1531
1563
|
db_instance.expired_at = updated_at
|
|
1532
|
-
|
|
1564
|
+
clone_revision_model_instance(model, db_instance, validate_only=True)
|
|
1533
1565
|
db.flush()
|
|
1534
1566
|
|
|
1535
1567
|
db_new_instance = clone_revision_model_instance(model, instance)
|
|
@@ -1538,6 +1570,7 @@ def db_update_revision_model[RevisionModelT: RevisionModelMixin](
|
|
|
1538
1570
|
db_new_instance.updated_at = updated_at
|
|
1539
1571
|
db_new_instance.expired_at = None
|
|
1540
1572
|
db_new_instance.revision = db_instance.revision + 1
|
|
1573
|
+
clone_revision_model_instance(model, db_new_instance, validate_only=True)
|
|
1541
1574
|
db.add(db_new_instance)
|
|
1542
1575
|
db.flush()
|
|
1543
1576
|
|
|
@@ -1560,7 +1593,7 @@ def db_expire_revision_model[RevisionModelT: RevisionModelMixin](
|
|
|
1560
1593
|
raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
|
|
1561
1594
|
|
|
1562
1595
|
db_instance.expired_at = updated_at
|
|
1563
|
-
|
|
1596
|
+
clone_revision_model_instance(model, db_instance, validate_only=True)
|
|
1564
1597
|
db.flush()
|
|
1565
1598
|
|
|
1566
1599
|
return db_instance
|
|
@@ -1593,9 +1626,10 @@ def db_activate_revision_model[RevisionModelT: RevisionModelMixin](
|
|
|
1593
1626
|
db_new_instance.updated_at = db_instance.expired_at
|
|
1594
1627
|
db_new_instance.expired_at = updated_at
|
|
1595
1628
|
db_new_instance.revision = db_instance.revision + 1
|
|
1596
|
-
|
|
1629
|
+
clone_revision_model_instance(model, db_new_instance, validate_only=True)
|
|
1597
1630
|
db_new_instance.updated_at = updated_at
|
|
1598
1631
|
db_new_instance.expired_at = None
|
|
1632
|
+
clone_revision_model_instance(model, db_new_instance, validate_only=True)
|
|
1599
1633
|
db.add(db_new_instance)
|
|
1600
1634
|
db.flush()
|
|
1601
1635
|
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/tagutils.py
RENAMED
|
@@ -27,12 +27,13 @@ from iker.common.utils.iterutils import dicttree_children, dicttree_lineage, dic
|
|
|
27
27
|
from iker.common.utils.jsonutils import JsonObject, JsonType
|
|
28
28
|
from iker.common.utils.randutils import randomizer
|
|
29
29
|
from iker.common.utils.strutils import is_blank
|
|
30
|
-
from sqlmodel import Field
|
|
30
|
+
from sqlmodel import Field
|
|
31
31
|
|
|
32
32
|
from plexus.common.resources.tags import predefined_tagset_specs
|
|
33
33
|
from plexus.common.utils.datautils import validate_colon_tag, validate_snake_case, validate_vehicle_name
|
|
34
34
|
from plexus.common.utils.datautils import validate_dt_timezone, validate_semver, validate_slash_tag
|
|
35
35
|
from plexus.common.utils.jsonutils import json_datetime_encoder
|
|
36
|
+
from plexus.common.utils.ormutils import SQLiteDateTime
|
|
36
37
|
from plexus.common.utils.ormutils import SequenceModelMixinProtocol
|
|
37
38
|
from plexus.common.utils.ormutils import clone_sequence_model_instance, make_base_model, make_sequence_model_mixin
|
|
38
39
|
from plexus.common.utils.sqlutils import escape_sql_like
|
|
@@ -45,6 +46,8 @@ __all__ = [
|
|
|
45
46
|
"populate_tagset",
|
|
46
47
|
"predefined_tagsets",
|
|
47
48
|
"render_tagset_markdown_readme",
|
|
49
|
+
"make_tag_target_model_mixin",
|
|
50
|
+
"make_tag_record_model_mixin",
|
|
48
51
|
"TagTarget",
|
|
49
52
|
"TagRecord",
|
|
50
53
|
"TagTargetTable",
|
|
@@ -375,173 +378,187 @@ def render_tagset_markdown_readme(tagset: Tagset) -> str:
|
|
|
375
378
|
return env.from_string(template_str).render(tagset=tagset)
|
|
376
379
|
|
|
377
380
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
description="End datetime of the target range associated with the tag record",
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
@pdt.field_validator("identifier", mode="after")
|
|
408
|
-
@classmethod
|
|
409
|
-
def validate_identifier(cls, v: str) -> str:
|
|
410
|
-
validate_slash_tag(v)
|
|
411
|
-
return v
|
|
412
|
-
|
|
413
|
-
@pdt.field_validator("tagger_name", mode="after")
|
|
414
|
-
@classmethod
|
|
415
|
-
def validate_tagger_name(cls, v: str) -> str:
|
|
416
|
-
validate_snake_case(v)
|
|
417
|
-
return v
|
|
418
|
-
|
|
419
|
-
@pdt.field_validator("tagger_version", mode="after")
|
|
420
|
-
@classmethod
|
|
421
|
-
def validate_tagger_version(cls, v: str) -> str:
|
|
422
|
-
validate_semver(v)
|
|
423
|
-
return v
|
|
424
|
-
|
|
425
|
-
@pdt.field_validator("vehicle_name", mode="after")
|
|
426
|
-
@classmethod
|
|
427
|
-
def validate_vehicle_name(cls, v: str) -> str:
|
|
428
|
-
validate_vehicle_name(v)
|
|
429
|
-
return v
|
|
430
|
-
|
|
431
|
-
@pdt.field_validator("begin_dt", mode="after")
|
|
432
|
-
@classmethod
|
|
433
|
-
def validate_begin_dt(cls, v: datetime.datetime) -> datetime.datetime:
|
|
434
|
-
validate_dt_timezone(v, allow_naive=True)
|
|
435
|
-
return v
|
|
436
|
-
|
|
437
|
-
@pdt.field_validator("end_dt", mode="after")
|
|
438
|
-
@classmethod
|
|
439
|
-
def validate_end_dt(cls, v: datetime.datetime) -> datetime.datetime:
|
|
440
|
-
validate_dt_timezone(v, allow_naive=True)
|
|
441
|
-
return v
|
|
442
|
-
|
|
443
|
-
@pdt.model_validator(mode="after")
|
|
444
|
-
def validate_begin_dt_end_dt(self) -> Self:
|
|
445
|
-
if self.begin_dt > self.end_dt:
|
|
446
|
-
raise ValueError(f"begin_dt '{self.begin_dt}' is greater than end_dt '{self.end_dt}'")
|
|
447
|
-
return self
|
|
381
|
+
def make_tag_target_model_mixin() -> type[pdt.BaseModel]:
|
|
382
|
+
class ModelMixin(pdt.BaseModel):
|
|
383
|
+
identifier: str = Field(
|
|
384
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(256), nullable=False, unique=True),
|
|
385
|
+
description="Identifier of the tag target",
|
|
386
|
+
)
|
|
387
|
+
tagger_name: str = Field(
|
|
388
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(128), nullable=False),
|
|
389
|
+
description="Name of the tagger that generates the tag records for the target",
|
|
390
|
+
)
|
|
391
|
+
tagger_version: str = Field(
|
|
392
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(32), nullable=False),
|
|
393
|
+
description="Version of the tagger that generates the tag records for the target",
|
|
394
|
+
)
|
|
395
|
+
vehicle_name: str = Field(
|
|
396
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(128), nullable=False),
|
|
397
|
+
description="Vehicle name associated with the tag record",
|
|
398
|
+
)
|
|
399
|
+
begin_dt: datetime.datetime = Field(
|
|
400
|
+
sa_column=sa.Column(SQLiteDateTime, nullable=False),
|
|
401
|
+
description="Begin datetime of the target range associated with the tag record",
|
|
402
|
+
)
|
|
403
|
+
end_dt: datetime.datetime = Field(
|
|
404
|
+
sa_column=sa.Column(SQLiteDateTime, nullable=False),
|
|
405
|
+
description="End datetime of the target range associated with the tag record",
|
|
406
|
+
)
|
|
448
407
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
408
|
+
@pdt.field_validator("identifier", mode="after")
|
|
409
|
+
@classmethod
|
|
410
|
+
def validate_identifier(cls, v: str) -> str:
|
|
411
|
+
validate_slash_tag(v)
|
|
412
|
+
return v
|
|
452
413
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
414
|
+
@pdt.field_validator("tagger_name", mode="after")
|
|
415
|
+
@classmethod
|
|
416
|
+
def validate_tagger_name(cls, v: str) -> str:
|
|
417
|
+
validate_snake_case(v)
|
|
418
|
+
return v
|
|
456
419
|
|
|
420
|
+
@pdt.field_validator("tagger_version", mode="after")
|
|
421
|
+
@classmethod
|
|
422
|
+
def validate_tagger_version(cls, v: str) -> str:
|
|
423
|
+
validate_semver(v)
|
|
424
|
+
return v
|
|
425
|
+
|
|
426
|
+
@pdt.field_validator("vehicle_name", mode="after")
|
|
427
|
+
@classmethod
|
|
428
|
+
def validate_vehicle_name(cls, v: str) -> str:
|
|
429
|
+
validate_vehicle_name(v)
|
|
430
|
+
return v
|
|
431
|
+
|
|
432
|
+
@pdt.field_validator("begin_dt", mode="after")
|
|
433
|
+
@classmethod
|
|
434
|
+
def validate_begin_dt(cls, v: datetime.datetime) -> datetime.datetime:
|
|
435
|
+
validate_dt_timezone(v)
|
|
436
|
+
return v
|
|
437
|
+
|
|
438
|
+
@pdt.field_validator("end_dt", mode="after")
|
|
439
|
+
@classmethod
|
|
440
|
+
def validate_end_dt(cls, v: datetime.datetime) -> datetime.datetime:
|
|
441
|
+
validate_dt_timezone(v)
|
|
442
|
+
return v
|
|
443
|
+
|
|
444
|
+
@pdt.model_validator(mode="after")
|
|
445
|
+
def validate_begin_dt_end_dt(self) -> Self:
|
|
446
|
+
if self.begin_dt > self.end_dt:
|
|
447
|
+
raise ValueError(f"begin_dt '{self.begin_dt}' is greater than end_dt '{self.end_dt}'")
|
|
448
|
+
return self
|
|
449
|
+
|
|
450
|
+
@pdt.field_serializer("begin_dt", mode="plain")
|
|
451
|
+
def serialize_begin_dt(self, v: datetime.datetime) -> str:
|
|
452
|
+
return json_datetime_encoder(v)
|
|
453
|
+
|
|
454
|
+
@pdt.field_serializer("end_dt", mode="plain")
|
|
455
|
+
def serialize_end_dt(self, v: datetime.datetime) -> str:
|
|
456
|
+
return json_datetime_encoder(v)
|
|
457
|
+
|
|
458
|
+
return ModelMixin
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def make_tag_record_model_mixin() -> type[pdt.BaseModel]:
|
|
462
|
+
class ModelMixin(pdt.BaseModel):
|
|
463
|
+
target_sqn: int = Field(
|
|
464
|
+
sa_column=sa.Column(sa_sqlite.INTEGER, nullable=False),
|
|
465
|
+
description="Sequence number of the tag record's target",
|
|
466
|
+
)
|
|
467
|
+
begin_dt: datetime.datetime = Field(
|
|
468
|
+
sa_column=sa.Column(SQLiteDateTime, nullable=False),
|
|
469
|
+
description="Begin datetime of the tag record",
|
|
470
|
+
)
|
|
471
|
+
end_dt: datetime.datetime = Field(
|
|
472
|
+
sa_column=sa.Column(SQLiteDateTime, nullable=False),
|
|
473
|
+
description="End datetime of the tag record",
|
|
474
|
+
)
|
|
475
|
+
tagset_namespace: str | None = Field(
|
|
476
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(64), nullable=True),
|
|
477
|
+
default=None,
|
|
478
|
+
description="Namespace of the tagset that the tag belongs to",
|
|
479
|
+
)
|
|
480
|
+
tagset_version: str | None = Field(
|
|
481
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(32), nullable=True),
|
|
482
|
+
default=None,
|
|
483
|
+
description="Version of the tagset that the tag belongs to",
|
|
484
|
+
)
|
|
485
|
+
tag: str = Field(
|
|
486
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(256), nullable=False),
|
|
487
|
+
description="Tag name",
|
|
488
|
+
)
|
|
489
|
+
props: JsonType | None = Field(
|
|
490
|
+
sa_column=sa.Column(sa_sqlite.JSON, nullable=True),
|
|
491
|
+
default=None,
|
|
492
|
+
description="Additional properties of the tag record in JSON format",
|
|
493
|
+
)
|
|
494
|
+
flags: int = Field(
|
|
495
|
+
sa_column=sa.Column(sa_sqlite.INTEGER),
|
|
496
|
+
default=0,
|
|
497
|
+
description="Integer bitmask storing status or metadata flags for this tag record",
|
|
498
|
+
)
|
|
457
499
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
500
|
+
@pdt.field_validator("begin_dt", mode="after")
|
|
501
|
+
@classmethod
|
|
502
|
+
def validate_begin_dt(cls, v: datetime.datetime) -> datetime.datetime:
|
|
503
|
+
validate_dt_timezone(v)
|
|
504
|
+
return v
|
|
505
|
+
|
|
506
|
+
@pdt.field_validator("end_dt", mode="after")
|
|
507
|
+
@classmethod
|
|
508
|
+
def validate_end_dt(cls, v: datetime.datetime) -> datetime.datetime:
|
|
509
|
+
validate_dt_timezone(v)
|
|
510
|
+
return v
|
|
511
|
+
|
|
512
|
+
@pdt.field_validator("tagset_namespace", mode="after")
|
|
513
|
+
@classmethod
|
|
514
|
+
def validate_tagset_namespace(cls, v: str | None) -> str | None:
|
|
515
|
+
if v is not None:
|
|
516
|
+
validate_snake_case(v)
|
|
517
|
+
return v
|
|
518
|
+
|
|
519
|
+
@pdt.field_validator("tagset_version", mode="after")
|
|
520
|
+
@classmethod
|
|
521
|
+
def validate_tagset_version(cls, v: str | None) -> str | None:
|
|
522
|
+
if v is not None:
|
|
523
|
+
validate_semver(v)
|
|
524
|
+
return v
|
|
525
|
+
|
|
526
|
+
@pdt.model_validator(mode="after")
|
|
527
|
+
def validate_begin_dt_end_dt(self) -> Self:
|
|
528
|
+
if self.begin_dt > self.end_dt:
|
|
529
|
+
raise ValueError(f"begin_dt '{self.begin_dt}' is greater than end_dt '{self.end_dt}'")
|
|
530
|
+
return self
|
|
531
|
+
|
|
532
|
+
@pdt.field_validator("tag", mode="after")
|
|
533
|
+
@classmethod
|
|
534
|
+
def validate_tag(cls, v: str) -> str:
|
|
535
|
+
validate_colon_tag(v)
|
|
536
|
+
return v
|
|
537
|
+
|
|
538
|
+
@pdt.field_serializer("begin_dt", mode="plain")
|
|
539
|
+
def serialize_begin_dt(self, v: datetime.datetime) -> str:
|
|
540
|
+
return json_datetime_encoder(v)
|
|
541
|
+
|
|
542
|
+
@pdt.field_serializer("end_dt", mode="plain")
|
|
543
|
+
def serialize_end_dt(self, v: datetime.datetime) -> str:
|
|
544
|
+
return json_datetime_encoder(v)
|
|
545
|
+
|
|
546
|
+
return ModelMixin
|
|
495
547
|
|
|
496
|
-
@pdt.field_validator("begin_dt", mode="after")
|
|
497
|
-
@classmethod
|
|
498
|
-
def validate_begin_dt(cls, v: datetime.datetime) -> datetime.datetime:
|
|
499
|
-
validate_dt_timezone(v, allow_naive=True)
|
|
500
|
-
return v
|
|
501
|
-
|
|
502
|
-
@pdt.field_validator("end_dt", mode="after")
|
|
503
|
-
@classmethod
|
|
504
|
-
def validate_end_dt(cls, v: datetime.datetime) -> datetime.datetime:
|
|
505
|
-
validate_dt_timezone(v, allow_naive=True)
|
|
506
|
-
return v
|
|
507
|
-
|
|
508
|
-
@pdt.field_validator("tagset_namespace", mode="after")
|
|
509
|
-
@classmethod
|
|
510
|
-
def validate_tagset_namespace(cls, v: str | None) -> str | None:
|
|
511
|
-
if v is not None:
|
|
512
|
-
validate_snake_case(v)
|
|
513
|
-
return v
|
|
514
548
|
|
|
515
|
-
|
|
516
|
-
@classmethod
|
|
517
|
-
def validate_tagset_version(cls, v: str | None) -> str | None:
|
|
518
|
-
if v is not None:
|
|
519
|
-
validate_semver(v)
|
|
520
|
-
return v
|
|
549
|
+
BaseModel = make_base_model()
|
|
521
550
|
|
|
522
|
-
@pdt.model_validator(mode="after")
|
|
523
|
-
def validate_begin_dt_end_dt(self) -> Self:
|
|
524
|
-
if self.begin_dt > self.end_dt:
|
|
525
|
-
raise ValueError(f"begin_dt '{self.begin_dt}' is greater than end_dt '{self.end_dt}'")
|
|
526
|
-
return self
|
|
527
551
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
def validate_tag(cls, v: str) -> str:
|
|
531
|
-
validate_colon_tag(v)
|
|
532
|
-
return v
|
|
552
|
+
class TagTarget(BaseModel, make_tag_target_model_mixin()):
|
|
553
|
+
pass
|
|
533
554
|
|
|
534
|
-
@pdt.field_serializer("begin_dt", mode="plain")
|
|
535
|
-
def serialize_begin_dt(self, v: datetime.datetime) -> str:
|
|
536
|
-
return json_datetime_encoder(v)
|
|
537
555
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
return json_datetime_encoder(v)
|
|
556
|
+
class TagRecord(BaseModel, make_tag_record_model_mixin()):
|
|
557
|
+
pass
|
|
541
558
|
|
|
542
559
|
|
|
543
560
|
class TagTargetTable(TagTarget, make_sequence_model_mixin("sqlite"), table=True):
|
|
544
|
-
__tablename__ = "
|
|
561
|
+
__tablename__ = "tag_target"
|
|
545
562
|
|
|
546
563
|
|
|
547
564
|
class TagRecordTable(TagRecord, make_sequence_model_mixin("sqlite"), table=True):
|
|
@@ -549,7 +566,7 @@ class TagRecordTable(TagRecord, make_sequence_model_mixin("sqlite"), table=True)
|
|
|
549
566
|
|
|
550
567
|
|
|
551
568
|
if typing.TYPE_CHECKING:
|
|
552
|
-
class
|
|
569
|
+
class TagTarget(BaseModel):
|
|
553
570
|
identifier: sa_orm.Mapped[str] = ...
|
|
554
571
|
tagger_name: sa_orm.Mapped[str] = ...
|
|
555
572
|
tagger_version: sa_orm.Mapped[str] = ...
|
|
@@ -558,7 +575,11 @@ if typing.TYPE_CHECKING:
|
|
|
558
575
|
end_dt: sa_orm.Mapped[datetime.datetime] = ...
|
|
559
576
|
|
|
560
577
|
|
|
561
|
-
class
|
|
578
|
+
class TagTargetTable(TagTarget, SequenceModelMixinProtocol):
|
|
579
|
+
pass
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class TagRecord(BaseModel):
|
|
562
583
|
target_sqn: sa_orm.Mapped[int] = ...
|
|
563
584
|
begin_dt: sa_orm.Mapped[datetime.datetime] = ...
|
|
564
585
|
end_dt: sa_orm.Mapped[datetime.datetime] = ...
|
|
@@ -569,9 +590,13 @@ if typing.TYPE_CHECKING:
|
|
|
569
590
|
flags: sa_orm.Mapped[int] = ...
|
|
570
591
|
|
|
571
592
|
|
|
593
|
+
class TagRecordTable(TagRecord, SequenceModelMixinProtocol):
|
|
594
|
+
pass
|
|
595
|
+
|
|
596
|
+
|
|
572
597
|
@singleton
|
|
573
598
|
def tag_cache_file_path() -> pathlib.Path:
|
|
574
|
-
return pathlib.Path.home() / ".local" / "
|
|
599
|
+
return pathlib.Path.home() / ".local" / "plexus" / "tag_cache" / f"{randomizer().random_alphanumeric(7)}.db"
|
|
575
600
|
|
|
576
601
|
|
|
577
602
|
class TagCache(object):
|
|
@@ -595,9 +620,13 @@ class TagCache(object):
|
|
|
595
620
|
|
|
596
621
|
@contextlib.contextmanager
|
|
597
622
|
def make_session(self) -> Generator[sa_orm.Session, None, None]:
|
|
598
|
-
with self.thread_lock:
|
|
599
|
-
|
|
623
|
+
with self.thread_lock, self.conn_maker.make_session(expire_on_commit=False) as session:
|
|
624
|
+
try:
|
|
600
625
|
yield session
|
|
626
|
+
session.commit()
|
|
627
|
+
except Exception:
|
|
628
|
+
session.rollback()
|
|
629
|
+
raise
|
|
601
630
|
|
|
602
631
|
def get_target(self, target: int | str) -> TagTargetTable | None:
|
|
603
632
|
with self.make_session() as session:
|
|
@@ -1019,7 +1048,6 @@ class TargetedTagCache(object):
|
|
|
1019
1048
|
session.add(db_tag_record)
|
|
1020
1049
|
session.commit()
|
|
1021
1050
|
|
|
1022
|
-
session.refresh(db_tag_record)
|
|
1023
1051
|
return chainable(self, db_tag_record)
|
|
1024
1052
|
|
|
1025
1053
|
def add_tag(
|
|
@@ -1104,7 +1132,6 @@ class TargetedTagCache(object):
|
|
|
1104
1132
|
|
|
1105
1133
|
session.commit()
|
|
1106
1134
|
|
|
1107
|
-
session.refresh(db_tag_record)
|
|
1108
1135
|
return chainable(self, db_tag_record)
|
|
1109
1136
|
|
|
1110
1137
|
def remove_tags(
|
|
@@ -1158,7 +1185,7 @@ class TargetedTagCache(object):
|
|
|
1158
1185
|
|
|
1159
1186
|
|
|
1160
1187
|
@memorized
|
|
1161
|
-
def tag_cache(*, identifier: str | None = None, file_path: str | None = None) -> TagCache:
|
|
1188
|
+
def tag_cache(*, identifier: str | None = None, file_path: str | os.PathLike[str] | None = None) -> TagCache:
|
|
1162
1189
|
"""
|
|
1163
1190
|
Get a ``TagCache`` instance associated with the given identifier. If the identifier is ``None``, return a
|
|
1164
1191
|
``TagCache`` instance associated with a default file path. Otherwise, validate the identifier as a snake case
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/testutils.py
RENAMED
|
@@ -116,6 +116,7 @@ def patched_postgresql_session_maker(
|
|
|
116
116
|
base_model: type[SQLModel],
|
|
117
117
|
app: FastAPI,
|
|
118
118
|
session_maker: Callable[[], Generator[sa_orm.Session]],
|
|
119
|
+
**kwargs,
|
|
119
120
|
) -> Callable[[pytest.FixtureRequest], Generator[Callable[[], Iterator[sa_orm.Session]]]]:
|
|
120
121
|
"""
|
|
121
122
|
Create a pytest fixture that provides a patched session maker for PostgreSQL tests.
|
|
@@ -141,7 +142,7 @@ def patched_postgresql_session_maker(
|
|
|
141
142
|
)
|
|
142
143
|
|
|
143
144
|
def make_session_maker() -> Iterator[sa_orm.Session]:
|
|
144
|
-
with cm.make_session(
|
|
145
|
+
with cm.make_session(**kwargs) as db:
|
|
145
146
|
try:
|
|
146
147
|
# language=postgresql
|
|
147
148
|
db.execute(sa.sql.text("SET TIMEZONE TO 'UTC'"))
|
|
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
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/0-dummy
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/1-dummy
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/2-dummy
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
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/resources/unittest/pathutils/dummy.txt
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
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/OSMFile.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/OSMNode.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/OSMTags.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/OSMWay.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/carto/__init__.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/resources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/__init__.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/apiutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/bagutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/config.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/datautils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/dockerutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/gisutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/pathutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/s3utils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/sqlutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/src/plexus/common/utils/strutils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.65 → plexus_python_common-1.0.67}/test/plexus_tests/common/__init__.py
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
|