lsst-felis 29.2025.2000__tar.gz → 29.2025.2100__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.2000/python/lsst_felis.egg-info → lsst_felis-29.2025.2100}/PKG-INFO +1 -1
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/cli.py +14 -1
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/datamodel.py +12 -2
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100/python/lsst_felis.egg-info}/PKG-INFO +1 -1
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/tests/test_tap_schema.py +80 -27
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/COPYRIGHT +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/LICENSE +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/README.rst +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/pyproject.toml +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/__init__.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/db/__init__.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/db/dialects.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/db/schema.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/db/sqltypes.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/db/utils.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/db/variants.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/diff.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/metadata.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/py.typed +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/schemas/tap_schema_std.yaml +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/tap_schema.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/tests/__init__.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/tests/postgresql.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/tests/run_cli.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/felis/types.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/lsst_felis.egg-info/SOURCES.txt +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/lsst_felis.egg-info/dependency_links.txt +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/lsst_felis.egg-info/entry_points.txt +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/lsst_felis.egg-info/requires.txt +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/lsst_felis.egg-info/top_level.txt +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/lsst_felis.egg-info/zip-safe +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/setup.cfg +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/tests/test_cli.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/tests/test_datamodel.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/tests/test_db.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/tests/test_diff.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/tests/test_metadata.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/tests/test_postgres.py +0 -0
- {lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/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.2100
|
|
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+)
|
|
@@ -198,6 +198,12 @@ def create(
|
|
|
198
198
|
@click.option("--dry-run", "-D", is_flag=True, help="Execute dry run only. Does not insert any data.")
|
|
199
199
|
@click.option("--echo", "-e", is_flag=True, help="Print out the generated insert statements to stdout")
|
|
200
200
|
@click.option("--output-file", "-o", type=click.Path(), help="Write SQL commands to a file")
|
|
201
|
+
@click.option(
|
|
202
|
+
"--force-unbounded-arraysize",
|
|
203
|
+
is_flag=True,
|
|
204
|
+
help="Use unbounded arraysize by default for all variable length string columns"
|
|
205
|
+
", e.g., ``votable:arraysize: *`` (workaround for astropy bug #18099)",
|
|
206
|
+
) # DM-50899: Variable-length bounded strings are not handled correctly in astropy
|
|
201
207
|
@click.option(
|
|
202
208
|
"--unique-keys",
|
|
203
209
|
"-u",
|
|
@@ -216,6 +222,7 @@ def load_tap_schema(
|
|
|
216
222
|
dry_run: bool,
|
|
217
223
|
echo: bool,
|
|
218
224
|
output_file: str | None,
|
|
225
|
+
force_unbounded_arraysize: bool,
|
|
219
226
|
unique_keys: bool,
|
|
220
227
|
file: IO[str],
|
|
221
228
|
) -> None:
|
|
@@ -256,7 +263,13 @@ def load_tap_schema(
|
|
|
256
263
|
table_name_postfix=tap_tables_postfix,
|
|
257
264
|
)
|
|
258
265
|
|
|
259
|
-
schema = Schema.from_stream(
|
|
266
|
+
schema = Schema.from_stream(
|
|
267
|
+
file,
|
|
268
|
+
context={
|
|
269
|
+
"id_generation": ctx.obj["id_generation"],
|
|
270
|
+
"force_unbounded_arraysize": force_unbounded_arraysize,
|
|
271
|
+
},
|
|
272
|
+
)
|
|
260
273
|
|
|
261
274
|
DataLoader(
|
|
262
275
|
schema,
|
|
@@ -421,7 +421,7 @@ class Column(BaseObject):
|
|
|
421
421
|
|
|
422
422
|
@model_validator(mode="before")
|
|
423
423
|
@classmethod
|
|
424
|
-
def check_votable_arraysize(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
424
|
+
def check_votable_arraysize(cls, values: dict[str, Any], info: ValidationInfo) -> dict[str, Any]:
|
|
425
425
|
"""Set the default value for the ``votable_arraysize`` field, which
|
|
426
426
|
corresponds to ``arraysize`` in the IVOA VOTable standard.
|
|
427
427
|
|
|
@@ -429,6 +429,8 @@ class Column(BaseObject):
|
|
|
429
429
|
----------
|
|
430
430
|
values
|
|
431
431
|
Values of the column.
|
|
432
|
+
info
|
|
433
|
+
Validation context used to determine if the check is enabled.
|
|
432
434
|
|
|
433
435
|
Returns
|
|
434
436
|
-------
|
|
@@ -443,6 +445,7 @@ class Column(BaseObject):
|
|
|
443
445
|
if values.get("name", None) is None or values.get("datatype", None) is None:
|
|
444
446
|
# Skip bad column data that will not validate
|
|
445
447
|
return values
|
|
448
|
+
context = info.context if info.context else {}
|
|
446
449
|
arraysize = values.get("votable:arraysize", None)
|
|
447
450
|
if arraysize is None:
|
|
448
451
|
length = values.get("length", None)
|
|
@@ -452,7 +455,14 @@ class Column(BaseObject):
|
|
|
452
455
|
if datatype == "char":
|
|
453
456
|
arraysize = str(length)
|
|
454
457
|
elif datatype in ("string", "unicode", "binary"):
|
|
455
|
-
|
|
458
|
+
if context.get("force_unbounded_arraysize", False):
|
|
459
|
+
arraysize = "*"
|
|
460
|
+
logger.debug(
|
|
461
|
+
f"Forced VOTable's 'arraysize' to '*' on column '{values['name']}' with datatype "
|
|
462
|
+
+ f"'{values['datatype']}' and length '{length}'"
|
|
463
|
+
)
|
|
464
|
+
else:
|
|
465
|
+
arraysize = f"{length}*"
|
|
456
466
|
elif datatype in ("timestamp", "text"):
|
|
457
467
|
arraysize = "*"
|
|
458
468
|
if arraysize is not None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-felis
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.2100
|
|
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,7 @@ import tempfile
|
|
|
25
25
|
import unittest
|
|
26
26
|
from typing import Any
|
|
27
27
|
|
|
28
|
-
from sqlalchemy import
|
|
28
|
+
from sqlalchemy import create_engine, select
|
|
29
29
|
|
|
30
30
|
from felis.datamodel import Schema
|
|
31
31
|
from felis.tap_schema import DataLoader, TableManager
|
|
@@ -153,29 +153,54 @@ def _find_row(rows: list[dict[str, Any]], column_name: str, value: str) -> dict[
|
|
|
153
153
|
return next_row
|
|
154
154
|
|
|
155
155
|
|
|
156
|
-
class
|
|
157
|
-
"""
|
|
156
|
+
class TapSchemaSqliteSetup:
|
|
157
|
+
"""Set up the TAP_SCHEMA SQLite database for testing.
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
context : `dict`
|
|
162
|
+
Context for the schema. Default is an empty dictionary.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(self, context: dict = {}) -> None:
|
|
161
166
|
with open(TEST_TAP_SCHEMA) as test_file:
|
|
162
|
-
self.
|
|
167
|
+
self._schema = Schema.from_stream(test_file, context=context)
|
|
163
168
|
|
|
164
|
-
self.
|
|
169
|
+
self._engine = create_engine("sqlite:///:memory:")
|
|
165
170
|
|
|
166
171
|
mgr = TableManager(apply_schema_to_metadata=False)
|
|
167
|
-
mgr.initialize_database(self.
|
|
168
|
-
self.
|
|
172
|
+
mgr.initialize_database(self._engine)
|
|
173
|
+
self._mgr = mgr
|
|
169
174
|
|
|
170
|
-
loader = DataLoader(self.
|
|
175
|
+
loader = DataLoader(self._schema, mgr, self._engine, tap_schema_index=2)
|
|
171
176
|
loader.load()
|
|
172
177
|
|
|
173
|
-
|
|
174
|
-
|
|
178
|
+
@property
|
|
179
|
+
def schema(self) -> Schema:
|
|
180
|
+
"""Return the schema."""
|
|
181
|
+
return self._schema
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def engine(self) -> Any:
|
|
185
|
+
"""Return the engine."""
|
|
186
|
+
return self._engine
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def mgr(self) -> TableManager:
|
|
190
|
+
"""Return the table manager."""
|
|
191
|
+
return self._mgr
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class TapSchemaDataTest(unittest.TestCase):
|
|
195
|
+
"""Test the validity of generated TAP SCHEMA data."""
|
|
196
|
+
|
|
197
|
+
def setUp(self) -> None:
|
|
198
|
+
"""Set up the test case."""
|
|
199
|
+
self.tap_schema_setup = TapSchemaSqliteSetup(context={"id_generation": True})
|
|
175
200
|
|
|
176
201
|
def test_schemas(self) -> None:
|
|
177
|
-
schemas_table = self.mgr["schemas"]
|
|
178
|
-
with self.engine.connect() as connection:
|
|
202
|
+
schemas_table = self.tap_schema_setup.mgr["schemas"]
|
|
203
|
+
with self.tap_schema_setup.engine.connect() as connection:
|
|
179
204
|
result = connection.execute(select(schemas_table))
|
|
180
205
|
schema_data = [row._asdict() for row in result]
|
|
181
206
|
|
|
@@ -188,8 +213,8 @@ class TapSchemaDataTest(unittest.TestCase):
|
|
|
188
213
|
self.assertEqual(schema["schema_index"], 2)
|
|
189
214
|
|
|
190
215
|
def test_tables(self) -> None:
|
|
191
|
-
tables_table = self.mgr["tables"]
|
|
192
|
-
with self.engine.connect() as connection:
|
|
216
|
+
tables_table = self.tap_schema_setup.mgr["tables"]
|
|
217
|
+
with self.tap_schema_setup.engine.connect() as connection:
|
|
193
218
|
result = connection.execute(select(tables_table))
|
|
194
219
|
table_data = [row._asdict() for row in result]
|
|
195
220
|
|
|
@@ -198,19 +223,21 @@ class TapSchemaDataTest(unittest.TestCase):
|
|
|
198
223
|
table = table_data[0]
|
|
199
224
|
assert isinstance(table, dict)
|
|
200
225
|
self.assertEqual(table["schema_name"], "test_schema")
|
|
201
|
-
self.assertEqual(table["table_name"], f"{self.schema.name}.table1")
|
|
226
|
+
self.assertEqual(table["table_name"], f"{self.tap_schema_setup.schema.name}.table1")
|
|
202
227
|
self.assertEqual(table["table_type"], "table")
|
|
203
228
|
self.assertEqual(table["utype"], "Table")
|
|
204
229
|
self.assertEqual(table["description"], "Test table 1")
|
|
205
230
|
self.assertEqual(table["table_index"], 2)
|
|
206
231
|
|
|
207
232
|
def test_columns(self) -> None:
|
|
208
|
-
columns_table = self.mgr["columns"]
|
|
209
|
-
with self.engine.connect() as connection:
|
|
233
|
+
columns_table = self.tap_schema_setup.mgr["columns"]
|
|
234
|
+
with self.tap_schema_setup.engine.connect() as connection:
|
|
210
235
|
result = connection.execute(select(columns_table))
|
|
211
236
|
column_data = [row._asdict() for row in result]
|
|
212
237
|
|
|
213
|
-
table1_rows = [
|
|
238
|
+
table1_rows = [
|
|
239
|
+
row for row in column_data if row["table_name"] == f"{self.tap_schema_setup.schema.name}.table1"
|
|
240
|
+
]
|
|
214
241
|
self.assertNotEqual(len(table1_rows), 0)
|
|
215
242
|
|
|
216
243
|
boolean_col = _find_row(table1_rows, "column_name", "boolean_field")
|
|
@@ -275,8 +302,8 @@ class TapSchemaDataTest(unittest.TestCase):
|
|
|
275
302
|
self.assertEqual(txt_col["arraysize"], "*")
|
|
276
303
|
|
|
277
304
|
def test_keys(self) -> None:
|
|
278
|
-
keys_table = self.mgr["keys"]
|
|
279
|
-
with self.engine.connect() as connection:
|
|
305
|
+
keys_table = self.tap_schema_setup.mgr["keys"]
|
|
306
|
+
with self.tap_schema_setup.engine.connect() as connection:
|
|
280
307
|
result = connection.execute(select(keys_table))
|
|
281
308
|
key_data = [row._asdict() for row in result]
|
|
282
309
|
|
|
@@ -286,14 +313,14 @@ class TapSchemaDataTest(unittest.TestCase):
|
|
|
286
313
|
assert isinstance(key, dict)
|
|
287
314
|
|
|
288
315
|
self.assertEqual(key["key_id"], "fk_table1_to_table2")
|
|
289
|
-
self.assertEqual(key["from_table"], f"{self.schema.name}.table1")
|
|
290
|
-
self.assertEqual(key["target_table"], f"{self.schema.name}.table2")
|
|
316
|
+
self.assertEqual(key["from_table"], f"{self.tap_schema_setup.schema.name}.table1")
|
|
317
|
+
self.assertEqual(key["target_table"], f"{self.tap_schema_setup.schema.name}.table2")
|
|
291
318
|
self.assertEqual(key["description"], "Foreign key from table1 to table2")
|
|
292
319
|
self.assertEqual(key["utype"], "ForeignKey")
|
|
293
320
|
|
|
294
321
|
def test_key_columns(self) -> None:
|
|
295
|
-
key_columns_table = self.mgr["key_columns"]
|
|
296
|
-
with self.engine.connect() as connection:
|
|
322
|
+
key_columns_table = self.tap_schema_setup.mgr["key_columns"]
|
|
323
|
+
with self.tap_schema_setup.engine.connect() as connection:
|
|
297
324
|
result = connection.execute(select(key_columns_table))
|
|
298
325
|
key_column_data = [row._asdict() for row in result]
|
|
299
326
|
|
|
@@ -309,7 +336,33 @@ class TapSchemaDataTest(unittest.TestCase):
|
|
|
309
336
|
def test_bad_table_name(self) -> None:
|
|
310
337
|
"""Test getting a bad TAP_SCHEMA table name."""
|
|
311
338
|
with self.assertRaises(KeyError):
|
|
312
|
-
self.mgr["bad_table"]
|
|
339
|
+
self.tap_schema_setup.mgr["bad_table"]
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class ForceUnboundArraySizeTest(unittest.TestCase):
|
|
343
|
+
"""Test that arraysize for appropriate types is set to '*' when the
|
|
344
|
+
``force_unboundeded_arraysize`` context flag is set to ``True``.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
def setUp(self) -> None:
|
|
348
|
+
"""Set up the test case."""
|
|
349
|
+
self.tap_schema_setup = TapSchemaSqliteSetup(
|
|
350
|
+
context={"id_generation": True, "force_unbounded_arraysize": True}
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def test_force_unbounded_arraysize(self) -> None:
|
|
354
|
+
"""Test that unbounded arraysize is set to None."""
|
|
355
|
+
columns_table = self.tap_schema_setup.mgr["columns"]
|
|
356
|
+
with self.tap_schema_setup.engine.connect() as connection:
|
|
357
|
+
result = connection.execute(select(columns_table))
|
|
358
|
+
column_data = [row._asdict() for row in result]
|
|
359
|
+
|
|
360
|
+
table1_rows = [
|
|
361
|
+
row for row in column_data if row["table_name"] == f"{self.tap_schema_setup.schema.name}.table1"
|
|
362
|
+
]
|
|
363
|
+
for row in table1_rows:
|
|
364
|
+
if row["column_name"] in ["string_field", "text_field", "unicode_field", "binary_field"]:
|
|
365
|
+
self.assertEqual(row["arraysize"], "*")
|
|
313
366
|
|
|
314
367
|
|
|
315
368
|
if __name__ == "__main__":
|
|
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.2000 → lsst_felis-29.2025.2100}/python/felis/schemas/tap_schema_std.yaml
RENAMED
|
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.2000 → lsst_felis-29.2025.2100}/python/lsst_felis.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/python/lsst_felis.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_felis-29.2025.2000 → lsst_felis-29.2025.2100}/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
|
|
File without changes
|