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 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
- "-s",
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
- "-d", "--require-description", is_flag=True, help="Require description for all objects", default=False
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
- "-t", "--check-redundant-datatypes", is_flag=True, help="Check for redundant datatypes", default=False
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
- schema_name: str,
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
- schema_class.model_validate(
274
+ Schema.model_validate(
276
275
  data,
277
276
  context={
277
+ "check_description": check_description,
278
278
  "check_redundant_datatypes": check_redundant_datatypes,
279
- "require_description": require_description,
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("require_description", False):
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="before")
212
- @classmethod
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 = values.get("fits:tunit")
216
- ivoa_unit = values.get("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 values
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 check_datatypes(self, info: ValidationInfo) -> Column:
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.2400"
2
+ __version__ = "27.2024.2500"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lsst-felis
3
- Version: 27.2024.2400
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=wBALFf9bMYpL6-A58I3JtozaiMSSoi7Gu7YyGuUk8Uo,9997
3
- felis/datamodel.py,sha256=ECXmd78fufrYDBK4S0n3-wSQytWmz-vGLkNRjtrHPCE,19627
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/validation.py,sha256=Zq0gyCvPCwRlhfQ-w_p6ccDTkjcyhxSA1-Gr5plXiZI,3465
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.2400.dist-info/COPYRIGHT,sha256=bUmNy19uUxqITMpjeHFe69q3IzQpjxvvBw6oV7kR7ho,129
16
- lsst_felis-27.2024.2400.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
17
- lsst_felis-27.2024.2400.dist-info/METADATA,sha256=agi49bvO-Q3nN0a2chNJcvsKtz1mi9J_Az02n4wh_Og,1191
18
- lsst_felis-27.2024.2400.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
19
- lsst_felis-27.2024.2400.dist-info/entry_points.txt,sha256=Gk2XFujA_Gp52VBk45g5kim8TDoMDJFPctsMqiq72EM,40
20
- lsst_felis-27.2024.2400.dist-info/top_level.txt,sha256=F4SvPip3iZRVyISi50CHhwTIAokAhSxjWiVcn4IVWRI,6
21
- lsst_felis-27.2024.2400.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
22
- lsst_felis-27.2024.2400.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (70.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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}'")