lsst-felis 27.2024.2400__py3-none-any.whl → 27.2024.2600__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 +162 -45
- felis/datamodel.py +377 -95
- felis/db/dialects.py +65 -12
- felis/db/sqltypes.py +255 -16
- felis/db/utils.py +108 -52
- felis/db/variants.py +66 -8
- felis/metadata.py +70 -54
- felis/tap.py +180 -18
- felis/types.py +56 -8
- felis/version.py +1 -1
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2600.dist-info}/COPYRIGHT +1 -1
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2600.dist-info}/METADATA +4 -2
- lsst_felis-27.2024.2600.dist-info/RECORD +21 -0
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2600.dist-info}/WHEEL +1 -1
- felis/validation.py +0 -103
- lsst_felis-27.2024.2400.dist-info/RECORD +0 -22
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2600.dist-info}/LICENSE +0 -0
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2600.dist-info}/entry_points.txt +0 -0
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2600.dist-info}/top_level.txt +0 -0
- {lsst_felis-27.2024.2400.dist-info → lsst_felis-27.2024.2600.dist-info}/zip-safe +0 -0
felis/tap.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Translate a Felis schema into a TAP_SCHEMA representation."""
|
|
2
|
+
|
|
1
3
|
# This file is part of felis.
|
|
2
4
|
#
|
|
3
5
|
# Developed for the LSST Data Management System.
|
|
@@ -21,8 +23,6 @@
|
|
|
21
23
|
|
|
22
24
|
from __future__ import annotations
|
|
23
25
|
|
|
24
|
-
__all__ = ["Tap11Base", "TapLoadingVisitor", "init_tables"]
|
|
25
|
-
|
|
26
26
|
import logging
|
|
27
27
|
from collections.abc import Iterable, MutableMapping
|
|
28
28
|
from typing import Any
|
|
@@ -39,9 +39,12 @@ from felis import datamodel
|
|
|
39
39
|
from .datamodel import Constraint, Index, Schema, Table
|
|
40
40
|
from .types import FelisType
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
__all__ = ["TapLoadingVisitor", "init_tables"]
|
|
43
|
+
|
|
43
44
|
logger = logging.getLogger(__name__)
|
|
44
45
|
|
|
46
|
+
Tap11Base: Any = declarative_base() # Any to avoid mypy mess with SA 2
|
|
47
|
+
|
|
45
48
|
IDENTIFIER_LENGTH = 128
|
|
46
49
|
SMALL_FIELD_LENGTH = 32
|
|
47
50
|
SIMPLE_FIELD_LENGTH = 128
|
|
@@ -60,7 +63,30 @@ def init_tables(
|
|
|
60
63
|
tap_keys_table: str | None = None,
|
|
61
64
|
tap_key_columns_table: str | None = None,
|
|
62
65
|
) -> MutableMapping[str, Any]:
|
|
63
|
-
"""Generate definitions for TAP tables.
|
|
66
|
+
"""Generate definitions for TAP tables.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
tap_schema_name
|
|
71
|
+
Name of the TAP schema.
|
|
72
|
+
tap_tables_postfix
|
|
73
|
+
Postfix for table names.
|
|
74
|
+
tap_schemas_table
|
|
75
|
+
Name of the schemas table.
|
|
76
|
+
tap_tables_table
|
|
77
|
+
Name of the tables table.
|
|
78
|
+
tap_columns_table
|
|
79
|
+
Name of the columns table.
|
|
80
|
+
tap_keys_table
|
|
81
|
+
Name of the keys table.
|
|
82
|
+
tap_key_columns_table
|
|
83
|
+
Name of the key columns table.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
`dict` [ `str`, `Any`]
|
|
88
|
+
A dictionary of table definitions.
|
|
89
|
+
"""
|
|
64
90
|
postfix = tap_tables_postfix or ""
|
|
65
91
|
|
|
66
92
|
# Dirty hack to enable this method to be called more than once, replaces
|
|
@@ -133,18 +159,21 @@ def init_tables(
|
|
|
133
159
|
|
|
134
160
|
|
|
135
161
|
class TapLoadingVisitor:
|
|
136
|
-
"""
|
|
162
|
+
"""Generate TAP_SCHEMA data and insert it into a database using the
|
|
163
|
+
SQLAlchemy ORM.
|
|
137
164
|
|
|
138
165
|
Parameters
|
|
139
166
|
----------
|
|
140
|
-
engine
|
|
167
|
+
engine
|
|
141
168
|
SQLAlchemy engine instance.
|
|
142
|
-
catalog_name
|
|
169
|
+
catalog_name
|
|
143
170
|
Name of the database catalog.
|
|
144
|
-
schema_name
|
|
145
|
-
Name of the
|
|
146
|
-
tap_tables
|
|
147
|
-
|
|
171
|
+
schema_name
|
|
172
|
+
Name of the schema.
|
|
173
|
+
tap_tables
|
|
174
|
+
Mapping of TAP_SCHEMA table name to its SQLAlchemy table object.
|
|
175
|
+
tap_schema_index
|
|
176
|
+
The index of the schema for this TAP environment.
|
|
148
177
|
"""
|
|
149
178
|
|
|
150
179
|
def __init__(
|
|
@@ -154,7 +183,8 @@ class TapLoadingVisitor:
|
|
|
154
183
|
schema_name: str | None = None,
|
|
155
184
|
tap_tables: MutableMapping[str, Any] | None = None,
|
|
156
185
|
tap_schema_index: int | None = None,
|
|
157
|
-
):
|
|
186
|
+
) -> None:
|
|
187
|
+
"""Create a TAP loading visitor."""
|
|
158
188
|
self.graph_index: MutableMapping[str, Any] = {}
|
|
159
189
|
self.catalog_name = catalog_name
|
|
160
190
|
self.schema_name = schema_name
|
|
@@ -172,12 +202,39 @@ class TapLoadingVisitor:
|
|
|
172
202
|
tap_tables: MutableMapping[str, Any] | None = None,
|
|
173
203
|
tap_schema_index: int | None = None,
|
|
174
204
|
) -> TapLoadingVisitor:
|
|
205
|
+
"""Create a TAP visitor from a mock connection.
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
mock_connection
|
|
210
|
+
Mock connection object.
|
|
211
|
+
catalog_name
|
|
212
|
+
Name of the database catalog.
|
|
213
|
+
schema_name
|
|
214
|
+
Name of the database schema.
|
|
215
|
+
tap_tables
|
|
216
|
+
Optional mapping of table name to its SQLAlchemy table object.
|
|
217
|
+
tap_schema_index
|
|
218
|
+
The index of the schema for this TAP environment.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
`TapLoadingVisitor`
|
|
223
|
+
The TAP loading visitor.
|
|
224
|
+
"""
|
|
175
225
|
visitor = cls(engine=None, catalog_name=catalog_name, schema_name=schema_name, tap_tables=tap_tables)
|
|
176
226
|
visitor._mock_connection = mock_connection
|
|
177
227
|
visitor.tap_schema_index = tap_schema_index
|
|
178
228
|
return visitor
|
|
179
229
|
|
|
180
230
|
def visit_schema(self, schema_obj: Schema) -> None:
|
|
231
|
+
"""Visit a schema object and insert it into the TAP_SCHEMA database.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
schema_obj
|
|
236
|
+
The schema object to visit.
|
|
237
|
+
"""
|
|
181
238
|
schema = self.tables["schemas"]()
|
|
182
239
|
# Override with default
|
|
183
240
|
self.schema_name = self.schema_name or schema_obj.name
|
|
@@ -226,6 +283,18 @@ class TapLoadingVisitor:
|
|
|
226
283
|
conn.execute(_insert(self.tables["key_columns"], key_column))
|
|
227
284
|
|
|
228
285
|
def visit_constraints(self, schema_obj: Schema) -> tuple:
|
|
286
|
+
"""Visit all constraints in a schema.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
schema_obj
|
|
291
|
+
The schema object to visit.
|
|
292
|
+
|
|
293
|
+
Returns
|
|
294
|
+
-------
|
|
295
|
+
`tuple`
|
|
296
|
+
A tuple of all TAP_SCHEMA keys and key columns that were created.
|
|
297
|
+
"""
|
|
229
298
|
all_keys = []
|
|
230
299
|
all_key_columns = []
|
|
231
300
|
for table_obj in schema_obj.tables:
|
|
@@ -238,6 +307,20 @@ class TapLoadingVisitor:
|
|
|
238
307
|
return all_keys, all_key_columns
|
|
239
308
|
|
|
240
309
|
def visit_table(self, table_obj: Table, schema_obj: Schema) -> tuple:
|
|
310
|
+
"""Visit a table object and build its TAP_SCHEMA representation.
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
table_obj
|
|
315
|
+
The table object to visit.
|
|
316
|
+
schema_obj
|
|
317
|
+
The schema object which the table belongs to.
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
`tuple`
|
|
322
|
+
A tuple of the SQLAlchemy ORM objects for the tables and columns.
|
|
323
|
+
"""
|
|
241
324
|
table_id = table_obj.id
|
|
242
325
|
table = self.tables["tables"]()
|
|
243
326
|
table.schema_name = self._schema_name()
|
|
@@ -257,6 +340,20 @@ class TapLoadingVisitor:
|
|
|
257
340
|
return table, columns
|
|
258
341
|
|
|
259
342
|
def check_column(self, column_obj: datamodel.Column) -> None:
|
|
343
|
+
"""Check consistency of VOTable attributes for a column.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
column_obj
|
|
348
|
+
The column object to check.
|
|
349
|
+
|
|
350
|
+
Notes
|
|
351
|
+
-----
|
|
352
|
+
This method checks that a column with a sized datatype has either a
|
|
353
|
+
``votable:arraysize`` or a ``length`` attribute and issues a warning
|
|
354
|
+
message if not. It also checks if a column with a timestamp datatype
|
|
355
|
+
has a ``arraysize`` attribute and issues a warning if not.
|
|
356
|
+
"""
|
|
260
357
|
_id = column_obj.id
|
|
261
358
|
datatype_name = column_obj.datatype
|
|
262
359
|
felis_type = FelisType.felis_type(datatype_name.value)
|
|
@@ -283,6 +380,20 @@ class TapLoadingVisitor:
|
|
|
283
380
|
)
|
|
284
381
|
|
|
285
382
|
def visit_column(self, column_obj: datamodel.Column, table_obj: Table) -> Tap11Base:
|
|
383
|
+
"""Visit a column object and build its TAP_SCHEMA representation.
|
|
384
|
+
|
|
385
|
+
Parameters
|
|
386
|
+
----------
|
|
387
|
+
column_obj
|
|
388
|
+
The column object to visit.
|
|
389
|
+
table_obj
|
|
390
|
+
The table object which the column belongs to.
|
|
391
|
+
|
|
392
|
+
Returns
|
|
393
|
+
-------
|
|
394
|
+
``Tap11Base``
|
|
395
|
+
The SQLAlchemy ORM object for the column.
|
|
396
|
+
"""
|
|
286
397
|
self.check_column(column_obj)
|
|
287
398
|
column_id = column_obj.id
|
|
288
399
|
table_name = self._table_name(table_obj.name)
|
|
@@ -321,6 +432,15 @@ class TapLoadingVisitor:
|
|
|
321
432
|
return column
|
|
322
433
|
|
|
323
434
|
def visit_primary_key(self, primary_key_obj: str | Iterable[str] | None, table_obj: Table) -> None:
|
|
435
|
+
"""Visit a primary key object and update the TAP_SCHEMA representation.
|
|
436
|
+
|
|
437
|
+
Parameters
|
|
438
|
+
----------
|
|
439
|
+
primary_key_obj
|
|
440
|
+
The primary key object to visit.
|
|
441
|
+
table_obj
|
|
442
|
+
The table object which the primary key belongs to.
|
|
443
|
+
"""
|
|
324
444
|
if primary_key_obj:
|
|
325
445
|
if isinstance(primary_key_obj, str):
|
|
326
446
|
primary_key_obj = [primary_key_obj]
|
|
@@ -328,9 +448,21 @@ class TapLoadingVisitor:
|
|
|
328
448
|
# if just one column and it's indexed, update the object
|
|
329
449
|
if len(columns) == 1:
|
|
330
450
|
columns[0].indexed = 1
|
|
331
|
-
return None
|
|
332
451
|
|
|
333
452
|
def visit_constraint(self, constraint_obj: Constraint) -> tuple:
|
|
453
|
+
"""Visit a constraint object and build its TAP_SCHEMA representation.
|
|
454
|
+
|
|
455
|
+
Parameters
|
|
456
|
+
----------
|
|
457
|
+
constraint_obj
|
|
458
|
+
The constraint object to visit.
|
|
459
|
+
|
|
460
|
+
Returns
|
|
461
|
+
-------
|
|
462
|
+
`tuple`
|
|
463
|
+
A tuple of the SQLAlchemy ORM objects for the TAP_SCHEMA ``key``
|
|
464
|
+
and ``key_columns`` data.
|
|
465
|
+
"""
|
|
334
466
|
constraint_type = constraint_obj.type
|
|
335
467
|
key = None
|
|
336
468
|
key_columns = []
|
|
@@ -375,13 +507,36 @@ class TapLoadingVisitor:
|
|
|
375
507
|
return key, key_columns
|
|
376
508
|
|
|
377
509
|
def visit_index(self, index_obj: Index, table_obj: Table) -> None:
|
|
510
|
+
"""Visit an index object and update the TAP_SCHEMA representation.
|
|
511
|
+
|
|
512
|
+
Parameters
|
|
513
|
+
----------
|
|
514
|
+
index_obj
|
|
515
|
+
The index object to visit.
|
|
516
|
+
table_obj
|
|
517
|
+
The table object which the index belongs to.
|
|
518
|
+
"""
|
|
378
519
|
columns = [self.graph_index[col_id] for col_id in getattr(index_obj, "columns", [])]
|
|
379
520
|
# if just one column and it's indexed, update the object
|
|
380
521
|
if len(columns) == 1:
|
|
381
522
|
columns[0].indexed = 1
|
|
382
523
|
return None
|
|
383
524
|
|
|
384
|
-
def _schema_name(
|
|
525
|
+
def _schema_name(
|
|
526
|
+
self, schema_name: str | None = None
|
|
527
|
+
) -> str | None: # DM-44870: Usage of this method needs to be better understood and possibly removed
|
|
528
|
+
"""Return the schema name.
|
|
529
|
+
|
|
530
|
+
Parameters
|
|
531
|
+
----------
|
|
532
|
+
schema_name
|
|
533
|
+
Name of the schema.
|
|
534
|
+
|
|
535
|
+
Returns
|
|
536
|
+
-------
|
|
537
|
+
schema_name
|
|
538
|
+
The schema name.
|
|
539
|
+
"""
|
|
385
540
|
# If _schema_name is None, SQLAlchemy will catch it
|
|
386
541
|
_schema_name = schema_name or self.schema_name
|
|
387
542
|
if self.catalog_name and _schema_name:
|
|
@@ -389,6 +544,13 @@ class TapLoadingVisitor:
|
|
|
389
544
|
return _schema_name
|
|
390
545
|
|
|
391
546
|
def _table_name(self, table_name: str) -> str:
|
|
547
|
+
"""Return the table name.
|
|
548
|
+
|
|
549
|
+
Parameters
|
|
550
|
+
----------
|
|
551
|
+
table_name
|
|
552
|
+
Name of the table.
|
|
553
|
+
"""
|
|
392
554
|
schema_name = self._schema_name()
|
|
393
555
|
if schema_name:
|
|
394
556
|
return ".".join([schema_name, table_name])
|
|
@@ -400,15 +562,15 @@ def _insert(table: Tap11Base, value: Any) -> Insert:
|
|
|
400
562
|
|
|
401
563
|
Parameters
|
|
402
564
|
----------
|
|
403
|
-
table
|
|
565
|
+
table
|
|
404
566
|
The table we are inserting into.
|
|
405
|
-
value
|
|
567
|
+
value
|
|
406
568
|
An object representing the object we are inserting to the table.
|
|
407
569
|
|
|
408
570
|
Returns
|
|
409
571
|
-------
|
|
410
|
-
|
|
411
|
-
|
|
572
|
+
`Insert`
|
|
573
|
+
SQLAlchemy insert statement.
|
|
412
574
|
"""
|
|
413
575
|
values_dict = {}
|
|
414
576
|
for i in table.__table__.columns:
|
felis/types.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Define the supported Felis datatypes."""
|
|
2
|
+
|
|
1
3
|
# This file is part of felis.
|
|
2
4
|
#
|
|
3
5
|
# Developed for the LSST Data Management System.
|
|
@@ -23,10 +25,29 @@ from __future__ import annotations
|
|
|
23
25
|
|
|
24
26
|
from typing import Any
|
|
25
27
|
|
|
28
|
+
__all__ = [
|
|
29
|
+
"FelisType",
|
|
30
|
+
"Boolean",
|
|
31
|
+
"Byte",
|
|
32
|
+
"Short",
|
|
33
|
+
"Int",
|
|
34
|
+
"Long",
|
|
35
|
+
"Float",
|
|
36
|
+
"Double",
|
|
37
|
+
"Char",
|
|
38
|
+
"String",
|
|
39
|
+
"Unicode",
|
|
40
|
+
"Text",
|
|
41
|
+
"Binary",
|
|
42
|
+
"Timestamp",
|
|
43
|
+
]
|
|
44
|
+
|
|
26
45
|
|
|
27
46
|
class FelisType:
|
|
28
|
-
"""Base class for
|
|
47
|
+
"""Base class for a representation of Felis column types.
|
|
29
48
|
|
|
49
|
+
Notes
|
|
50
|
+
-----
|
|
30
51
|
This class plays a role of a metaclass without being an actual metaclass.
|
|
31
52
|
It provides a method to retrieve a class (type) given Felis type name.
|
|
32
53
|
There should be no instances of this class (or sub-classes), the utility
|
|
@@ -34,12 +55,22 @@ class FelisType:
|
|
|
34
55
|
"""
|
|
35
56
|
|
|
36
57
|
felis_name: str
|
|
58
|
+
"""Name of the type as defined in the Felis schema."""
|
|
59
|
+
|
|
37
60
|
votable_name: str
|
|
61
|
+
"""Name of the type as defined in VOTable."""
|
|
62
|
+
|
|
38
63
|
is_numeric: bool
|
|
64
|
+
"""Flag indicating if the type is numeric."""
|
|
65
|
+
|
|
39
66
|
is_sized: bool
|
|
67
|
+
"""Flag indicating if the type is sized, meaning it requires a length."""
|
|
68
|
+
|
|
40
69
|
is_timestamp: bool
|
|
70
|
+
"""Flag indicating if the type is a timestamp."""
|
|
41
71
|
|
|
42
72
|
_types: dict[str, type[FelisType]] = {}
|
|
73
|
+
"""Dictionary of all known Felis types."""
|
|
43
74
|
|
|
44
75
|
@classmethod
|
|
45
76
|
def __init_subclass__(
|
|
@@ -52,6 +83,23 @@ class FelisType:
|
|
|
52
83
|
is_timestamp: bool = False,
|
|
53
84
|
**kwargs: Any,
|
|
54
85
|
):
|
|
86
|
+
"""Register a new Felis type.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
felis_name
|
|
91
|
+
Name of the type.
|
|
92
|
+
votable_name
|
|
93
|
+
Name of the type as defined in VOTable.
|
|
94
|
+
is_numeric
|
|
95
|
+
Flag indicating if the type is numeric.
|
|
96
|
+
is_sized
|
|
97
|
+
Flag indicating if the type is sized.
|
|
98
|
+
is_timestamp
|
|
99
|
+
Flag indicating if the type is a timestamp.
|
|
100
|
+
kwargs
|
|
101
|
+
Additional keyword arguments.
|
|
102
|
+
"""
|
|
55
103
|
super().__init_subclass__(**kwargs)
|
|
56
104
|
cls.felis_name = felis_name
|
|
57
105
|
cls.votable_name = votable_name
|
|
@@ -62,17 +110,17 @@ class FelisType:
|
|
|
62
110
|
|
|
63
111
|
@classmethod
|
|
64
112
|
def felis_type(cls, felis_name: str) -> type[FelisType]:
|
|
65
|
-
"""Return specific Felis type for a given
|
|
113
|
+
"""Return specific Felis type for a given name.
|
|
66
114
|
|
|
67
115
|
Parameters
|
|
68
116
|
----------
|
|
69
|
-
felis_name
|
|
70
|
-
|
|
117
|
+
felis_name
|
|
118
|
+
Name of the felis type as defined in felis schema.
|
|
71
119
|
|
|
72
120
|
Returns
|
|
73
121
|
-------
|
|
74
|
-
|
|
75
|
-
|
|
122
|
+
`type` [ `FelisType` ]
|
|
123
|
+
A specific Felis type class.
|
|
76
124
|
|
|
77
125
|
Raises
|
|
78
126
|
------
|
|
@@ -106,11 +154,11 @@ class Long(FelisType, felis_name="long", votable_name="long", is_numeric=True):
|
|
|
106
154
|
|
|
107
155
|
|
|
108
156
|
class Float(FelisType, felis_name="float", votable_name="float", is_numeric=True):
|
|
109
|
-
"""Felis definition of single precision floating type."""
|
|
157
|
+
"""Felis definition of single precision floating point type."""
|
|
110
158
|
|
|
111
159
|
|
|
112
160
|
class Double(FelisType, felis_name="double", votable_name="double", is_numeric=True):
|
|
113
|
-
"""Felis definition of double precision floating type."""
|
|
161
|
+
"""Felis definition of double precision floating point type."""
|
|
114
162
|
|
|
115
163
|
|
|
116
164
|
class Char(FelisType, felis_name="char", votable_name="char", is_sized=True):
|
felis/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "27.2024.
|
|
2
|
+
__version__ = "27.2024.2600"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Copyright
|
|
1
|
+
Copyright 2018-2024 The Board of Trustees of the Leland Stanford Junior University, through SLAC National Accelerator Laboratory
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lsst-felis
|
|
3
|
-
Version: 27.2024.
|
|
3
|
+
Version: 27.2024.2600
|
|
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+)
|
|
@@ -25,7 +25,9 @@ Requires-Dist: pyyaml >=6
|
|
|
25
25
|
Requires-Dist: pydantic <3,>=2
|
|
26
26
|
Requires-Dist: lsst-utils
|
|
27
27
|
Provides-Extra: dev
|
|
28
|
-
Requires-Dist: documenteer[guide] ; extra == 'dev'
|
|
28
|
+
Requires-Dist: documenteer[guide] <2 ; extra == 'dev'
|
|
29
|
+
Requires-Dist: autodoc-pydantic ; extra == 'dev'
|
|
30
|
+
Requires-Dist: sphinx-click ; extra == 'dev'
|
|
29
31
|
Provides-Extra: test
|
|
30
32
|
Requires-Dist: pytest >=3.2 ; extra == 'test'
|
|
31
33
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
felis/__init__.py,sha256=THmRg3ylB4E73XhFjJX7YlnV_CM3lr_gZO_HqQFzIQ4,937
|
|
2
|
+
felis/cli.py,sha256=i5FlA9OBecqHP4SISngaveQ7YbWH8pxRGHeOzMOOMyg,14086
|
|
3
|
+
felis/datamodel.py,sha256=k0wAZpIhwOWVvAlHLLpzCOYYxY5iD8jpjlOu_lpzv-4,25902
|
|
4
|
+
felis/metadata.py,sha256=W1Y4s7izAEiH3MLJkKQpvxHsGE5Dbu4lhhNCzWlfl0o,12290
|
|
5
|
+
felis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
felis/tap.py,sha256=YBWl1CicdN4jW_KhPJQ7-TuhOfFFTIYIH4tTJ8P_o1c,21035
|
|
7
|
+
felis/types.py,sha256=m80GSGfNHQ3-NzRuTzKOyRXLJboPxdk9kzpp1SO8XdY,5510
|
|
8
|
+
felis/version.py,sha256=aahBy2OK7EjIKUv__rAtLwgu_sqkTymTjJ8WETh6OXg,55
|
|
9
|
+
felis/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
felis/db/dialects.py,sha256=IsjdD_2n7tucUdR7TMyRd2gWtnD-2kXPAJUUQdbUe_Q,3526
|
|
11
|
+
felis/db/sqltypes.py,sha256=sDzkqemYwILp8hk0yfg3H0ZK7bGnIJR9x6ntd2coASc,11248
|
|
12
|
+
felis/db/utils.py,sha256=TgNJTBv2Jt4Qq_RtOO_jWj_02tb-fEA2QeOOHYS1GyU,10177
|
|
13
|
+
felis/db/variants.py,sha256=o5m101upQQbWZD_l8qlB8gt-ZQ9-VqsWZrmxQO1eEQA,5246
|
|
14
|
+
lsst_felis-27.2024.2600.dist-info/COPYRIGHT,sha256=vJAFLFTSF1mhy9eIuA3P6R-3yxTWKQgpig88P-1IzRw,129
|
|
15
|
+
lsst_felis-27.2024.2600.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
16
|
+
lsst_felis-27.2024.2600.dist-info/METADATA,sha256=9To3B_2DA9ZePgBiyIx999RjfjcXgwNyUd1IH81yOdo,1288
|
|
17
|
+
lsst_felis-27.2024.2600.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
|
|
18
|
+
lsst_felis-27.2024.2600.dist-info/entry_points.txt,sha256=Gk2XFujA_Gp52VBk45g5kim8TDoMDJFPctsMqiq72EM,40
|
|
19
|
+
lsst_felis-27.2024.2600.dist-info/top_level.txt,sha256=F4SvPip3iZRVyISi50CHhwTIAokAhSxjWiVcn4IVWRI,6
|
|
20
|
+
lsst_felis-27.2024.2600.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
21
|
+
lsst_felis-27.2024.2600.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}'")
|
|
@@ -1,22 +0,0 @@
|
|
|
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
|
|
4
|
-
felis/metadata.py,sha256=lSgKy9CksmyJp9zUkzkwOAzjk2Hzivb9VVbYfTPNmvY,12140
|
|
5
|
-
felis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
felis/tap.py,sha256=fVYvvIFk_vccXqbcFYdgK2yOfp4P5E4guvsSGktsNxs,16795
|
|
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
|
|
10
|
-
felis/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
felis/db/dialects.py,sha256=mSYgS8gqUJQGgZw3IA-zBfKlrJ2r4nEUR94wKZNRKZg,2267
|
|
12
|
-
felis/db/sqltypes.py,sha256=yFlautQ1hv21MHF4AIfBp7_2m1-exKBfc76xYsMHBgk,5735
|
|
13
|
-
felis/db/utils.py,sha256=oYPGOt5K_82GnScJOg8WarOyvval-Cu1nX7CduFxKss,9122
|
|
14
|
-
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|