lsst-felis 29.2025.1000__py3-none-any.whl → 29.2025.1200__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 +20 -6
- felis/tap_schema.py +65 -5
- {lsst_felis-29.2025.1000.dist-info → lsst_felis-29.2025.1200.dist-info}/METADATA +3 -2
- {lsst_felis-29.2025.1000.dist-info → lsst_felis-29.2025.1200.dist-info}/RECORD +10 -10
- {lsst_felis-29.2025.1000.dist-info → lsst_felis-29.2025.1200.dist-info}/WHEEL +1 -1
- {lsst_felis-29.2025.1000.dist-info → lsst_felis-29.2025.1200.dist-info}/entry_points.txt +0 -0
- {lsst_felis-29.2025.1000.dist-info → lsst_felis-29.2025.1200.dist-info/licenses}/COPYRIGHT +0 -0
- {lsst_felis-29.2025.1000.dist-info → lsst_felis-29.2025.1200.dist-info/licenses}/LICENSE +0 -0
- {lsst_felis-29.2025.1000.dist-info → lsst_felis-29.2025.1200.dist-info}/top_level.txt +0 -0
- {lsst_felis-29.2025.1000.dist-info → lsst_felis-29.2025.1200.dist-info}/zip-safe +0 -0
felis/cli.py
CHANGED
|
@@ -180,14 +180,26 @@ def create(
|
|
|
180
180
|
|
|
181
181
|
@cli.command("load-tap-schema", help="Load metadata from a Felis file into a TAP_SCHEMA database")
|
|
182
182
|
@click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL")
|
|
183
|
-
@click.option("--tap-schema-name", help="Name of the TAP_SCHEMA schema in the database (default: TAP_SCHEMA)")
|
|
184
183
|
@click.option(
|
|
185
|
-
"--tap-
|
|
184
|
+
"--tap-schema-name", "-n", help="Name of the TAP_SCHEMA schema in the database (default: TAP_SCHEMA)"
|
|
185
|
+
)
|
|
186
|
+
@click.option(
|
|
187
|
+
"--tap-tables-postfix",
|
|
188
|
+
"-p",
|
|
189
|
+
help="Postfix which is applied to standard TAP_SCHEMA table names",
|
|
190
|
+
default="",
|
|
191
|
+
)
|
|
192
|
+
@click.option("--tap-schema-index", "-i", type=int, help="TAP_SCHEMA index of the schema in this environment")
|
|
193
|
+
@click.option("--dry-run", "-D", is_flag=True, help="Execute dry run only. Does not insert any data.")
|
|
194
|
+
@click.option("--echo", "-e", is_flag=True, help="Print out the generated insert statements to stdout")
|
|
195
|
+
@click.option("--output-file", "-o", type=click.Path(), help="Write SQL commands to a file")
|
|
196
|
+
@click.option(
|
|
197
|
+
"--unique-keys",
|
|
198
|
+
"-u",
|
|
199
|
+
is_flag=True,
|
|
200
|
+
help="Generate unique key_id values for keys and key_columns tables by prepending the schema name",
|
|
201
|
+
default=False,
|
|
186
202
|
)
|
|
187
|
-
@click.option("--tap-schema-index", type=int, help="TAP_SCHEMA index of the schema in this environment")
|
|
188
|
-
@click.option("--dry-run", is_flag=True, help="Execute dry run only. Does not insert any data.")
|
|
189
|
-
@click.option("--echo", is_flag=True, help="Print out the generated insert statements to stdout")
|
|
190
|
-
@click.option("--output-file", type=click.Path(), help="Write SQL commands to a file")
|
|
191
203
|
@click.argument("file", type=click.File())
|
|
192
204
|
@click.pass_context
|
|
193
205
|
def load_tap_schema(
|
|
@@ -199,6 +211,7 @@ def load_tap_schema(
|
|
|
199
211
|
dry_run: bool,
|
|
200
212
|
echo: bool,
|
|
201
213
|
output_file: str | None,
|
|
214
|
+
unique_keys: bool,
|
|
202
215
|
file: IO[str],
|
|
203
216
|
) -> None:
|
|
204
217
|
"""Load TAP metadata from a Felis file.
|
|
@@ -248,6 +261,7 @@ def load_tap_schema(
|
|
|
248
261
|
dry_run=dry_run,
|
|
249
262
|
print_sql=echo,
|
|
250
263
|
output_path=output_file,
|
|
264
|
+
unique_keys=unique_keys,
|
|
251
265
|
).load()
|
|
252
266
|
|
|
253
267
|
|
felis/tap_schema.py
CHANGED
|
@@ -27,7 +27,7 @@ import re
|
|
|
27
27
|
from typing import Any
|
|
28
28
|
|
|
29
29
|
from lsst.resources import ResourcePath
|
|
30
|
-
from sqlalchemy import MetaData, Table, text
|
|
30
|
+
from sqlalchemy import MetaData, Table, select, text
|
|
31
31
|
from sqlalchemy.engine import Connection, Engine
|
|
32
32
|
from sqlalchemy.engine.mock import MockConnection
|
|
33
33
|
from sqlalchemy.exc import SQLAlchemyError
|
|
@@ -35,7 +35,7 @@ from sqlalchemy.schema import CreateSchema
|
|
|
35
35
|
from sqlalchemy.sql.dml import Insert
|
|
36
36
|
|
|
37
37
|
from felis import datamodel
|
|
38
|
-
from felis.datamodel import Schema
|
|
38
|
+
from felis.datamodel import Constraint, Schema
|
|
39
39
|
from felis.db.utils import is_valid_engine
|
|
40
40
|
from felis.metadata import MetaDataBuilder
|
|
41
41
|
|
|
@@ -163,7 +163,7 @@ class TableManager:
|
|
|
163
163
|
tables to be accessed by their standard TAP_SCHEMA names.
|
|
164
164
|
"""
|
|
165
165
|
if table_name not in self._table_map:
|
|
166
|
-
raise KeyError(f"Table '{table_name}' not found in
|
|
166
|
+
raise KeyError(f"Table '{table_name}' not found in TAP_SCHEMA")
|
|
167
167
|
return self.metadata.tables[self._table_map[table_name]]
|
|
168
168
|
|
|
169
169
|
@property
|
|
@@ -365,6 +365,34 @@ class TableManager:
|
|
|
365
365
|
self._create_schema(engine)
|
|
366
366
|
self.metadata.create_all(engine)
|
|
367
367
|
|
|
368
|
+
def select(self, engine: Engine, table_name: str, filter_condition: str = "") -> list[dict[str, Any]]:
|
|
369
|
+
"""Select all rows from a TAP_SCHEMA table with an optional filter
|
|
370
|
+
condition.
|
|
371
|
+
|
|
372
|
+
Parameters
|
|
373
|
+
----------
|
|
374
|
+
engine
|
|
375
|
+
The SQLAlchemy engine to use to connect to the database.
|
|
376
|
+
table_name
|
|
377
|
+
The name of the table to select from.
|
|
378
|
+
filter_condition
|
|
379
|
+
The filter condition as a string. If empty, no filter will be
|
|
380
|
+
applied.
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
list
|
|
385
|
+
A list of dictionaries containing the rows from the table.
|
|
386
|
+
"""
|
|
387
|
+
table = self[table_name]
|
|
388
|
+
query = select(table)
|
|
389
|
+
if filter_condition:
|
|
390
|
+
query = query.where(text(filter_condition))
|
|
391
|
+
with engine.connect() as connection:
|
|
392
|
+
result = connection.execute(query)
|
|
393
|
+
rows = [dict(row._mapping) for row in result]
|
|
394
|
+
return rows
|
|
395
|
+
|
|
368
396
|
|
|
369
397
|
class DataLoader:
|
|
370
398
|
"""Load data into the TAP_SCHEMA tables.
|
|
@@ -386,6 +414,9 @@ class DataLoader:
|
|
|
386
414
|
If True, print the SQL statements that will be executed.
|
|
387
415
|
dry_run
|
|
388
416
|
If True, the data will not be loaded into the database.
|
|
417
|
+
unique_keys
|
|
418
|
+
If True, prepend the schema name to the key name to make it unique
|
|
419
|
+
when loading data into the keys and key_columns tables.
|
|
389
420
|
"""
|
|
390
421
|
|
|
391
422
|
def __init__(
|
|
@@ -397,6 +428,7 @@ class DataLoader:
|
|
|
397
428
|
output_path: str | None = None,
|
|
398
429
|
print_sql: bool = False,
|
|
399
430
|
dry_run: bool = False,
|
|
431
|
+
unique_keys: bool = False,
|
|
400
432
|
):
|
|
401
433
|
self.schema = schema
|
|
402
434
|
self.mgr = mgr
|
|
@@ -406,6 +438,7 @@ class DataLoader:
|
|
|
406
438
|
self.output_path = output_path
|
|
407
439
|
self.print_sql = print_sql
|
|
408
440
|
self.dry_run = dry_run
|
|
441
|
+
self.unique_keys = unique_keys
|
|
409
442
|
|
|
410
443
|
def load(self) -> None:
|
|
411
444
|
"""Load the schema data into the TAP_SCHEMA tables.
|
|
@@ -501,6 +534,32 @@ class DataLoader:
|
|
|
501
534
|
}
|
|
502
535
|
self._insert("columns", column_record)
|
|
503
536
|
|
|
537
|
+
def _get_key(self, constraint: Constraint) -> str:
|
|
538
|
+
"""Get the key name for a constraint.
|
|
539
|
+
|
|
540
|
+
Parameters
|
|
541
|
+
----------
|
|
542
|
+
constraint
|
|
543
|
+
The constraint to get the key name for.
|
|
544
|
+
|
|
545
|
+
Returns
|
|
546
|
+
-------
|
|
547
|
+
str
|
|
548
|
+
The key name for the constraint.
|
|
549
|
+
|
|
550
|
+
Notes
|
|
551
|
+
-----
|
|
552
|
+
This will prepend the name of the schema to the key name if the
|
|
553
|
+
`unique_keys` attribute is set to True. Otherwise, it will just return
|
|
554
|
+
the name of the constraint.
|
|
555
|
+
"""
|
|
556
|
+
if self.unique_keys:
|
|
557
|
+
key_id = f"{self.schema.name}_{constraint.name}"
|
|
558
|
+
logger.debug("Generated unique key_id: %s -> %s", constraint.name, key_id)
|
|
559
|
+
else:
|
|
560
|
+
key_id = constraint.name
|
|
561
|
+
return key_id
|
|
562
|
+
|
|
504
563
|
def _insert_keys(self) -> None:
|
|
505
564
|
"""Insert the foreign keys into the keys and key_columns tables."""
|
|
506
565
|
for table in self.schema.tables:
|
|
@@ -511,8 +570,9 @@ class DataLoader:
|
|
|
511
570
|
constraint.referenced_columns[0], datamodel.Column
|
|
512
571
|
)
|
|
513
572
|
referenced_table = self.schema.get_table_by_column(referenced_column)
|
|
573
|
+
key_id = self._get_key(constraint)
|
|
514
574
|
key_record = {
|
|
515
|
-
"key_id":
|
|
575
|
+
"key_id": key_id,
|
|
516
576
|
"from_table": self._get_table_name(table),
|
|
517
577
|
"target_table": self._get_table_name(referenced_table),
|
|
518
578
|
"description": constraint.description,
|
|
@@ -526,7 +586,7 @@ class DataLoader:
|
|
|
526
586
|
constraint.referenced_columns[0], datamodel.Column
|
|
527
587
|
)
|
|
528
588
|
key_columns_record = {
|
|
529
|
-
"key_id":
|
|
589
|
+
"key_id": key_id,
|
|
530
590
|
"from_column": from_column.name,
|
|
531
591
|
"target_column": target_column.name,
|
|
532
592
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-felis
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.1200
|
|
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+)
|
|
@@ -35,3 +35,4 @@ Provides-Extra: dev
|
|
|
35
35
|
Requires-Dist: documenteer[guide]<2; extra == "dev"
|
|
36
36
|
Requires-Dist: autodoc_pydantic; extra == "dev"
|
|
37
37
|
Requires-Dist: sphinx-click; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
felis/__init__.py,sha256=HnwWzLaPOSnPzAoppSIHzTrGfixEgvkzJdBxa8-03cw,1294
|
|
2
|
-
felis/cli.py,sha256=
|
|
2
|
+
felis/cli.py,sha256=1a1wFYG5t1nZnXQhNtUA9WnXOyI3ECLXfcMZsk2AuYM,15578
|
|
3
3
|
felis/datamodel.py,sha256=VC11uKxSumksen_3zyCkH9l9A_BcTjMib341fLyOLMU,43068
|
|
4
4
|
felis/diff.py,sha256=Vs4JuNwHmm7FCXuhIWMNHB7BTQk0zCPq6tUXQq66Otw,7492
|
|
5
5
|
felis/metadata.py,sha256=cYx_qizkLBqcoxWV46h4TbwTi1KVJAkuA2OuUmD-K5k,13536
|
|
6
6
|
felis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
felis/tap_schema.py,sha256=
|
|
7
|
+
felis/tap_schema.py,sha256=vO_f1LvFPU6D1vv2SplIzOW_TV36zOOFOfXQIdD0rMI,24781
|
|
8
8
|
felis/types.py,sha256=ifZQjc-Uw5CM3L7hmFUb7wcHY1O_HgJCw6HPqyUkHvk,5510
|
|
9
9
|
felis/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
felis/db/dialects.py,sha256=XTZEbTnfy96GJDNRLCQMbAV6irerC87vhO_HyTIXLbs,3517
|
|
@@ -15,11 +15,11 @@ felis/db/variants.py,sha256=eahthrbVeV8ZdGamWQccNmWgx6CCscGrU0vQRs5HZK8,5260
|
|
|
15
15
|
felis/schemas/tap_schema_std.yaml,sha256=sPW-Vk72nY0PFpCvP5d8L8fWvhkif-x32sGtcfDZ8bU,7131
|
|
16
16
|
felis/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
felis/tests/postgresql.py,sha256=B_xk4fLual5-viGDqP20r94okuc0pbSvytRH_L0fvMs,4035
|
|
18
|
-
lsst_felis-29.2025.
|
|
19
|
-
lsst_felis-29.2025.
|
|
20
|
-
lsst_felis-29.2025.
|
|
21
|
-
lsst_felis-29.2025.
|
|
22
|
-
lsst_felis-29.2025.
|
|
23
|
-
lsst_felis-29.2025.
|
|
24
|
-
lsst_felis-29.2025.
|
|
25
|
-
lsst_felis-29.2025.
|
|
18
|
+
lsst_felis-29.2025.1200.dist-info/licenses/COPYRIGHT,sha256=vJAFLFTSF1mhy9eIuA3P6R-3yxTWKQgpig88P-1IzRw,129
|
|
19
|
+
lsst_felis-29.2025.1200.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
20
|
+
lsst_felis-29.2025.1200.dist-info/METADATA,sha256=aiSy5I7kzsMQwx28GGq98zBI4kPMgOfSgHSNKHE8yJA,1433
|
|
21
|
+
lsst_felis-29.2025.1200.dist-info/WHEEL,sha256=tTnHoFhvKQHCh4jz3yCn0WPTYIy7wXx3CJtJ7SJGV7c,91
|
|
22
|
+
lsst_felis-29.2025.1200.dist-info/entry_points.txt,sha256=Gk2XFujA_Gp52VBk45g5kim8TDoMDJFPctsMqiq72EM,40
|
|
23
|
+
lsst_felis-29.2025.1200.dist-info/top_level.txt,sha256=F4SvPip3iZRVyISi50CHhwTIAokAhSxjWiVcn4IVWRI,6
|
|
24
|
+
lsst_felis-29.2025.1200.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
25
|
+
lsst_felis-29.2025.1200.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|