lsst-felis 27.0.0rc3__py3-none-any.whl → 27.2024.1800__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/tap.py CHANGED
@@ -24,7 +24,7 @@ from __future__ import annotations
24
24
  __all__ = ["Tap11Base", "TapLoadingVisitor", "init_tables"]
25
25
 
26
26
  import logging
27
- from collections.abc import Iterable, Mapping, MutableMapping
27
+ from collections.abc import Iterable, MutableMapping
28
28
  from typing import Any
29
29
 
30
30
  from sqlalchemy import Column, Integer, String
@@ -34,14 +34,13 @@ from sqlalchemy.orm import Session, declarative_base, sessionmaker
34
34
  from sqlalchemy.schema import MetaData
35
35
  from sqlalchemy.sql.expression import Insert, insert
36
36
 
37
- from .check import FelisValidator
38
- from .types import FelisType
39
- from .visitor import Visitor
37
+ from felis import datamodel
40
38
 
41
- _Mapping = Mapping[str, Any]
39
+ from .datamodel import Constraint, Index, Schema, Table
40
+ from .types import FelisType
42
41
 
43
42
  Tap11Base: Any = declarative_base() # Any to avoid mypy mess with SA 2
44
- logger = logging.getLogger("felis")
43
+ logger = logging.getLogger(__name__)
45
44
 
46
45
  IDENTIFIER_LENGTH = 128
47
46
  SMALL_FIELD_LENGTH = 32
@@ -133,7 +132,7 @@ def init_tables(
133
132
  )
134
133
 
135
134
 
136
- class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]):
135
+ class TapLoadingVisitor:
137
136
  """Felis schema visitor for generating TAP schema.
138
137
 
139
138
  Parameters
@@ -154,6 +153,7 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
154
153
  catalog_name: str | None = None,
155
154
  schema_name: str | None = None,
156
155
  tap_tables: MutableMapping[str, Any] | None = None,
156
+ tap_schema_index: int | None = None,
157
157
  ):
158
158
  self.graph_index: MutableMapping[str, Any] = {}
159
159
  self.catalog_name = catalog_name
@@ -161,7 +161,7 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
161
161
  self.engine = engine
162
162
  self._mock_connection: MockConnection | None = None
163
163
  self.tables = tap_tables or init_tables()
164
- self.checker = FelisValidator()
164
+ self.tap_schema_index = tap_schema_index
165
165
 
166
166
  @classmethod
167
167
  def from_mock_connection(
@@ -170,30 +170,30 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
170
170
  catalog_name: str | None = None,
171
171
  schema_name: str | None = None,
172
172
  tap_tables: MutableMapping[str, Any] | None = None,
173
+ tap_schema_index: int | None = None,
173
174
  ) -> TapLoadingVisitor:
174
175
  visitor = cls(engine=None, catalog_name=catalog_name, schema_name=schema_name, tap_tables=tap_tables)
175
176
  visitor._mock_connection = mock_connection
177
+ visitor.tap_schema_index = tap_schema_index
176
178
  return visitor
177
179
 
178
- def visit_schema(self, schema_obj: _Mapping) -> None:
179
- self.checker.check_schema(schema_obj)
180
- if (version_obj := schema_obj.get("version")) is not None:
181
- self.visit_schema_version(version_obj, schema_obj)
180
+ def visit_schema(self, schema_obj: Schema) -> None:
182
181
  schema = self.tables["schemas"]()
183
182
  # Override with default
184
- self.schema_name = self.schema_name or schema_obj["name"]
183
+ self.schema_name = self.schema_name or schema_obj.name
185
184
 
186
185
  schema.schema_name = self._schema_name()
187
- schema.description = schema_obj.get("description")
188
- schema.utype = schema_obj.get("votable:utype")
189
- schema.schema_index = int(schema_obj.get("tap:schema_index", 0))
186
+ schema.description = schema_obj.description
187
+ schema.utype = schema_obj.votable_utype
188
+ schema.schema_index = self.tap_schema_index
189
+ logger.debug(f"Set TAP_SCHEMA index: {self.tap_schema_index}")
190
190
 
191
191
  if self.engine is not None:
192
192
  session: Session = sessionmaker(self.engine)()
193
193
 
194
194
  session.add(schema)
195
195
 
196
- for table_obj in schema_obj["tables"]:
196
+ for table_obj in schema_obj.tables:
197
197
  table, columns = self.visit_table(table_obj, schema_obj)
198
198
  session.add(table)
199
199
  session.add_all(columns)
@@ -202,6 +202,8 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
202
202
  session.add_all(keys)
203
203
  session.add_all(key_columns)
204
204
 
205
+ logger.debug("Committing TAP schema: %s", schema_obj.name)
206
+ logger.debug("TAP tables: %s", len(self.tables))
205
207
  session.commit()
206
208
  else:
207
209
  logger.info("Dry run, not inserting into database")
@@ -211,7 +213,7 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
211
213
  conn = self._mock_connection
212
214
  conn.execute(_insert(self.tables["schemas"], schema))
213
215
 
214
- for table_obj in schema_obj["tables"]:
216
+ for table_obj in schema_obj.tables:
215
217
  table, columns = self.visit_table(table_obj, schema_obj)
216
218
  conn.execute(_insert(self.tables["tables"], table))
217
219
  for column in columns:
@@ -223,56 +225,45 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
223
225
  for key_column in key_columns:
224
226
  conn.execute(_insert(self.tables["key_columns"], key_column))
225
227
 
226
- def visit_constraints(self, schema_obj: _Mapping) -> tuple:
228
+ def visit_constraints(self, schema_obj: Schema) -> tuple:
227
229
  all_keys = []
228
230
  all_key_columns = []
229
- for table_obj in schema_obj["tables"]:
230
- for c in table_obj.get("constraints", []):
231
- key, key_columns = self.visit_constraint(c, table_obj)
231
+ for table_obj in schema_obj.tables:
232
+ for c in table_obj.constraints:
233
+ key, key_columns = self.visit_constraint(c)
232
234
  if not key:
233
235
  continue
234
236
  all_keys.append(key)
235
237
  all_key_columns += key_columns
236
238
  return all_keys, all_key_columns
237
239
 
238
- def visit_schema_version(
239
- self, version_obj: str | Mapping[str, Any], schema_obj: Mapping[str, Any]
240
- ) -> None:
241
- # Docstring is inherited.
242
-
243
- # For now we ignore schema versioning completely, still do some checks.
244
- self.checker.check_schema_version(version_obj, schema_obj)
245
-
246
- def visit_table(self, table_obj: _Mapping, schema_obj: _Mapping) -> tuple:
247
- self.checker.check_table(table_obj, schema_obj)
248
- table_id = table_obj["@id"]
240
+ def visit_table(self, table_obj: Table, schema_obj: Schema) -> tuple:
241
+ table_id = table_obj.id
249
242
  table = self.tables["tables"]()
250
243
  table.schema_name = self._schema_name()
251
- table.table_name = self._table_name(table_obj["name"])
244
+ table.table_name = self._table_name(table_obj.name)
252
245
  table.table_type = "table"
253
- table.utype = table_obj.get("votable:utype")
254
- table.description = table_obj.get("description")
255
- table.table_index = int(table_obj.get("tap:table_index", 0))
246
+ table.utype = table_obj.votable_utype
247
+ table.description = table_obj.description
248
+ table.table_index = 0 if table_obj.tap_table_index is None else table_obj.tap_table_index
256
249
 
257
- columns = [self.visit_column(c, table_obj) for c in table_obj["columns"]]
258
- self.visit_primary_key(table_obj.get("primaryKey", []), table_obj)
250
+ columns = [self.visit_column(c, table_obj) for c in table_obj.columns]
251
+ self.visit_primary_key(table_obj.primary_key, table_obj)
259
252
 
260
- for i in table_obj.get("indexes", []):
253
+ for i in table_obj.indexes:
261
254
  self.visit_index(i, table)
262
255
 
263
256
  self.graph_index[table_id] = table
264
257
  return table, columns
265
258
 
266
- def check_column(self, column_obj: _Mapping, table_obj: _Mapping) -> None:
267
- self.checker.check_column(column_obj, table_obj)
268
- _id = column_obj["@id"]
269
- # Guaranteed to exist at this point, for mypy use "" as default
270
- datatype_name = column_obj.get("datatype", "")
271
- felis_type = FelisType.felis_type(datatype_name)
259
+ def check_column(self, column_obj: datamodel.Column) -> None:
260
+ _id = column_obj.id
261
+ datatype_name = column_obj.datatype
262
+ felis_type = FelisType.felis_type(datatype_name.value)
272
263
  if felis_type.is_sized:
273
264
  # It is expected that both arraysize and length are fine for
274
265
  # length types.
275
- arraysize = column_obj.get("votable:arraysize", column_obj.get("length"))
266
+ arraysize = column_obj.votable_arraysize or column_obj.length
276
267
  if arraysize is None:
277
268
  logger.warning(
278
269
  f"votable:arraysize and length for {_id} are None for type {datatype_name}. "
@@ -283,7 +274,7 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
283
274
  # datetime types really should have a votable:arraysize, because
284
275
  # they are converted to strings and the `length` is loosely to the
285
276
  # string size
286
- if "votable:arraysize" not in column_obj:
277
+ if not column_obj.votable_arraysize:
287
278
  logger.warning(
288
279
  f"votable:arraysize for {_id} is None for type {datatype_name}. "
289
280
  f'Using length "*". '
@@ -291,47 +282,45 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
291
282
  "materialized datetime/timestamp strings."
292
283
  )
293
284
 
294
- def visit_column(self, column_obj: _Mapping, table_obj: _Mapping) -> Tap11Base:
295
- self.check_column(column_obj, table_obj)
296
- column_id = column_obj["@id"]
297
- table_name = self._table_name(table_obj["name"])
285
+ def visit_column(self, column_obj: datamodel.Column, table_obj: Table) -> Tap11Base:
286
+ self.check_column(column_obj)
287
+ column_id = column_obj.id
288
+ table_name = self._table_name(table_obj.name)
298
289
 
299
290
  column = self.tables["columns"]()
300
291
  column.table_name = table_name
301
- column.column_name = column_obj["name"]
292
+ column.column_name = column_obj.name
302
293
 
303
- felis_datatype = column_obj["datatype"]
304
- felis_type = FelisType.felis_type(felis_datatype)
305
- column.datatype = column_obj.get("votable:datatype", felis_type.votable_name)
294
+ felis_datatype = column_obj.datatype
295
+ felis_type = FelisType.felis_type(felis_datatype.value)
296
+ column.datatype = column_obj.votable_datatype or felis_type.votable_name
306
297
 
307
298
  arraysize = None
308
299
  if felis_type.is_sized:
309
- # prefer votable:arraysize to length, fall back to `*`
310
- arraysize = column_obj.get("votable:arraysize", column_obj.get("length", "*"))
300
+ arraysize = column_obj.votable_arraysize or column_obj.length or "*"
311
301
  if felis_type.is_timestamp:
312
- arraysize = column_obj.get("votable:arraysize", "*")
302
+ arraysize = column_obj.votable_arraysize or "*"
313
303
  column.arraysize = arraysize
314
304
 
315
- column.xtype = column_obj.get("votable:xtype")
316
- column.description = column_obj.get("description")
317
- column.utype = column_obj.get("votable:utype")
305
+ column.xtype = column_obj.votable_xtype
306
+ column.description = column_obj.description
307
+ column.utype = column_obj.votable_utype
318
308
 
319
- unit = column_obj.get("ivoa:unit") or column_obj.get("fits:tunit")
309
+ unit = column_obj.ivoa_unit or column_obj.fits_tunit
320
310
  column.unit = unit
321
- column.ucd = column_obj.get("ivoa:ucd")
311
+ column.ucd = column_obj.ivoa_ucd
322
312
 
323
313
  # We modify this after we process columns
324
314
  column.indexed = 0
325
315
 
326
- column.principal = column_obj.get("tap:principal", 0)
327
- column.std = column_obj.get("tap:std", 0)
328
- column.column_index = column_obj.get("tap:column_index")
316
+ column.principal = column_obj.tap_principal
317
+ column.std = column_obj.tap_std
318
+ column.column_index = column_obj.tap_column_index
329
319
 
330
320
  self.graph_index[column_id] = column
331
321
  return column
332
322
 
333
- def visit_primary_key(self, primary_key_obj: str | Iterable[str], table_obj: _Mapping) -> None:
334
- self.checker.check_primary_key(primary_key_obj, table_obj)
323
+ def visit_primary_key(self, primary_key_obj: str | Iterable[str] | None, table_obj: Table) -> None:
335
324
  if primary_key_obj:
336
325
  if isinstance(primary_key_obj, str):
337
326
  primary_key_obj = [primary_key_obj]
@@ -341,19 +330,18 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
341
330
  columns[0].indexed = 1
342
331
  return None
343
332
 
344
- def visit_constraint(self, constraint_obj: _Mapping, table_obj: _Mapping) -> tuple:
345
- self.checker.check_constraint(constraint_obj, table_obj)
346
- constraint_type = constraint_obj["@type"]
333
+ def visit_constraint(self, constraint_obj: Constraint) -> tuple:
334
+ constraint_type = constraint_obj.type
347
335
  key = None
348
336
  key_columns = []
349
337
  if constraint_type == "ForeignKey":
350
- constraint_name = constraint_obj["name"]
351
- description = constraint_obj.get("description")
352
- utype = constraint_obj.get("votable:utype")
338
+ constraint_name = constraint_obj.name
339
+ description = constraint_obj.description
340
+ utype = constraint_obj.votable_utype
353
341
 
354
- columns = [self.graph_index[col["@id"]] for col in constraint_obj.get("columns", [])]
342
+ columns = [self.graph_index[col_id] for col_id in getattr(constraint_obj, "columns", [])]
355
343
  refcolumns = [
356
- self.graph_index[refcol["@id"]] for refcol in constraint_obj.get("referencedColumns", [])
344
+ self.graph_index[refcol_id] for refcol_id in getattr(constraint_obj, "referenced_columns", [])
357
345
  ]
358
346
 
359
347
  table_name = None
@@ -386,9 +374,8 @@ class TapLoadingVisitor(Visitor[None, tuple, Tap11Base, None, tuple, None, None]
386
374
  key_columns.append(key_column)
387
375
  return key, key_columns
388
376
 
389
- def visit_index(self, index_obj: _Mapping, table_obj: _Mapping) -> None:
390
- self.checker.check_index(index_obj, table_obj)
391
- columns = [self.graph_index[col["@id"]] for col in index_obj.get("columns", [])]
377
+ def visit_index(self, index_obj: Index, table_obj: Table) -> None:
378
+ columns = [self.graph_index[col_id] for col_id in getattr(index_obj, "columns", [])]
392
379
  # if just one column and it's indexed, update the object
393
380
  if len(columns) == 1:
394
381
  columns[0].indexed = 1
felis/validation.py CHANGED
@@ -60,7 +60,7 @@ class RspTable(Table):
60
60
 
61
61
  @model_validator(mode="after") # type: ignore[arg-type]
62
62
  @classmethod
63
- def check_tap_principal(cls: Any, tbl: "RspTable") -> "RspTable":
63
+ def check_tap_principal(cls: Any, tbl: RspTable) -> RspTable:
64
64
  """Check that at least one column is flagged as 'principal' for
65
65
  TAP purposes.
66
66
  """
felis/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "27.0.0rc3"
2
+ __version__ = "27.2024.1800"
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lsst-felis
3
- Version: 27.0.0rc3
3
+ Version: 27.2024.1800
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+)
7
- Project-URL: Homepage, https://github.com/lsst/felis
7
+ Project-URL: Homepage, https://felis.lsst.io
8
+ Project-URL: Source, https://github.com/lsst/felis
8
9
  Keywords: lsst
9
10
  Classifier: Intended Audience :: Science/Research
10
11
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
@@ -21,9 +22,10 @@ Requires-Dist: astropy >=4
21
22
  Requires-Dist: sqlalchemy >=1.4
22
23
  Requires-Dist: click >=7
23
24
  Requires-Dist: pyyaml >=6
24
- Requires-Dist: pyld >=2
25
25
  Requires-Dist: pydantic <3,>=2
26
26
  Requires-Dist: lsst-utils
27
+ Provides-Extra: dev
28
+ Requires-Dist: documenteer[guide] ; extra == 'dev'
27
29
  Provides-Extra: test
28
30
  Requires-Dist: pytest >=3.2 ; extra == 'test'
29
31
 
@@ -0,0 +1,20 @@
1
+ felis/__init__.py,sha256=THmRg3ylB4E73XhFjJX7YlnV_CM3lr_gZO_HqQFzIQ4,937
2
+ felis/cli.py,sha256=l_4srdXPghBLAVuOvfJVdyIVhq45kTV5KYskmYSsIUA,10279
3
+ felis/datamodel.py,sha256=Jf6WQLoxuERw61GFF9Q1Vl2hSr3-osym_FtJNlOZGFU,20540
4
+ felis/metadata.py,sha256=R2mJgtupBDG_B7sPhV8NlAEa5XI6ezy8JhZjBT5Jme0,18907
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=2dU7WXKzRkyzGTBOBEcitF06p_bPRV6WQw9jHANDoDo,55
10
+ felis/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ felis/db/_variants.py,sha256=zCuXDgU_x_pTZcWkBLgqQCiOhlA6y2tBt-PUQfafwmM,3368
12
+ felis/db/sqltypes.py,sha256=yFlautQ1hv21MHF4AIfBp7_2m1-exKBfc76xYsMHBgk,5735
13
+ lsst_felis-27.2024.1800.dist-info/COPYRIGHT,sha256=bUmNy19uUxqITMpjeHFe69q3IzQpjxvvBw6oV7kR7ho,129
14
+ lsst_felis-27.2024.1800.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
15
+ lsst_felis-27.2024.1800.dist-info/METADATA,sha256=6iicjHwqksW8KOYVpkvhkfnzBkaojndCqHdRGTds_1M,1191
16
+ lsst_felis-27.2024.1800.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
17
+ lsst_felis-27.2024.1800.dist-info/entry_points.txt,sha256=Gk2XFujA_Gp52VBk45g5kim8TDoMDJFPctsMqiq72EM,40
18
+ lsst_felis-27.2024.1800.dist-info/top_level.txt,sha256=F4SvPip3iZRVyISi50CHhwTIAokAhSxjWiVcn4IVWRI,6
19
+ lsst_felis-27.2024.1800.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
20
+ lsst_felis-27.2024.1800.dist-info/RECORD,,