lsst-felis 29.2025.2700__tar.gz → 29.2025.2900__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.
Potentially problematic release.
This version of lsst-felis might be problematic. Click here for more details.
- {lsst_felis-29.2025.2700/python/lsst_felis.egg-info → lsst_felis-29.2025.2900}/PKG-INFO +1 -1
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/datamodel.py +27 -3
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/tap_schema.py +24 -14
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900/python/lsst_felis.egg-info}/PKG-INFO +1 -1
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/tests/test_datamodel.py +25 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/tests/test_tap_schema.py +64 -5
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/COPYRIGHT +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/LICENSE +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/README.rst +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/pyproject.toml +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/__init__.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/cli.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/columns.csv +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/key_columns.csv +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/keys.csv +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/schemas.csv +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/tables.csv +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/tap_schema_std.yaml +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/db/__init__.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/db/dialects.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/db/schema.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/db/sqltypes.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/db/utils.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/db/variants.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/diff.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/metadata.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/py.typed +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/tests/__init__.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/tests/postgresql.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/tests/run_cli.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/types.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/SOURCES.txt +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/dependency_links.txt +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/entry_points.txt +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/requires.txt +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/top_level.txt +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/zip-safe +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/setup.cfg +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/tests/test_cli.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/tests/test_db.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/tests/test_diff.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/tests/test_metadata.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/tests/test_postgres.py +0 -0
- {lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/tests/test_tap_schema_postgres.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-felis
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.2900
|
|
4
4
|
Summary: A vocabulary for describing catalogs and acting on those descriptions
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
6
|
License: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -618,7 +618,9 @@ class ForeignKeyConstraint(Constraint):
|
|
|
618
618
|
"""Table foreign key constraint model.
|
|
619
619
|
|
|
620
620
|
This constraint is used to define a foreign key relationship between two
|
|
621
|
-
tables in the schema.
|
|
621
|
+
tables in the schema. There must be at least one column in the
|
|
622
|
+
`columns` list, and at least one column in the `referenced_columns` list
|
|
623
|
+
or a validation error will be raised.
|
|
622
624
|
|
|
623
625
|
Notes
|
|
624
626
|
-----
|
|
@@ -629,10 +631,10 @@ class ForeignKeyConstraint(Constraint):
|
|
|
629
631
|
type: Literal["ForeignKey"] = Field("ForeignKey", alias="@type")
|
|
630
632
|
"""Type of the constraint."""
|
|
631
633
|
|
|
632
|
-
columns: list[str]
|
|
634
|
+
columns: list[str] = Field(min_length=1)
|
|
633
635
|
"""The columns comprising the foreign key."""
|
|
634
636
|
|
|
635
|
-
referenced_columns: list[str] = Field(alias="referencedColumns")
|
|
637
|
+
referenced_columns: list[str] = Field(alias="referencedColumns", min_length=1)
|
|
636
638
|
"""The columns referenced by the foreign key."""
|
|
637
639
|
|
|
638
640
|
on_delete: Literal["CASCADE", "SET NULL", "SET DEFAULT", "RESTRICT", "NO ACTION"] | None = None
|
|
@@ -657,6 +659,28 @@ class ForeignKeyConstraint(Constraint):
|
|
|
657
659
|
"""
|
|
658
660
|
return value
|
|
659
661
|
|
|
662
|
+
@model_validator(mode="after")
|
|
663
|
+
def check_column_lengths(self) -> ForeignKeyConstraint:
|
|
664
|
+
"""Check that the `columns` and `referenced_columns` lists have the
|
|
665
|
+
same length.
|
|
666
|
+
|
|
667
|
+
Returns
|
|
668
|
+
-------
|
|
669
|
+
`ForeignKeyConstraint`
|
|
670
|
+
The foreign key constraint being validated.
|
|
671
|
+
|
|
672
|
+
Raises
|
|
673
|
+
------
|
|
674
|
+
ValueError
|
|
675
|
+
Raised if the `columns` and `referenced_columns` lists do not have
|
|
676
|
+
the same length.
|
|
677
|
+
"""
|
|
678
|
+
if len(self.columns) != len(self.referenced_columns):
|
|
679
|
+
raise ValueError(
|
|
680
|
+
"Columns and referencedColumns must have the same length for a ForeignKey constraint"
|
|
681
|
+
)
|
|
682
|
+
return self
|
|
683
|
+
|
|
660
684
|
|
|
661
685
|
_ConstraintType = Annotated[
|
|
662
686
|
CheckConstraint | ForeignKeyConstraint | UniqueConstraint, Field(discriminator="type")
|
|
@@ -470,7 +470,7 @@ class DataLoader:
|
|
|
470
470
|
logger.info("Dry run - not loading data into database")
|
|
471
471
|
|
|
472
472
|
def _insert_schemas(self) -> None:
|
|
473
|
-
"""Insert the schema data into the schemas table."""
|
|
473
|
+
"""Insert the schema data into the ``schemas`` table."""
|
|
474
474
|
schema_record = {
|
|
475
475
|
"schema_name": self.schema.name,
|
|
476
476
|
"utype": self.schema.votable_utype,
|
|
@@ -495,7 +495,7 @@ class DataLoader:
|
|
|
495
495
|
return f"{self.schema.name}.{table.name}"
|
|
496
496
|
|
|
497
497
|
def _insert_tables(self) -> None:
|
|
498
|
-
"""Insert the table data into the tables table."""
|
|
498
|
+
"""Insert the table data into the ``tables`` table."""
|
|
499
499
|
for table in self.schema.tables:
|
|
500
500
|
table_record = {
|
|
501
501
|
"schema_name": self.schema.name,
|
|
@@ -508,7 +508,7 @@ class DataLoader:
|
|
|
508
508
|
self._insert("tables", table_record)
|
|
509
509
|
|
|
510
510
|
def _insert_columns(self) -> None:
|
|
511
|
-
"""Insert the column data into the columns table."""
|
|
511
|
+
"""Insert the column data into the ``columns`` table."""
|
|
512
512
|
for table in self.schema.tables:
|
|
513
513
|
for column in table.columns:
|
|
514
514
|
felis_type = FelisType.felis_type(column.datatype.value)
|
|
@@ -563,11 +563,15 @@ class DataLoader:
|
|
|
563
563
|
return key_id
|
|
564
564
|
|
|
565
565
|
def _insert_keys(self) -> None:
|
|
566
|
-
"""Insert the foreign keys into the keys and key_columns
|
|
566
|
+
"""Insert the foreign keys into the ``keys`` and ``key_columns``
|
|
567
|
+
tables.
|
|
568
|
+
"""
|
|
567
569
|
for table in self.schema.tables:
|
|
568
570
|
for constraint in table.constraints:
|
|
569
571
|
if isinstance(constraint, datamodel.ForeignKeyConstraint):
|
|
572
|
+
###########################################################
|
|
570
573
|
# Handle keys table
|
|
574
|
+
###########################################################
|
|
571
575
|
referenced_column = self.schema.find_object_by_id(
|
|
572
576
|
constraint.referenced_columns[0], datamodel.Column
|
|
573
577
|
)
|
|
@@ -582,17 +586,23 @@ class DataLoader:
|
|
|
582
586
|
}
|
|
583
587
|
self._insert("keys", key_record)
|
|
584
588
|
|
|
589
|
+
###########################################################
|
|
585
590
|
# Handle key_columns table
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
591
|
+
###########################################################
|
|
592
|
+
# Loop over the corresponding columns and referenced
|
|
593
|
+
# columns and insert a record for each pair. This is
|
|
594
|
+
# necessary for proper handling of composite keys.
|
|
595
|
+
for from_column_id, target_column_id in zip(
|
|
596
|
+
constraint.columns, constraint.referenced_columns
|
|
597
|
+
):
|
|
598
|
+
from_column = self.schema.find_object_by_id(from_column_id, datamodel.Column)
|
|
599
|
+
target_column = self.schema.find_object_by_id(target_column_id, datamodel.Column)
|
|
600
|
+
key_columns_record = {
|
|
601
|
+
"key_id": key_id,
|
|
602
|
+
"from_column": from_column.name,
|
|
603
|
+
"target_column": target_column.name,
|
|
604
|
+
}
|
|
605
|
+
self._insert("key_columns", key_columns_record)
|
|
596
606
|
|
|
597
607
|
def _generate_all_inserts(self) -> None:
|
|
598
608
|
"""Generate the inserts for all the data."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-felis
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.2900
|
|
4
4
|
Summary: A vocabulary for describing catalogs and acting on those descriptions
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
6
|
License: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -422,6 +422,31 @@ class ConstraintTestCase(unittest.TestCase):
|
|
|
422
422
|
constraint.referenced_columns, ["test_column"], "referenced_columns should be ['test_column']"
|
|
423
423
|
)
|
|
424
424
|
|
|
425
|
+
# Creating a foreign key constraint with no columns should raise an
|
|
426
|
+
# exception.
|
|
427
|
+
with self.assertRaises(ValidationError):
|
|
428
|
+
ForeignKeyConstraint(
|
|
429
|
+
name="fk_test", id="#fk_test", columns=[], referenced_columns=["test_column"]
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Creating a foreign key constraint with no referenced columns should
|
|
433
|
+
# raise an exception.
|
|
434
|
+
with self.assertRaises(ValidationError):
|
|
435
|
+
ForeignKeyConstraint(
|
|
436
|
+
name="fk_test", id="#fk_test", columns=["test_column"], referenced_columns=[]
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Creating a foreign key constraint where the number of foreign key
|
|
440
|
+
# columns does not match the number of referenced columns should raise
|
|
441
|
+
# an exception.
|
|
442
|
+
with self.assertRaises(ValidationError):
|
|
443
|
+
ForeignKeyConstraint(
|
|
444
|
+
name="fk_test",
|
|
445
|
+
id="#fk_test",
|
|
446
|
+
columns=["test_column", "test_column2"],
|
|
447
|
+
referenced_columns=["test_column"],
|
|
448
|
+
)
|
|
449
|
+
|
|
425
450
|
def test_check_constraint(self) -> None:
|
|
426
451
|
"""Test validation of check constraints."""
|
|
427
452
|
# Setting name and id should throw an exception from missing
|
|
@@ -33,6 +33,7 @@ from felis.tap_schema import DataLoader, TableManager
|
|
|
33
33
|
TEST_DIR = os.path.dirname(__file__)
|
|
34
34
|
TEST_SALES = os.path.join(TEST_DIR, "data", "sales.yaml")
|
|
35
35
|
TEST_TAP_SCHEMA = os.path.join(TEST_DIR, "data", "test_tap_schema.yaml")
|
|
36
|
+
TEST_COMPOSITE_KEYS = os.path.join(TEST_DIR, "data", "test_composite_keys.yaml")
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
class TableManagerTestCase(unittest.TestCase):
|
|
@@ -158,12 +159,15 @@ class TapSchemaSqliteSetup:
|
|
|
158
159
|
|
|
159
160
|
Parameters
|
|
160
161
|
----------
|
|
161
|
-
|
|
162
|
+
test_file_path:
|
|
163
|
+
Path to the TAP_SCHEMA test file.
|
|
164
|
+
|
|
165
|
+
context
|
|
162
166
|
Context for the schema. Default is an empty dictionary.
|
|
163
167
|
"""
|
|
164
168
|
|
|
165
|
-
def __init__(self, context: dict = {}) -> None:
|
|
166
|
-
with open(
|
|
169
|
+
def __init__(self, test_file_path: str, context: dict = {}) -> None:
|
|
170
|
+
with open(test_file_path) as test_file:
|
|
167
171
|
self._schema = Schema.from_stream(test_file, context=context)
|
|
168
172
|
|
|
169
173
|
self._engine = create_engine("sqlite:///:memory:")
|
|
@@ -196,7 +200,7 @@ class TapSchemaDataTest(unittest.TestCase):
|
|
|
196
200
|
|
|
197
201
|
def setUp(self) -> None:
|
|
198
202
|
"""Set up the test case."""
|
|
199
|
-
self.tap_schema_setup = TapSchemaSqliteSetup(context={"id_generation": True})
|
|
203
|
+
self.tap_schema_setup = TapSchemaSqliteSetup(TEST_TAP_SCHEMA, context={"id_generation": True})
|
|
200
204
|
|
|
201
205
|
def test_schemas(self) -> None:
|
|
202
206
|
schemas_table = self.tap_schema_setup.mgr["schemas"]
|
|
@@ -347,7 +351,7 @@ class ForceUnboundArraySizeTest(unittest.TestCase):
|
|
|
347
351
|
def setUp(self) -> None:
|
|
348
352
|
"""Set up the test case."""
|
|
349
353
|
self.tap_schema_setup = TapSchemaSqliteSetup(
|
|
350
|
-
context={"id_generation": True, "force_unbounded_arraysize": True}
|
|
354
|
+
TEST_TAP_SCHEMA, context={"id_generation": True, "force_unbounded_arraysize": True}
|
|
351
355
|
)
|
|
352
356
|
|
|
353
357
|
def test_force_unbounded_arraysize(self) -> None:
|
|
@@ -365,5 +369,60 @@ class ForceUnboundArraySizeTest(unittest.TestCase):
|
|
|
365
369
|
self.assertEqual(row["arraysize"], "*")
|
|
366
370
|
|
|
367
371
|
|
|
372
|
+
class CompositeKeysTestCase(unittest.TestCase):
|
|
373
|
+
"""Test the handling of composite foreign keys."""
|
|
374
|
+
|
|
375
|
+
def setUp(self) -> None:
|
|
376
|
+
"""Set up the test case."""
|
|
377
|
+
self.tap_schema_setup = TapSchemaSqliteSetup(TEST_COMPOSITE_KEYS, context={"id_generation": True})
|
|
378
|
+
|
|
379
|
+
# Fetch the keys and key_columns data from the TAP_SCHEMA tables.
|
|
380
|
+
keys_table = self.tap_schema_setup.mgr["keys"]
|
|
381
|
+
key_columns_table = self.tap_schema_setup.mgr["key_columns"]
|
|
382
|
+
with self.tap_schema_setup.engine.connect() as connection:
|
|
383
|
+
key_columns_result = connection.execute(select(key_columns_table))
|
|
384
|
+
self.key_columns_data = [row._asdict() for row in key_columns_result]
|
|
385
|
+
|
|
386
|
+
keys_result = connection.execute(select(keys_table))
|
|
387
|
+
self.keys_data = [row._asdict() for row in keys_result]
|
|
388
|
+
|
|
389
|
+
def test_keys(self) -> None:
|
|
390
|
+
"""Test that composite keys are handled correctly by inspecting the
|
|
391
|
+
data in the generated TAP_SCHEMA ``keys`` table.
|
|
392
|
+
"""
|
|
393
|
+
print(f"\nComposite keys data: {self.keys_data}")
|
|
394
|
+
|
|
395
|
+
self.assertEqual(len(self.keys_data), 1)
|
|
396
|
+
|
|
397
|
+
self.assertEqual(
|
|
398
|
+
self.keys_data[0],
|
|
399
|
+
{
|
|
400
|
+
"key_id": "fk_composite",
|
|
401
|
+
"from_table": "test_composite_keys.table1",
|
|
402
|
+
"target_table": "test_composite_keys.table2",
|
|
403
|
+
"utype": "ForeignKey",
|
|
404
|
+
"description": "Composite foreign key from table1 to table2",
|
|
405
|
+
},
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
def test_key_columns(self) -> None:
|
|
409
|
+
"""Test that composite keys are handled correctly by inspecting the
|
|
410
|
+
data in the generated TAP_SCHEMA ``key_columns`` table.
|
|
411
|
+
"""
|
|
412
|
+
print(f"\nComposite key columns data: {self.key_columns_data}")
|
|
413
|
+
|
|
414
|
+
self.assertEqual(len(self.key_columns_data), 2)
|
|
415
|
+
|
|
416
|
+
key_columns_row1 = self.key_columns_data[0]
|
|
417
|
+
self.assertEqual(
|
|
418
|
+
key_columns_row1, {"key_id": "fk_composite", "from_column": "id1", "target_column": "id1"}
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
key_columns_row2 = self.key_columns_data[1]
|
|
422
|
+
self.assertEqual(
|
|
423
|
+
key_columns_row2, {"key_id": "fk_composite", "from_column": "id2", "target_column": "id2"}
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
368
427
|
if __name__ == "__main__":
|
|
369
428
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/columns.csv
RENAMED
|
File without changes
|
{lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/key_columns.csv
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/schemas.csv
RENAMED
|
File without changes
|
{lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/felis/config/tap_schema/tables.csv
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
|
{lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_felis-29.2025.2700 → lsst_felis-29.2025.2900}/python/lsst_felis.egg-info/top_level.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
|