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 CHANGED
@@ -1,3 +1,5 @@
1
+ """Click command line interface."""
2
+
1
3
  # This file is part of felis.
2
4
  #
3
5
  # Developed for the LSST Data Management System.
@@ -37,7 +39,8 @@ from .datamodel import Schema
37
39
  from .db.utils import DatabaseContext
38
40
  from .metadata import MetaDataBuilder
39
41
  from .tap import Tap11Base, TapLoadingVisitor, init_tables
40
- from .validation import get_schema
42
+
43
+ __all__ = ["cli"]
41
44
 
42
45
  logger = logging.getLogger("felis")
43
46
 
@@ -60,14 +63,14 @@ loglevel_choices = ["CRITICAL", "FATAL", "ERROR", "WARNING", "INFO", "DEBUG"]
60
63
  help="Felis log file path",
61
64
  )
62
65
  def cli(log_level: str, log_file: str | None) -> None:
63
- """Felis Command Line Tools."""
66
+ """Felis command line tools"""
64
67
  if log_file:
65
68
  logging.basicConfig(filename=log_file, level=log_level)
66
69
  else:
67
70
  logging.basicConfig(level=log_level)
68
71
 
69
72
 
70
- @cli.command("create")
73
+ @cli.command("create", help="Create database objects from the Felis file")
71
74
  @click.option("--engine-url", envvar="ENGINE_URL", help="SQLAlchemy Engine URL", default="sqlite://")
72
75
  @click.option("--schema-name", help="Alternate schema name to override Felis file")
73
76
  @click.option(
@@ -90,7 +93,34 @@ def create(
90
93
  output_file: IO[str] | None,
91
94
  file: IO,
92
95
  ) -> None:
93
- """Create database objects from the Felis file."""
96
+ """Create database objects from the Felis file.
97
+
98
+ Parameters
99
+ ----------
100
+ engine_url
101
+ SQLAlchemy Engine URL.
102
+ schema_name
103
+ Alternate schema name to override Felis file.
104
+ create_if_not_exists
105
+ Create the schema in the database if it does not exist.
106
+ drop_if_exists
107
+ Drop schema if it already exists in the database.
108
+ echo
109
+ Echo database commands as they are executed.
110
+ dry_run
111
+ Dry run only to print out commands instead of executing.
112
+ output_file
113
+ Write SQL commands to a file instead of executing.
114
+ file
115
+ Felis file to read.
116
+
117
+ Notes
118
+ -----
119
+ This command creates database objects from the Felis file. The
120
+ ``--create-if-not-exists`` or ``--drop-if-exists`` flags can be used to
121
+ create a new MySQL database or PostgreSQL schema if it does not exist
122
+ already.
123
+ """
94
124
  yaml_data = yaml.safe_load(file)
95
125
  schema = Schema.model_validate(yaml_data)
96
126
  url = make_url(engine_url)
@@ -131,13 +161,13 @@ def create(
131
161
  context.create_all()
132
162
 
133
163
 
134
- @cli.command("init-tap")
135
- @click.option("--tap-schema-name", help="Alt Schema Name for TAP_SCHEMA")
136
- @click.option("--tap-schemas-table", help="Alt Table Name for TAP_SCHEMA.schemas")
137
- @click.option("--tap-tables-table", help="Alt Table Name for TAP_SCHEMA.tables")
138
- @click.option("--tap-columns-table", help="Alt Table Name for TAP_SCHEMA.columns")
139
- @click.option("--tap-keys-table", help="Alt Table Name for TAP_SCHEMA.keys")
140
- @click.option("--tap-key-columns-table", help="Alt Table Name for TAP_SCHEMA.key_columns")
164
+ @cli.command("init-tap", help="Initialize TAP_SCHEMA objects in the database")
165
+ @click.option("--tap-schema-name", help="Alternate database schema name for 'TAP_SCHEMA'")
166
+ @click.option("--tap-schemas-table", help="Alternate table name for 'schemas'")
167
+ @click.option("--tap-tables-table", help="Alternate table name for 'tables'")
168
+ @click.option("--tap-columns-table", help="Alternate table name for 'columns'")
169
+ @click.option("--tap-keys-table", help="Alternate table name for 'keys'")
170
+ @click.option("--tap-key-columns-table", help="Alternate table name for 'key_columns'")
141
171
  @click.argument("engine-url")
142
172
  def init_tap(
143
173
  engine_url: str,
@@ -148,10 +178,31 @@ def init_tap(
148
178
  tap_keys_table: str,
149
179
  tap_key_columns_table: str,
150
180
  ) -> None:
151
- """Initialize TAP 1.1 TAP_SCHEMA objects.
152
-
153
- Please verify the schema/catalog you are executing this in in your
154
- engine URL.
181
+ """Initialize TAP_SCHEMA objects in the database.
182
+
183
+ Parameters
184
+ ----------
185
+ engine_url
186
+ SQLAlchemy Engine URL. The target PostgreSQL schema or MySQL database
187
+ must already exist and be referenced in the URL.
188
+ tap_schema_name
189
+ Alterate name for the database schema ``TAP_SCHEMA``.
190
+ tap_schemas_table
191
+ Alterate table name for ``schemas``.
192
+ tap_tables_table
193
+ Alterate table name for ``tables``.
194
+ tap_columns_table
195
+ Alterate table name for ``columns``.
196
+ tap_keys_table
197
+ Alterate table name for ``keys``.
198
+ tap_key_columns_table
199
+ Alterate table name for ``key_columns``.
200
+
201
+ Notes
202
+ -----
203
+ The supported version of TAP_SCHEMA in the SQLAlchemy metadata is 1.1. The
204
+ tables are created in the database schema specified by the engine URL,
205
+ which must be a PostgreSQL schema or MySQL database that already exists.
155
206
  """
156
207
  engine = create_engine(engine_url, echo=True)
157
208
  init_tables(
@@ -165,19 +216,19 @@ def init_tap(
165
216
  Tap11Base.metadata.create_all(engine)
166
217
 
167
218
 
168
- @cli.command("load-tap")
219
+ @cli.command("load-tap", help="Load metadata from a Felis file into a TAP_SCHEMA database")
169
220
  @click.option("--engine-url", envvar="ENGINE_URL", help="SQLAlchemy Engine URL to catalog")
170
221
  @click.option("--schema-name", help="Alternate Schema Name for Felis file")
171
222
  @click.option("--catalog-name", help="Catalog Name for Schema")
172
223
  @click.option("--dry-run", is_flag=True, help="Dry Run Only. Prints out the DDL that would be executed")
173
- @click.option("--tap-schema-name", help="Alt Schema Name for TAP_SCHEMA")
174
- @click.option("--tap-tables-postfix", help="Postfix for TAP table names")
175
- @click.option("--tap-schemas-table", help="Alt Table Name for TAP_SCHEMA.schemas")
176
- @click.option("--tap-tables-table", help="Alt Table Name for TAP_SCHEMA.tables")
177
- @click.option("--tap-columns-table", help="Alt Table Name for TAP_SCHEMA.columns")
178
- @click.option("--tap-keys-table", help="Alt Table Name for TAP_SCHEMA.keys")
179
- @click.option("--tap-key-columns-table", help="Alt Table Name for TAP_SCHEMA.key_columns")
180
- @click.option("--tap-schema-index", type=int, help="TAP_SCHEMA index of the schema")
224
+ @click.option("--tap-schema-name", help="Alternate schema name for 'TAP_SCHEMA'")
225
+ @click.option("--tap-tables-postfix", help="Postfix for TAP_SCHEMA table names")
226
+ @click.option("--tap-schemas-table", help="Alternate table name for 'schemas'")
227
+ @click.option("--tap-tables-table", help="Alternate table name for 'tables'")
228
+ @click.option("--tap-columns-table", help="Alternate table name for 'columns'")
229
+ @click.option("--tap-keys-table", help="Alternate table name for 'keys'")
230
+ @click.option("--tap-key-columns-table", help="Alternate table name for 'key_columns'")
231
+ @click.option("--tap-schema-index", type=int, help="TAP_SCHEMA index of the schema in this environment")
181
232
  @click.argument("file", type=click.File())
182
233
  def load_tap(
183
234
  engine_url: str,
@@ -194,10 +245,46 @@ def load_tap(
194
245
  tap_schema_index: int,
195
246
  file: io.TextIOBase,
196
247
  ) -> None:
197
- """Load TAP metadata from a Felis FILE.
198
-
199
- This command loads the associated TAP metadata from a Felis FILE
200
- to the TAP_SCHEMA tables.
248
+ """Load TAP metadata from a Felis file.
249
+
250
+ This command loads the associated TAP metadata from a Felis YAML file
251
+ into the TAP_SCHEMA tables.
252
+
253
+ Parameters
254
+ ----------
255
+ engine_url
256
+ SQLAlchemy Engine URL to catalog.
257
+ schema_name
258
+ Alternate schema name. This overrides the schema name in the
259
+ ``catalog`` field of the Felis file.
260
+ catalog_name
261
+ Catalog name for the schema. This possibly duplicates the
262
+ ``tap_schema_name`` argument (DM-44870).
263
+ dry_run
264
+ Dry run only to print out commands instead of executing.
265
+ tap_schema_name
266
+ Alternate name for the schema of TAP_SCHEMA in the database.
267
+ tap_tables_postfix
268
+ Postfix for TAP table names that will be automatically appended.
269
+ tap_schemas_table
270
+ Alternate table name for ``schemas``.
271
+ tap_tables_table
272
+ Alternate table name for ``tables``.
273
+ tap_columns_table
274
+ Alternate table name for ``columns``.
275
+ tap_keys_table
276
+ Alternate table name for ``keys``.
277
+ tap_key_columns_table
278
+ Alternate table name for ``key_columns``.
279
+ tap_schema_index
280
+ TAP_SCHEMA index of the schema in this TAP environment.
281
+ file
282
+ Felis file to read.
283
+
284
+ Notes
285
+ -----
286
+ The data will be loaded into the TAP_SCHEMA from the engine URL. The
287
+ tables must have already been initialized or an error will occur.
201
288
  """
202
289
  yaml_data = yaml.load(file, Loader=yaml.SafeLoader)
203
290
  schema = Schema.model_validate(yaml_data)
@@ -240,43 +327,73 @@ def load_tap(
240
327
  tap_visitor.visit_schema(schema)
241
328
 
242
329
 
243
- @cli.command("validate")
330
+ @cli.command("validate", help="Validate one or more Felis YAML files")
244
331
  @click.option(
245
- "-s",
246
- "--schema-name",
247
- help="Schema name for validation",
248
- type=click.Choice(["RSP", "default"]),
249
- default="default",
332
+ "--check-description", is_flag=True, help="Check that all objects have a description", default=False
250
333
  )
251
334
  @click.option(
252
- "-d", "--require-description", is_flag=True, help="Require description for all objects", default=False
335
+ "--check-redundant-datatypes", is_flag=True, help="Check for redundant datatype overrides", default=False
253
336
  )
254
337
  @click.option(
255
- "-t", "--check-redundant-datatypes", is_flag=True, help="Check for redundant datatypes", default=False
338
+ "--check-tap-table-indexes",
339
+ is_flag=True,
340
+ help="Check that every table has a unique TAP table index",
341
+ default=False,
342
+ )
343
+ @click.option(
344
+ "--check-tap-principal",
345
+ is_flag=True,
346
+ help="Check that at least one column per table is flagged as TAP principal",
347
+ default=False,
256
348
  )
257
349
  @click.argument("files", nargs=-1, type=click.File())
258
350
  def validate(
259
- schema_name: str,
260
- require_description: bool,
351
+ check_description: bool,
261
352
  check_redundant_datatypes: bool,
353
+ check_tap_table_indexes: bool,
354
+ check_tap_principal: bool,
262
355
  files: Iterable[io.TextIOBase],
263
356
  ) -> None:
264
- """Validate one or more felis YAML files."""
265
- schema_class = get_schema(schema_name)
266
- if schema_name != "default":
267
- logger.info(f"Using schema '{schema_class.__name__}'")
268
-
357
+ """Validate one or more felis YAML files.
358
+
359
+ Parameters
360
+ ----------
361
+ check_description
362
+ Check that all objects have a valid description.
363
+ check_redundant_datatypes
364
+ Check for redundant type overrides.
365
+ check_tap_table_indexes
366
+ Check that every table has a unique TAP table index.
367
+ check_tap_principal
368
+ Check that at least one column per table is flagged as TAP principal.
369
+ files
370
+ The Felis YAML files to validate.
371
+
372
+ Raises
373
+ ------
374
+ click.exceptions.Exit
375
+ If any validation errors are found. The ``ValidationError`` which is
376
+ thrown when a schema fails to validate will be logged as an error
377
+ message.
378
+
379
+ Notes
380
+ -----
381
+ All of the ``check`` flags are turned off by default and represent
382
+ optional validations controlled by the Pydantic context.
383
+ """
269
384
  rc = 0
270
385
  for file in files:
271
386
  file_name = getattr(file, "name", None)
272
387
  logger.info(f"Validating {file_name}")
273
388
  try:
274
389
  data = yaml.load(file, Loader=yaml.SafeLoader)
275
- schema_class.model_validate(
390
+ Schema.model_validate(
276
391
  data,
277
392
  context={
393
+ "check_description": check_description,
278
394
  "check_redundant_datatypes": check_redundant_datatypes,
279
- "require_description": require_description,
395
+ "check_tap_table_indexes": check_tap_table_indexes,
396
+ "check_tap_principal": check_tap_principal,
280
397
  },
281
398
  )
282
399
  except ValidationError as e: