lsst-felis 27.2024.2400__py3-none-any.whl → 27.2024.2500__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of lsst-felis might be problematic. Click here for more details.
- felis/cli.py +17 -16
- felis/datamodel.py +45 -8
- felis/version.py +1 -1
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2500.dist-info}/METADATA +1 -1
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2500.dist-info}/RECORD +11 -12
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2500.dist-info}/WHEEL +1 -1
- felis/validation.py +0 -103
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2500.dist-info}/COPYRIGHT +0 -0
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2500.dist-info}/LICENSE +0 -0
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2500.dist-info}/entry_points.txt +0 -0
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2500.dist-info}/top_level.txt +0 -0
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2500.dist-info}/zip-safe +0 -0
felis/cli.py
CHANGED
|
@@ -37,7 +37,6 @@ from .datamodel import Schema
|
|
|
37
37
|
from .db.utils import DatabaseContext
|
|
38
38
|
from .metadata import MetaDataBuilder
|
|
39
39
|
from .tap import Tap11Base, TapLoadingVisitor, init_tables
|
|
40
|
-
from .validation import get_schema
|
|
41
40
|
|
|
42
41
|
logger = logging.getLogger("felis")
|
|
43
42
|
|
|
@@ -241,42 +240,44 @@ def load_tap(
|
|
|
241
240
|
|
|
242
241
|
|
|
243
242
|
@cli.command("validate")
|
|
243
|
+
@click.option("--check-description", is_flag=True, help="Require description for all objects", default=False)
|
|
244
244
|
@click.option(
|
|
245
|
-
"-
|
|
246
|
-
"--schema-name",
|
|
247
|
-
help="Schema name for validation",
|
|
248
|
-
type=click.Choice(["RSP", "default"]),
|
|
249
|
-
default="default",
|
|
245
|
+
"--check-redundant-datatypes", is_flag=True, help="Check for redundant datatypes", default=False
|
|
250
246
|
)
|
|
251
247
|
@click.option(
|
|
252
|
-
"
|
|
248
|
+
"--check-tap-table-indexes",
|
|
249
|
+
is_flag=True,
|
|
250
|
+
help="Check that every table has a unique TAP table index",
|
|
251
|
+
default=False,
|
|
253
252
|
)
|
|
254
253
|
@click.option(
|
|
255
|
-
"
|
|
254
|
+
"--check-tap-principal",
|
|
255
|
+
is_flag=True,
|
|
256
|
+
help="Check that at least one column per table is flagged as TAP principal",
|
|
257
|
+
default=False,
|
|
256
258
|
)
|
|
257
259
|
@click.argument("files", nargs=-1, type=click.File())
|
|
258
260
|
def validate(
|
|
259
|
-
|
|
260
|
-
require_description: bool,
|
|
261
|
+
check_description: bool,
|
|
261
262
|
check_redundant_datatypes: bool,
|
|
263
|
+
check_tap_table_indexes: bool,
|
|
264
|
+
check_tap_principal: bool,
|
|
262
265
|
files: Iterable[io.TextIOBase],
|
|
263
266
|
) -> None:
|
|
264
267
|
"""Validate one or more felis YAML files."""
|
|
265
|
-
schema_class = get_schema(schema_name)
|
|
266
|
-
if schema_name != "default":
|
|
267
|
-
logger.info(f"Using schema '{schema_class.__name__}'")
|
|
268
|
-
|
|
269
268
|
rc = 0
|
|
270
269
|
for file in files:
|
|
271
270
|
file_name = getattr(file, "name", None)
|
|
272
271
|
logger.info(f"Validating {file_name}")
|
|
273
272
|
try:
|
|
274
273
|
data = yaml.load(file, Loader=yaml.SafeLoader)
|
|
275
|
-
|
|
274
|
+
Schema.model_validate(
|
|
276
275
|
data,
|
|
277
276
|
context={
|
|
277
|
+
"check_description": check_description,
|
|
278
278
|
"check_redundant_datatypes": check_redundant_datatypes,
|
|
279
|
-
"
|
|
279
|
+
"check_tap_table_indexes": check_tap_table_indexes,
|
|
280
|
+
"check_tap_principal": check_tap_principal,
|
|
280
281
|
},
|
|
281
282
|
)
|
|
282
283
|
except ValidationError as e:
|
felis/datamodel.py
CHANGED
|
@@ -96,7 +96,7 @@ class BaseObject(BaseModel):
|
|
|
96
96
|
def check_description(self, info: ValidationInfo) -> BaseObject:
|
|
97
97
|
"""Check that the description is present if required."""
|
|
98
98
|
context = info.context
|
|
99
|
-
if not context or not context.get("
|
|
99
|
+
if not context or not context.get("check_description", False):
|
|
100
100
|
return self
|
|
101
101
|
if self.description is None or self.description == "":
|
|
102
102
|
raise ValueError("Description is required and must be non-empty")
|
|
@@ -208,12 +208,11 @@ class Column(BaseObject):
|
|
|
208
208
|
raise ValueError(f"Invalid IVOA UCD: {e}")
|
|
209
209
|
return ivoa_ucd
|
|
210
210
|
|
|
211
|
-
@model_validator(mode="
|
|
212
|
-
|
|
213
|
-
def check_units(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
211
|
+
@model_validator(mode="after")
|
|
212
|
+
def check_units(self) -> Column:
|
|
214
213
|
"""Check that units are valid."""
|
|
215
|
-
fits_unit =
|
|
216
|
-
ivoa_unit =
|
|
214
|
+
fits_unit = self.fits_tunit
|
|
215
|
+
ivoa_unit = self.ivoa_unit
|
|
217
216
|
|
|
218
217
|
if fits_unit and ivoa_unit:
|
|
219
218
|
raise ValueError("Column cannot have both FITS and IVOA units")
|
|
@@ -225,7 +224,7 @@ class Column(BaseObject):
|
|
|
225
224
|
except ValueError as e:
|
|
226
225
|
raise ValueError(f"Invalid unit: {e}")
|
|
227
226
|
|
|
228
|
-
return
|
|
227
|
+
return self
|
|
229
228
|
|
|
230
229
|
@model_validator(mode="before")
|
|
231
230
|
@classmethod
|
|
@@ -250,7 +249,7 @@ class Column(BaseObject):
|
|
|
250
249
|
return values
|
|
251
250
|
|
|
252
251
|
@model_validator(mode="after")
|
|
253
|
-
def
|
|
252
|
+
def check_redundant_datatypes(self, info: ValidationInfo) -> Column:
|
|
254
253
|
"""Check for redundant datatypes on columns."""
|
|
255
254
|
context = info.context
|
|
256
255
|
if not context or not context.get("check_redundant_datatypes", False):
|
|
@@ -419,6 +418,29 @@ class Table(BaseObject):
|
|
|
419
418
|
raise ValueError("Column names must be unique")
|
|
420
419
|
return columns
|
|
421
420
|
|
|
421
|
+
@model_validator(mode="after")
|
|
422
|
+
def check_tap_table_index(self, info: ValidationInfo) -> Table:
|
|
423
|
+
"""Check that the table has a TAP table index."""
|
|
424
|
+
context = info.context
|
|
425
|
+
if not context or not context.get("check_tap_table_indexes", False):
|
|
426
|
+
return self
|
|
427
|
+
if self.tap_table_index is None:
|
|
428
|
+
raise ValueError("Table is missing a TAP table index")
|
|
429
|
+
return self
|
|
430
|
+
|
|
431
|
+
@model_validator(mode="after")
|
|
432
|
+
def check_tap_principal(self, info: ValidationInfo) -> Table:
|
|
433
|
+
"""Check that at least one column is flagged as 'principal' for TAP
|
|
434
|
+
purposes.
|
|
435
|
+
"""
|
|
436
|
+
context = info.context
|
|
437
|
+
if not context or not context.get("check_tap_principal", False):
|
|
438
|
+
return self
|
|
439
|
+
for col in self.columns:
|
|
440
|
+
if col.tap_principal == 1:
|
|
441
|
+
return self
|
|
442
|
+
raise ValueError(f"Table '{self.name}' is missing at least one column designated as 'tap:principal'")
|
|
443
|
+
|
|
422
444
|
|
|
423
445
|
class SchemaVersion(BaseModel):
|
|
424
446
|
"""The version of the schema."""
|
|
@@ -508,6 +530,21 @@ class Schema(BaseObject):
|
|
|
508
530
|
raise ValueError("Table names must be unique")
|
|
509
531
|
return tables
|
|
510
532
|
|
|
533
|
+
@model_validator(mode="after")
|
|
534
|
+
def check_tap_table_indexes(self, info: ValidationInfo) -> Schema:
|
|
535
|
+
"""Check that the TAP table indexes are unique."""
|
|
536
|
+
context = info.context
|
|
537
|
+
if not context or not context.get("check_tap_table_indexes", False):
|
|
538
|
+
return self
|
|
539
|
+
table_indicies = set()
|
|
540
|
+
for table in self.tables:
|
|
541
|
+
table_index = table.tap_table_index
|
|
542
|
+
if table_index is not None:
|
|
543
|
+
if table_index in table_indicies:
|
|
544
|
+
raise ValueError(f"Duplicate 'tap:table_index' value {table_index} found in schema")
|
|
545
|
+
table_indicies.add(table_index)
|
|
546
|
+
return self
|
|
547
|
+
|
|
511
548
|
def _create_id_map(self: Schema) -> Schema:
|
|
512
549
|
"""Create a map of IDs to objects.
|
|
513
550
|
|
felis/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "27.2024.
|
|
2
|
+
__version__ = "27.2024.2500"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lsst-felis
|
|
3
|
-
Version: 27.2024.
|
|
3
|
+
Version: 27.2024.2500
|
|
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+)
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
felis/__init__.py,sha256=THmRg3ylB4E73XhFjJX7YlnV_CM3lr_gZO_HqQFzIQ4,937
|
|
2
|
-
felis/cli.py,sha256=
|
|
3
|
-
felis/datamodel.py,sha256=
|
|
2
|
+
felis/cli.py,sha256=7acvMUku17wDgS1cxGUhKMo5agkofHFN5NUdIdZyJ3o,10132
|
|
3
|
+
felis/datamodel.py,sha256=psSj2IBI7Va_2RvSnKrlZTDIwA__g5YeHh0Q3tweNWo,21208
|
|
4
4
|
felis/metadata.py,sha256=lSgKy9CksmyJp9zUkzkwOAzjk2Hzivb9VVbYfTPNmvY,12140
|
|
5
5
|
felis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
felis/tap.py,sha256=fVYvvIFk_vccXqbcFYdgK2yOfp4P5E4guvsSGktsNxs,16795
|
|
7
7
|
felis/types.py,sha256=z_ECfSxpqiFSGppjxKwCO4fPP7TLBaIN3Qo1AGF16Go,4418
|
|
8
|
-
felis/
|
|
9
|
-
felis/version.py,sha256=SKE5LZn31z5ONJXoQypgUH-1aa2Ar0owtNYSiqyaKog,55
|
|
8
|
+
felis/version.py,sha256=DpIrT4nE40bHhTZ12_Zx-z9xNkBs4IQms4j7HZqcdCA,55
|
|
10
9
|
felis/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
10
|
felis/db/dialects.py,sha256=mSYgS8gqUJQGgZw3IA-zBfKlrJ2r4nEUR94wKZNRKZg,2267
|
|
12
11
|
felis/db/sqltypes.py,sha256=yFlautQ1hv21MHF4AIfBp7_2m1-exKBfc76xYsMHBgk,5735
|
|
13
12
|
felis/db/utils.py,sha256=oYPGOt5K_82GnScJOg8WarOyvval-Cu1nX7CduFxKss,9122
|
|
14
13
|
felis/db/variants.py,sha256=Ti2oZf7nFTe8aFyG-GeFSW4bIb5ClNikm9xOJtRcxLY,3862
|
|
15
|
-
lsst_felis-27.2024.
|
|
16
|
-
lsst_felis-27.2024.
|
|
17
|
-
lsst_felis-27.2024.
|
|
18
|
-
lsst_felis-27.2024.
|
|
19
|
-
lsst_felis-27.2024.
|
|
20
|
-
lsst_felis-27.2024.
|
|
21
|
-
lsst_felis-27.2024.
|
|
22
|
-
lsst_felis-27.2024.
|
|
14
|
+
lsst_felis-27.2024.2500.dist-info/COPYRIGHT,sha256=bUmNy19uUxqITMpjeHFe69q3IzQpjxvvBw6oV7kR7ho,129
|
|
15
|
+
lsst_felis-27.2024.2500.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
16
|
+
lsst_felis-27.2024.2500.dist-info/METADATA,sha256=co1oyBz9lU3XhIYJmN86G1Qt2iMW9LW66ShIkXrA2E4,1191
|
|
17
|
+
lsst_felis-27.2024.2500.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
|
|
18
|
+
lsst_felis-27.2024.2500.dist-info/entry_points.txt,sha256=Gk2XFujA_Gp52VBk45g5kim8TDoMDJFPctsMqiq72EM,40
|
|
19
|
+
lsst_felis-27.2024.2500.dist-info/top_level.txt,sha256=F4SvPip3iZRVyISi50CHhwTIAokAhSxjWiVcn4IVWRI,6
|
|
20
|
+
lsst_felis-27.2024.2500.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
21
|
+
lsst_felis-27.2024.2500.dist-info/RECORD,,
|
felis/validation.py
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# This file is part of felis.
|
|
2
|
-
#
|
|
3
|
-
# Developed for the LSST Data Management System.
|
|
4
|
-
# This product includes software developed by the LSST Project
|
|
5
|
-
# (https://www.lsst.org).
|
|
6
|
-
# See the COPYRIGHT file at the top-level directory of this distribution
|
|
7
|
-
# for details of code ownership.
|
|
8
|
-
#
|
|
9
|
-
# This program is free software: you can redistribute it and/or modify
|
|
10
|
-
# it under the terms of the GNU General Public License as published by
|
|
11
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
12
|
-
# (at your option) any later version.
|
|
13
|
-
#
|
|
14
|
-
# This program is distributed in the hope that it will be useful,
|
|
15
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
-
# GNU General Public License for more details.
|
|
18
|
-
#
|
|
19
|
-
# You should have received a copy of the GNU General Public License
|
|
20
|
-
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
-
|
|
22
|
-
from __future__ import annotations
|
|
23
|
-
|
|
24
|
-
import logging
|
|
25
|
-
from collections.abc import Sequence
|
|
26
|
-
from typing import Any
|
|
27
|
-
|
|
28
|
-
from pydantic import Field, model_validator
|
|
29
|
-
|
|
30
|
-
from .datamodel import Column, DescriptionStr, Schema, Table
|
|
31
|
-
|
|
32
|
-
logger = logging.getLogger(__name__)
|
|
33
|
-
|
|
34
|
-
__all__ = ["RspColumn", "RspSchema", "RspTable", "get_schema"]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class RspColumn(Column):
|
|
38
|
-
"""Column for RSP data validation."""
|
|
39
|
-
|
|
40
|
-
description: DescriptionStr
|
|
41
|
-
"""Redefine description to make it required."""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class RspTable(Table):
|
|
45
|
-
"""Table for RSP data validation.
|
|
46
|
-
|
|
47
|
-
The list of columns is overridden to use RspColumn instead of Column.
|
|
48
|
-
|
|
49
|
-
Tables for the RSP must have a TAP table index and a valid description.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
description: DescriptionStr
|
|
53
|
-
"""Redefine description to make it required."""
|
|
54
|
-
|
|
55
|
-
tap_table_index: int = Field(..., alias="tap:table_index")
|
|
56
|
-
"""Redefine the TAP_SCHEMA table index so that it is required."""
|
|
57
|
-
|
|
58
|
-
columns: Sequence[RspColumn]
|
|
59
|
-
"""Redefine columns to include RSP validation."""
|
|
60
|
-
|
|
61
|
-
@model_validator(mode="after") # type: ignore[arg-type]
|
|
62
|
-
@classmethod
|
|
63
|
-
def check_tap_principal(cls: Any, tbl: RspTable) -> RspTable:
|
|
64
|
-
"""Check that at least one column is flagged as 'principal' for
|
|
65
|
-
TAP purposes.
|
|
66
|
-
"""
|
|
67
|
-
for col in tbl.columns:
|
|
68
|
-
if col.tap_principal == 1:
|
|
69
|
-
return tbl
|
|
70
|
-
raise ValueError(f"Table '{tbl.name}' is missing at least one column designated as 'tap:principal'")
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class RspSchema(Schema):
|
|
74
|
-
"""Schema for RSP data validation.
|
|
75
|
-
|
|
76
|
-
TAP table indexes must be unique across all tables.
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
tables: Sequence[RspTable]
|
|
80
|
-
"""Redefine tables to include RSP validation."""
|
|
81
|
-
|
|
82
|
-
@model_validator(mode="after") # type: ignore[arg-type]
|
|
83
|
-
@classmethod
|
|
84
|
-
def check_tap_table_indexes(cls: Any, sch: RspSchema) -> RspSchema:
|
|
85
|
-
"""Check that the TAP table indexes are unique."""
|
|
86
|
-
table_indicies = set()
|
|
87
|
-
for table in sch.tables:
|
|
88
|
-
table_index = table.tap_table_index
|
|
89
|
-
if table_index is not None:
|
|
90
|
-
if table_index in table_indicies:
|
|
91
|
-
raise ValueError(f"Duplicate 'tap:table_index' value {table_index} found in schema")
|
|
92
|
-
table_indicies.add(table_index)
|
|
93
|
-
return sch
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def get_schema(schema_name: str) -> type[Schema]:
|
|
97
|
-
"""Get the schema class for the given name."""
|
|
98
|
-
if schema_name == "default":
|
|
99
|
-
return Schema
|
|
100
|
-
elif schema_name == "RSP":
|
|
101
|
-
return RspSchema
|
|
102
|
-
else:
|
|
103
|
-
raise ValueError(f"Unknown schema name '{schema_name}'")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|