lsst-felis 28.2024.4500__py3-none-any.whl → 29.2025.4500__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.
- felis/__init__.py +12 -1
- felis/cli.py +189 -175
- felis/config/tap_schema/columns.csv +33 -0
- felis/config/tap_schema/key_columns.csv +8 -0
- felis/config/tap_schema/keys.csv +8 -0
- felis/config/tap_schema/schemas.csv +2 -0
- felis/config/tap_schema/tables.csv +6 -0
- felis/datamodel.py +597 -56
- felis/db/dialects.py +1 -1
- felis/db/schema.py +62 -0
- felis/db/sqltypes.py +7 -7
- felis/db/utils.py +1 -1
- felis/diff.py +234 -0
- felis/metadata.py +10 -8
- felis/tap_schema.py +149 -26
- felis/tests/run_cli.py +79 -0
- felis/types.py +7 -7
- {lsst_felis-28.2024.4500.dist-info → lsst_felis-29.2025.4500.dist-info}/METADATA +20 -16
- lsst_felis-29.2025.4500.dist-info/RECORD +31 -0
- {lsst_felis-28.2024.4500.dist-info → lsst_felis-29.2025.4500.dist-info}/WHEEL +1 -1
- felis/tap.py +0 -597
- felis/tests/utils.py +0 -122
- felis/version.py +0 -2
- lsst_felis-28.2024.4500.dist-info/RECORD +0 -26
- /felis/{schemas → config/tap_schema}/tap_schema_std.yaml +0 -0
- {lsst_felis-28.2024.4500.dist-info → lsst_felis-29.2025.4500.dist-info}/entry_points.txt +0 -0
- {lsst_felis-28.2024.4500.dist-info → lsst_felis-29.2025.4500.dist-info/licenses}/COPYRIGHT +0 -0
- {lsst_felis-28.2024.4500.dist-info → lsst_felis-29.2025.4500.dist-info/licenses}/LICENSE +0 -0
- {lsst_felis-28.2024.4500.dist-info → lsst_felis-29.2025.4500.dist-info}/top_level.txt +0 -0
- {lsst_felis-28.2024.4500.dist-info → lsst_felis-29.2025.4500.dist-info}/zip-safe +0 -0
felis/__init__.py
CHANGED
|
@@ -19,4 +19,15 @@
|
|
|
19
19
|
# You should have received a copy of the GNU General Public License
|
|
20
20
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
21
|
|
|
22
|
-
from .
|
|
22
|
+
from .datamodel import Schema
|
|
23
|
+
from .db.schema import create_database
|
|
24
|
+
from .diff import DatabaseDiff, FormattedSchemaDiff, SchemaDiff
|
|
25
|
+
from .metadata import MetaDataBuilder
|
|
26
|
+
|
|
27
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
__version__ = version("lsst-felis")
|
|
31
|
+
except PackageNotFoundError:
|
|
32
|
+
# Package not installed or scons not run.
|
|
33
|
+
__version__ = "0.0.0"
|
felis/cli.py
CHANGED
|
@@ -34,10 +34,11 @@ from sqlalchemy.engine.mock import MockConnection, create_mock_engine
|
|
|
34
34
|
|
|
35
35
|
from . import __version__
|
|
36
36
|
from .datamodel import Schema
|
|
37
|
+
from .db.schema import create_database
|
|
37
38
|
from .db.utils import DatabaseContext, is_mock_url
|
|
39
|
+
from .diff import DatabaseDiff, FormattedSchemaDiff, SchemaDiff
|
|
38
40
|
from .metadata import MetaDataBuilder
|
|
39
|
-
from .
|
|
40
|
-
from .tap_schema import DataLoader, TableManager
|
|
41
|
+
from .tap_schema import DataLoader, MetadataInserter, TableManager
|
|
41
42
|
|
|
42
43
|
__all__ = ["cli"]
|
|
43
44
|
|
|
@@ -62,7 +63,10 @@ loglevel_choices = ["CRITICAL", "FATAL", "ERROR", "WARNING", "INFO", "DEBUG"]
|
|
|
62
63
|
help="Felis log file path",
|
|
63
64
|
)
|
|
64
65
|
@click.option(
|
|
65
|
-
"--id-generation",
|
|
66
|
+
"--id-generation/--no-id-generation",
|
|
67
|
+
is_flag=True,
|
|
68
|
+
help="Generate IDs for all objects that do not have them",
|
|
69
|
+
default=True,
|
|
66
70
|
)
|
|
67
71
|
@click.pass_context
|
|
68
72
|
def cli(ctx: click.Context, log_level: str, log_file: str | None, id_generation: bool) -> None:
|
|
@@ -71,6 +75,8 @@ def cli(ctx: click.Context, log_level: str, log_file: str | None, id_generation:
|
|
|
71
75
|
ctx.obj["id_generation"] = id_generation
|
|
72
76
|
if ctx.obj["id_generation"]:
|
|
73
77
|
logger.info("ID generation is enabled")
|
|
78
|
+
else:
|
|
79
|
+
logger.info("ID generation is disabled")
|
|
74
80
|
if log_file:
|
|
75
81
|
logging.basicConfig(filename=log_file, level=log_level)
|
|
76
82
|
else:
|
|
@@ -177,181 +183,34 @@ def create(
|
|
|
177
183
|
raise click.ClickException(str(e))
|
|
178
184
|
|
|
179
185
|
|
|
180
|
-
@cli.command("init-tap", help="Initialize TAP_SCHEMA objects in the database")
|
|
181
|
-
@click.option("--tap-schema-name", help="Alternate database schema name for 'TAP_SCHEMA'")
|
|
182
|
-
@click.option("--tap-schemas-table", help="Alternate table name for 'schemas'")
|
|
183
|
-
@click.option("--tap-tables-table", help="Alternate table name for 'tables'")
|
|
184
|
-
@click.option("--tap-columns-table", help="Alternate table name for 'columns'")
|
|
185
|
-
@click.option("--tap-keys-table", help="Alternate table name for 'keys'")
|
|
186
|
-
@click.option("--tap-key-columns-table", help="Alternate table name for 'key_columns'")
|
|
187
|
-
@click.argument("engine-url")
|
|
188
|
-
def init_tap(
|
|
189
|
-
engine_url: str,
|
|
190
|
-
tap_schema_name: str,
|
|
191
|
-
tap_schemas_table: str,
|
|
192
|
-
tap_tables_table: str,
|
|
193
|
-
tap_columns_table: str,
|
|
194
|
-
tap_keys_table: str,
|
|
195
|
-
tap_key_columns_table: str,
|
|
196
|
-
) -> None:
|
|
197
|
-
"""Initialize TAP_SCHEMA objects in the database.
|
|
198
|
-
|
|
199
|
-
Parameters
|
|
200
|
-
----------
|
|
201
|
-
engine_url
|
|
202
|
-
SQLAlchemy Engine URL. The target PostgreSQL schema or MySQL database
|
|
203
|
-
must already exist and be referenced in the URL.
|
|
204
|
-
tap_schema_name
|
|
205
|
-
Alterate name for the database schema ``TAP_SCHEMA``.
|
|
206
|
-
tap_schemas_table
|
|
207
|
-
Alterate table name for ``schemas``.
|
|
208
|
-
tap_tables_table
|
|
209
|
-
Alterate table name for ``tables``.
|
|
210
|
-
tap_columns_table
|
|
211
|
-
Alterate table name for ``columns``.
|
|
212
|
-
tap_keys_table
|
|
213
|
-
Alterate table name for ``keys``.
|
|
214
|
-
tap_key_columns_table
|
|
215
|
-
Alterate table name for ``key_columns``.
|
|
216
|
-
|
|
217
|
-
Notes
|
|
218
|
-
-----
|
|
219
|
-
The supported version of TAP_SCHEMA in the SQLAlchemy metadata is 1.1. The
|
|
220
|
-
tables are created in the database schema specified by the engine URL,
|
|
221
|
-
which must be a PostgreSQL schema or MySQL database that already exists.
|
|
222
|
-
"""
|
|
223
|
-
engine = create_engine(engine_url)
|
|
224
|
-
init_tables(
|
|
225
|
-
tap_schema_name,
|
|
226
|
-
tap_schemas_table,
|
|
227
|
-
tap_tables_table,
|
|
228
|
-
tap_columns_table,
|
|
229
|
-
tap_keys_table,
|
|
230
|
-
tap_key_columns_table,
|
|
231
|
-
)
|
|
232
|
-
Tap11Base.metadata.create_all(engine)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
@cli.command("load-tap", help="Load metadata from a Felis file into a TAP_SCHEMA database")
|
|
236
|
-
@click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL")
|
|
237
|
-
@click.option("--schema-name", help="Alternate Schema Name for Felis file")
|
|
238
|
-
@click.option("--catalog-name", help="Catalog Name for Schema")
|
|
239
|
-
@click.option("--dry-run", is_flag=True, help="Dry Run Only. Prints out the DDL that would be executed")
|
|
240
|
-
@click.option("--tap-schema-name", help="Alternate schema name for 'TAP_SCHEMA'")
|
|
241
|
-
@click.option("--tap-tables-postfix", help="Postfix for TAP_SCHEMA table names")
|
|
242
|
-
@click.option("--tap-schemas-table", help="Alternate table name for 'schemas'")
|
|
243
|
-
@click.option("--tap-tables-table", help="Alternate table name for 'tables'")
|
|
244
|
-
@click.option("--tap-columns-table", help="Alternate table name for 'columns'")
|
|
245
|
-
@click.option("--tap-keys-table", help="Alternate table name for 'keys'")
|
|
246
|
-
@click.option("--tap-key-columns-table", help="Alternate table name for 'key_columns'")
|
|
247
|
-
@click.option("--tap-schema-index", type=int, help="TAP_SCHEMA index of the schema in this environment")
|
|
248
|
-
@click.argument("file", type=click.File())
|
|
249
|
-
def load_tap(
|
|
250
|
-
engine_url: str,
|
|
251
|
-
schema_name: str,
|
|
252
|
-
catalog_name: str,
|
|
253
|
-
dry_run: bool,
|
|
254
|
-
tap_schema_name: str,
|
|
255
|
-
tap_tables_postfix: str,
|
|
256
|
-
tap_schemas_table: str,
|
|
257
|
-
tap_tables_table: str,
|
|
258
|
-
tap_columns_table: str,
|
|
259
|
-
tap_keys_table: str,
|
|
260
|
-
tap_key_columns_table: str,
|
|
261
|
-
tap_schema_index: int,
|
|
262
|
-
file: IO[str],
|
|
263
|
-
) -> None:
|
|
264
|
-
"""Load TAP metadata from a Felis file.
|
|
265
|
-
|
|
266
|
-
This command loads the associated TAP metadata from a Felis YAML file
|
|
267
|
-
into the TAP_SCHEMA tables.
|
|
268
|
-
|
|
269
|
-
Parameters
|
|
270
|
-
----------
|
|
271
|
-
engine_url
|
|
272
|
-
SQLAlchemy Engine URL to catalog.
|
|
273
|
-
schema_name
|
|
274
|
-
Alternate schema name. This overrides the schema name in the
|
|
275
|
-
``catalog`` field of the Felis file.
|
|
276
|
-
catalog_name
|
|
277
|
-
Catalog name for the schema. This possibly duplicates the
|
|
278
|
-
``tap_schema_name`` argument (DM-44870).
|
|
279
|
-
dry_run
|
|
280
|
-
Dry run only to print out commands instead of executing.
|
|
281
|
-
tap_schema_name
|
|
282
|
-
Alternate name for the schema of TAP_SCHEMA in the database.
|
|
283
|
-
tap_tables_postfix
|
|
284
|
-
Postfix for TAP table names that will be automatically appended.
|
|
285
|
-
tap_schemas_table
|
|
286
|
-
Alternate table name for ``schemas``.
|
|
287
|
-
tap_tables_table
|
|
288
|
-
Alternate table name for ``tables``.
|
|
289
|
-
tap_columns_table
|
|
290
|
-
Alternate table name for ``columns``.
|
|
291
|
-
tap_keys_table
|
|
292
|
-
Alternate table name for ``keys``.
|
|
293
|
-
tap_key_columns_table
|
|
294
|
-
Alternate table name for ``key_columns``.
|
|
295
|
-
tap_schema_index
|
|
296
|
-
TAP_SCHEMA index of the schema in this TAP environment.
|
|
297
|
-
file
|
|
298
|
-
Felis file to read.
|
|
299
|
-
|
|
300
|
-
Notes
|
|
301
|
-
-----
|
|
302
|
-
The data will be loaded into the TAP_SCHEMA from the engine URL. The
|
|
303
|
-
tables must have already been initialized or an error will occur.
|
|
304
|
-
"""
|
|
305
|
-
schema = Schema.from_stream(file)
|
|
306
|
-
|
|
307
|
-
tap_tables = init_tables(
|
|
308
|
-
tap_schema_name,
|
|
309
|
-
tap_tables_postfix,
|
|
310
|
-
tap_schemas_table,
|
|
311
|
-
tap_tables_table,
|
|
312
|
-
tap_columns_table,
|
|
313
|
-
tap_keys_table,
|
|
314
|
-
tap_key_columns_table,
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
if not dry_run:
|
|
318
|
-
engine = create_engine(engine_url)
|
|
319
|
-
|
|
320
|
-
if engine_url == "sqlite://" and not dry_run:
|
|
321
|
-
# In Memory SQLite - Mostly used to test
|
|
322
|
-
Tap11Base.metadata.create_all(engine)
|
|
323
|
-
|
|
324
|
-
tap_visitor = TapLoadingVisitor(
|
|
325
|
-
engine,
|
|
326
|
-
catalog_name=catalog_name,
|
|
327
|
-
schema_name=schema_name,
|
|
328
|
-
tap_tables=tap_tables,
|
|
329
|
-
tap_schema_index=tap_schema_index,
|
|
330
|
-
)
|
|
331
|
-
tap_visitor.visit_schema(schema)
|
|
332
|
-
else:
|
|
333
|
-
conn = DatabaseContext.create_mock_engine(engine_url)
|
|
334
|
-
|
|
335
|
-
tap_visitor = TapLoadingVisitor.from_mock_connection(
|
|
336
|
-
conn,
|
|
337
|
-
catalog_name=catalog_name,
|
|
338
|
-
schema_name=schema_name,
|
|
339
|
-
tap_tables=tap_tables,
|
|
340
|
-
tap_schema_index=tap_schema_index,
|
|
341
|
-
)
|
|
342
|
-
tap_visitor.visit_schema(schema)
|
|
343
|
-
|
|
344
|
-
|
|
345
186
|
@cli.command("load-tap-schema", help="Load metadata from a Felis file into a TAP_SCHEMA database")
|
|
346
187
|
@click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL")
|
|
347
|
-
@click.option("--tap-schema-name", help="Name of the TAP_SCHEMA schema in the database")
|
|
348
188
|
@click.option(
|
|
349
|
-
"--tap-
|
|
189
|
+
"--tap-schema-name", "-n", help="Name of the TAP_SCHEMA schema in the database (default: TAP_SCHEMA)"
|
|
190
|
+
)
|
|
191
|
+
@click.option(
|
|
192
|
+
"--tap-tables-postfix",
|
|
193
|
+
"-p",
|
|
194
|
+
help="Postfix which is applied to standard TAP_SCHEMA table names",
|
|
195
|
+
default="",
|
|
196
|
+
)
|
|
197
|
+
@click.option("--tap-schema-index", "-i", type=int, help="TAP_SCHEMA index of the schema in this environment")
|
|
198
|
+
@click.option("--dry-run", "-D", is_flag=True, help="Execute dry run only. Does not insert any data.")
|
|
199
|
+
@click.option("--echo", "-e", is_flag=True, help="Print out the generated insert statements to stdout")
|
|
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
|
|
207
|
+
@click.option(
|
|
208
|
+
"--unique-keys",
|
|
209
|
+
"-u",
|
|
210
|
+
is_flag=True,
|
|
211
|
+
help="Generate unique key_id values for keys and key_columns tables by prepending the schema name",
|
|
212
|
+
default=False,
|
|
350
213
|
)
|
|
351
|
-
@click.option("--tap-schema-index", type=int, help="TAP_SCHEMA index of the schema in this environment")
|
|
352
|
-
@click.option("--dry-run", is_flag=True, help="Execute dry run only. Does not insert any data.")
|
|
353
|
-
@click.option("--echo", is_flag=True, help="Print out the generated insert statements to stdout")
|
|
354
|
-
@click.option("--output-file", type=click.Path(), help="Write SQL commands to a file")
|
|
355
214
|
@click.argument("file", type=click.File())
|
|
356
215
|
@click.pass_context
|
|
357
216
|
def load_tap_schema(
|
|
@@ -363,6 +222,8 @@ def load_tap_schema(
|
|
|
363
222
|
dry_run: bool,
|
|
364
223
|
echo: bool,
|
|
365
224
|
output_file: str | None,
|
|
225
|
+
force_unbounded_arraysize: bool,
|
|
226
|
+
unique_keys: bool,
|
|
366
227
|
file: IO[str],
|
|
367
228
|
) -> None:
|
|
368
229
|
"""Load TAP metadata from a Felis file.
|
|
@@ -402,7 +263,13 @@ def load_tap_schema(
|
|
|
402
263
|
table_name_postfix=tap_tables_postfix,
|
|
403
264
|
)
|
|
404
265
|
|
|
405
|
-
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
|
+
)
|
|
406
273
|
|
|
407
274
|
DataLoader(
|
|
408
275
|
schema,
|
|
@@ -412,9 +279,57 @@ def load_tap_schema(
|
|
|
412
279
|
dry_run=dry_run,
|
|
413
280
|
print_sql=echo,
|
|
414
281
|
output_path=output_file,
|
|
282
|
+
unique_keys=unique_keys,
|
|
415
283
|
).load()
|
|
416
284
|
|
|
417
285
|
|
|
286
|
+
@cli.command("init-tap-schema", help="Initialize a standard TAP_SCHEMA database")
|
|
287
|
+
@click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL", required=True)
|
|
288
|
+
@click.option("--tap-schema-name", help="Name of the TAP_SCHEMA schema in the database")
|
|
289
|
+
@click.option(
|
|
290
|
+
"--tap-tables-postfix", help="Postfix which is applied to standard TAP_SCHEMA table names", default=""
|
|
291
|
+
)
|
|
292
|
+
@click.option(
|
|
293
|
+
"--insert-metadata/--no-insert-metadata",
|
|
294
|
+
is_flag=True,
|
|
295
|
+
help="Insert metadata describing TAP_SCHEMA itself",
|
|
296
|
+
default=True,
|
|
297
|
+
)
|
|
298
|
+
@click.pass_context
|
|
299
|
+
def init_tap_schema(
|
|
300
|
+
ctx: click.Context, engine_url: str, tap_schema_name: str, tap_tables_postfix: str, insert_metadata: bool
|
|
301
|
+
) -> None:
|
|
302
|
+
"""Initialize a standard TAP_SCHEMA database.
|
|
303
|
+
|
|
304
|
+
Parameters
|
|
305
|
+
----------
|
|
306
|
+
engine_url
|
|
307
|
+
SQLAlchemy Engine URL.
|
|
308
|
+
tap_schema_name
|
|
309
|
+
Name of the TAP_SCHEMA schema in the database.
|
|
310
|
+
tap_tables_postfix
|
|
311
|
+
Postfix which is applied to standard TAP_SCHEMA table names.
|
|
312
|
+
insert_metadata
|
|
313
|
+
Insert metadata describing TAP_SCHEMA itself.
|
|
314
|
+
If set to False, only the TAP_SCHEMA tables will be created, but no
|
|
315
|
+
metadata will be inserted.
|
|
316
|
+
"""
|
|
317
|
+
url = make_url(engine_url)
|
|
318
|
+
engine: Engine | MockConnection
|
|
319
|
+
if is_mock_url(url):
|
|
320
|
+
raise click.ClickException("Mock engine URL is not supported for this command")
|
|
321
|
+
engine = create_engine(engine_url)
|
|
322
|
+
mgr = TableManager(
|
|
323
|
+
apply_schema_to_metadata=False if engine.dialect.name == "sqlite" else True,
|
|
324
|
+
schema_name=tap_schema_name,
|
|
325
|
+
table_name_postfix=tap_tables_postfix,
|
|
326
|
+
)
|
|
327
|
+
mgr.initialize_database(engine)
|
|
328
|
+
if insert_metadata:
|
|
329
|
+
inserter = MetadataInserter(mgr, engine)
|
|
330
|
+
inserter.insert_metadata()
|
|
331
|
+
|
|
332
|
+
|
|
418
333
|
@cli.command("validate", help="Validate one or more Felis YAML files")
|
|
419
334
|
@click.option(
|
|
420
335
|
"--check-description", is_flag=True, help="Check that all objects have a description", default=False
|
|
@@ -493,5 +408,104 @@ def validate(
|
|
|
493
408
|
raise click.exceptions.Exit(rc)
|
|
494
409
|
|
|
495
410
|
|
|
411
|
+
@cli.command(
|
|
412
|
+
"diff",
|
|
413
|
+
help="""
|
|
414
|
+
Compare two schemas or a schema and a database for changes
|
|
415
|
+
|
|
416
|
+
Examples:
|
|
417
|
+
|
|
418
|
+
felis diff schema1.yaml schema2.yaml
|
|
419
|
+
|
|
420
|
+
felis diff -c alembic schema1.yaml schema2.yaml
|
|
421
|
+
|
|
422
|
+
felis diff --engine-url sqlite:///test.db schema.yaml
|
|
423
|
+
""",
|
|
424
|
+
)
|
|
425
|
+
@click.option("--engine-url", envvar="FELIS_ENGINE_URL", help="SQLAlchemy Engine URL")
|
|
426
|
+
@click.option(
|
|
427
|
+
"-c",
|
|
428
|
+
"--comparator",
|
|
429
|
+
type=click.Choice(["alembic", "deepdiff"], case_sensitive=False),
|
|
430
|
+
help="Comparator to use for schema comparison",
|
|
431
|
+
default="deepdiff",
|
|
432
|
+
)
|
|
433
|
+
@click.option("-E", "--error-on-change", is_flag=True, help="Exit with error code if schemas are different")
|
|
434
|
+
@click.argument("files", nargs=-1, type=click.File())
|
|
435
|
+
@click.pass_context
|
|
436
|
+
def diff(
|
|
437
|
+
ctx: click.Context,
|
|
438
|
+
engine_url: str | None,
|
|
439
|
+
comparator: str,
|
|
440
|
+
error_on_change: bool,
|
|
441
|
+
files: Iterable[IO[str]],
|
|
442
|
+
) -> None:
|
|
443
|
+
schemas = [
|
|
444
|
+
Schema.from_stream(file, context={"id_generation": ctx.obj["id_generation"]}) for file in files
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
diff: SchemaDiff
|
|
448
|
+
if len(schemas) == 2 and engine_url is None:
|
|
449
|
+
if comparator == "alembic":
|
|
450
|
+
db_context = create_database(schemas[0])
|
|
451
|
+
assert isinstance(db_context.engine, Engine)
|
|
452
|
+
diff = DatabaseDiff(schemas[1], db_context.engine)
|
|
453
|
+
else:
|
|
454
|
+
diff = FormattedSchemaDiff(schemas[0], schemas[1])
|
|
455
|
+
elif len(schemas) == 1 and engine_url is not None:
|
|
456
|
+
engine = create_engine(engine_url)
|
|
457
|
+
diff = DatabaseDiff(schemas[0], engine)
|
|
458
|
+
else:
|
|
459
|
+
raise click.ClickException(
|
|
460
|
+
"Invalid arguments - provide two schemas or a schema and a database engine URL"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
diff.print()
|
|
464
|
+
|
|
465
|
+
if diff.has_changes and error_on_change:
|
|
466
|
+
raise click.ClickException("Schema was changed")
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
@cli.command(
|
|
470
|
+
"dump",
|
|
471
|
+
help="""
|
|
472
|
+
Dump a schema file to YAML or JSON format
|
|
473
|
+
|
|
474
|
+
Example:
|
|
475
|
+
|
|
476
|
+
felis dump schema.yaml schema.json
|
|
477
|
+
|
|
478
|
+
felis dump schema.yaml schema_dump.yaml
|
|
479
|
+
""",
|
|
480
|
+
)
|
|
481
|
+
@click.option(
|
|
482
|
+
"--strip-ids/--no-strip-ids",
|
|
483
|
+
is_flag=True,
|
|
484
|
+
help="Strip IDs from the output schema",
|
|
485
|
+
default=False,
|
|
486
|
+
)
|
|
487
|
+
@click.argument("files", nargs=2, type=click.Path())
|
|
488
|
+
@click.pass_context
|
|
489
|
+
def dump(
|
|
490
|
+
ctx: click.Context,
|
|
491
|
+
strip_ids: bool,
|
|
492
|
+
files: list[str],
|
|
493
|
+
) -> None:
|
|
494
|
+
if strip_ids:
|
|
495
|
+
logger.info("Stripping IDs from the output schema")
|
|
496
|
+
if files[1].endswith(".json"):
|
|
497
|
+
format = "json"
|
|
498
|
+
elif files[1].endswith(".yaml"):
|
|
499
|
+
format = "yaml"
|
|
500
|
+
else:
|
|
501
|
+
raise click.ClickException("Output file must have a .json or .yaml extension")
|
|
502
|
+
schema = Schema.from_uri(files[0], context={"id_generation": ctx.obj["id_generation"]})
|
|
503
|
+
with open(files[1], "w") as f:
|
|
504
|
+
if format == "yaml":
|
|
505
|
+
schema.dump_yaml(f, strip_ids=strip_ids)
|
|
506
|
+
elif format == "json":
|
|
507
|
+
schema.dump_json(f, strip_ids=strip_ids)
|
|
508
|
+
|
|
509
|
+
|
|
496
510
|
if __name__ == "__main__":
|
|
497
511
|
cli()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
table_name,column_name,utype,ucd,unit,description,datatype,arraysize,xtype,size,principal,indexed,std,column_index
|
|
2
|
+
tap_schema.columns,"""size""",\N,\N,\N,deprecated: use arraysize,int,\N,\N,\N,1,0,1,9
|
|
3
|
+
tap_schema.columns,arraysize,\N,\N,\N,lists the size of variable-length columns in the tableset,char,16*,\N,16,1,0,1,8
|
|
4
|
+
tap_schema.columns,column_index,\N,\N,\N,recommended sort order when listing columns of a table,int,\N,\N,\N,1,0,1,13
|
|
5
|
+
tap_schema.columns,column_name,\N,\N,\N,the column name,char,64*,\N,64,1,0,1,2
|
|
6
|
+
tap_schema.columns,datatype,\N,\N,\N,lists the ADQL datatype of columns in the tableset,char,64*,\N,64,1,0,1,7
|
|
7
|
+
tap_schema.columns,description,\N,\N,\N,describes the columns in the tableset,char,512*,\N,512,1,0,1,6
|
|
8
|
+
tap_schema.columns,indexed,\N,\N,\N,"an indexed column; 1 means 1, 0 means 0",int,\N,\N,\N,1,0,1,11
|
|
9
|
+
tap_schema.columns,principal,\N,\N,\N,"a principal column; 1 means 1, 0 means 0",int,\N,\N,\N,1,0,1,10
|
|
10
|
+
tap_schema.columns,std,\N,\N,\N,"a standard column; 1 means 1, 0 means 0",int,\N,\N,\N,1,0,1,12
|
|
11
|
+
tap_schema.columns,table_name,\N,\N,\N,the table this column belongs to,char,64*,\N,64,1,0,1,1
|
|
12
|
+
tap_schema.columns,ucd,\N,\N,\N,lists the UCDs of columns in the tableset,char,64*,\N,64,1,0,1,4
|
|
13
|
+
tap_schema.columns,unit,\N,\N,\N,lists the unit used for column values in the tableset,char,64*,\N,64,1,0,1,5
|
|
14
|
+
tap_schema.columns,utype,\N,\N,\N,lists the utypes of columns in the tableset,char,512*,\N,512,1,0,1,3
|
|
15
|
+
tap_schema.columns,xtype,\N,\N,\N,a DALI or custom extended type annotation,char,64*,\N,64,1,0,1,7
|
|
16
|
+
tap_schema.key_columns,from_column,\N,\N,\N,column in the from_table,char,64*,\N,64,1,0,1,2
|
|
17
|
+
tap_schema.key_columns,key_id,\N,\N,\N,key to join to tap_schema.keys,char,64*,\N,64,1,0,1,1
|
|
18
|
+
tap_schema.key_columns,target_column,\N,\N,\N,column in the target_table,char,64*,\N,64,1,0,1,3
|
|
19
|
+
tap_schema.keys,description,\N,\N,\N,describes keys in the tableset,char,512*,\N,512,1,0,1,5
|
|
20
|
+
tap_schema.keys,from_table,\N,\N,\N,the table with the foreign key,char,64*,\N,64,1,0,1,2
|
|
21
|
+
tap_schema.keys,key_id,\N,\N,\N,unique key to join to tap_schema.key_columns,char,64*,\N,64,1,0,1,1
|
|
22
|
+
tap_schema.keys,target_table,\N,\N,\N,the table with the primary key,char,64*,\N,64,1,0,1,3
|
|
23
|
+
tap_schema.keys,utype,\N,\N,\N,lists the utype of keys in the tableset,char,512*,\N,512,1,0,1,4
|
|
24
|
+
tap_schema.schemas,description,\N,\N,\N,describes schemas in the tableset,char,512*,\N,512,1,0,1,3
|
|
25
|
+
tap_schema.schemas,schema_index,\N,\N,\N,recommended sort order when listing schemas,int,\N,\N,\N,1,0,1,4
|
|
26
|
+
tap_schema.schemas,schema_name,\N,\N,\N,schema name for reference to tap_schema.schemas,char,64*,\N,64,1,0,1,1
|
|
27
|
+
tap_schema.schemas,utype,\N,\N,\N,lists the utypes of schemas in the tableset,char,512*,\N,512,1,0,1,2
|
|
28
|
+
tap_schema.tables,description,\N,\N,\N,describes tables in the tableset,char,512*,\N,512,1,0,1,5
|
|
29
|
+
tap_schema.tables,schema_name,\N,\N,\N,the schema this table belongs to,char,512*,\N,512,1,0,1,1
|
|
30
|
+
tap_schema.tables,table_index,\N,\N,\N,recommended sort order when listing tables,int,\N,\N,\N,1,0,1,6
|
|
31
|
+
tap_schema.tables,table_name,\N,\N,\N,the fully qualified table name,char,64*,\N,64,1,0,1,2
|
|
32
|
+
tap_schema.tables,table_type,\N,\N,\N,one of: table view,char,8*,\N,8,1,0,1,3
|
|
33
|
+
tap_schema.tables,utype,\N,\N,\N,lists the utype of tables in the tableset,char,512*,\N,512,1,0,1,4
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
key_id,from_table,target_table,utype,description
|
|
2
|
+
k1,tap_schema.tables,tap_schema.schemas,\N,\N
|
|
3
|
+
k2,tap_schema.columns,tap_schema.tables,\N,\N
|
|
4
|
+
k3,tap_schema.keys,tap_schema.tables,\N,\N
|
|
5
|
+
k4,tap_schema.keys,tap_schema.tables,\N,\N
|
|
6
|
+
k5,tap_schema.key_columns,tap_schema.keys,\N,\N
|
|
7
|
+
k6,tap_schema.key_columns,tap_schema.columns,\N,\N
|
|
8
|
+
k7,tap_schema.key_columns,tap_schema.columns,\N,\N
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
schema_name,table_name,table_type,utype,description,table_index
|
|
2
|
+
tap_schema,tap_schema.columns,table,\N,description of columns in this tableset,102000
|
|
3
|
+
tap_schema,tap_schema.key_columns,table,\N,description of foreign key columns in this tableset,104000
|
|
4
|
+
tap_schema,tap_schema.keys,table,\N,description of foreign keys in this tableset,103000
|
|
5
|
+
tap_schema,tap_schema.schemas,table,\N,description of schemas in this tableset,100000
|
|
6
|
+
tap_schema,tap_schema.tables,table,\N,description of tables in this tableset,101000
|