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/__init__.py +0 -25
- felis/cli.py +23 -195
- felis/datamodel.py +59 -43
- felis/db/sqltypes.py +3 -3
- felis/metadata.py +21 -12
- felis/tap.py +67 -80
- felis/validation.py +1 -1
- felis/version.py +1 -1
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/METADATA +5 -3
- lsst_felis-27.2024.1800.dist-info/RECORD +20 -0
- felis/check.py +0 -381
- felis/simple.py +0 -424
- felis/utils.py +0 -100
- felis/visitor.py +0 -180
- lsst_felis-27.0.0rc3.dist-info/RECORD +0 -24
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/COPYRIGHT +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/LICENSE +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/WHEEL +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/entry_points.txt +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/top_level.txt +0 -0
- {lsst_felis-27.0.0rc3.dist-info → lsst_felis-27.2024.1800.dist-info}/zip-safe +0 -0
felis/simple.py
DELETED
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
# This file is part of felis.
|
|
2
|
-
#
|
|
3
|
-
# Developed for the LSST Data Management System.
|
|
4
|
-
# This product includes software developed by the LSST Project
|
|
5
|
-
# (https://www.lsst.org).
|
|
6
|
-
# See the COPYRIGHT file at the top-level directory of this distribution
|
|
7
|
-
# for details of code ownership.
|
|
8
|
-
#
|
|
9
|
-
# This program is free software: you can redistribute it and/or modify
|
|
10
|
-
# it under the terms of the GNU General Public License as published by
|
|
11
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
12
|
-
# (at your option) any later version.
|
|
13
|
-
#
|
|
14
|
-
# This program is distributed in the hope that it will be useful,
|
|
15
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
-
# GNU General Public License for more details.
|
|
18
|
-
#
|
|
19
|
-
# You should have received a copy of the GNU General Public License
|
|
20
|
-
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
-
|
|
22
|
-
from __future__ import annotations
|
|
23
|
-
|
|
24
|
-
__all__ = [
|
|
25
|
-
"CheckConstraint",
|
|
26
|
-
"Column",
|
|
27
|
-
"Constraint",
|
|
28
|
-
"ForeignKeyConstraint",
|
|
29
|
-
"Index",
|
|
30
|
-
"Schema",
|
|
31
|
-
"SchemaVersion",
|
|
32
|
-
"SimpleVisitor",
|
|
33
|
-
"Table",
|
|
34
|
-
"UniqueConstraint",
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
import dataclasses
|
|
38
|
-
import logging
|
|
39
|
-
from collections.abc import Iterable, Mapping, MutableMapping
|
|
40
|
-
from typing import Any, cast
|
|
41
|
-
|
|
42
|
-
from .check import FelisValidator
|
|
43
|
-
from .types import FelisType
|
|
44
|
-
from .visitor import Visitor
|
|
45
|
-
|
|
46
|
-
_Mapping = Mapping[str, Any]
|
|
47
|
-
|
|
48
|
-
logger = logging.getLogger("felis.generic")
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def _strip_keys(map: _Mapping, keys: Iterable[str]) -> _Mapping:
|
|
52
|
-
"""Return a copy of a dictionary with some keys removed."""
|
|
53
|
-
keys = set(keys)
|
|
54
|
-
return {key: value for key, value in map.items() if key not in keys}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _make_iterable(obj: str | Iterable[str]) -> Iterable[str]:
|
|
58
|
-
"""Make an iterable out of string or list of strings."""
|
|
59
|
-
if isinstance(obj, str):
|
|
60
|
-
yield obj
|
|
61
|
-
else:
|
|
62
|
-
yield from obj
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@dataclasses.dataclass
|
|
66
|
-
class Column:
|
|
67
|
-
"""Column representation in schema."""
|
|
68
|
-
|
|
69
|
-
name: str
|
|
70
|
-
"""Column name."""
|
|
71
|
-
|
|
72
|
-
id: str
|
|
73
|
-
"""Felis ID for this column."""
|
|
74
|
-
|
|
75
|
-
datatype: type[FelisType]
|
|
76
|
-
"""Column type, one of the types/classes defined in `types`."""
|
|
77
|
-
|
|
78
|
-
length: int | None = None
|
|
79
|
-
"""Optional length for string/binary columns"""
|
|
80
|
-
|
|
81
|
-
nullable: bool = True
|
|
82
|
-
"""True for nullable columns."""
|
|
83
|
-
|
|
84
|
-
value: Any = None
|
|
85
|
-
"""Default value for column, can be `None`."""
|
|
86
|
-
|
|
87
|
-
autoincrement: bool | None = None
|
|
88
|
-
"""Unspecified value results in `None`."""
|
|
89
|
-
|
|
90
|
-
description: str | None = None
|
|
91
|
-
"""Column description."""
|
|
92
|
-
|
|
93
|
-
annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
|
|
94
|
-
"""Additional annotations for this column."""
|
|
95
|
-
|
|
96
|
-
table: Table | None = None
|
|
97
|
-
"""Table which defines this column, usually not `None`."""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
@dataclasses.dataclass
|
|
101
|
-
class Index:
|
|
102
|
-
"""Index representation."""
|
|
103
|
-
|
|
104
|
-
name: str
|
|
105
|
-
"""index name, can be empty."""
|
|
106
|
-
|
|
107
|
-
id: str
|
|
108
|
-
"""Felis ID for this index."""
|
|
109
|
-
|
|
110
|
-
columns: list[Column] = dataclasses.field(default_factory=list)
|
|
111
|
-
"""List of columns in index, one of the ``columns`` or ``expressions``
|
|
112
|
-
must be non-empty.
|
|
113
|
-
"""
|
|
114
|
-
|
|
115
|
-
expressions: list[str] = dataclasses.field(default_factory=list)
|
|
116
|
-
"""List of expressions in index, one of the ``columns`` or ``expressions``
|
|
117
|
-
must be non-empty.
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
description: str | None = None
|
|
121
|
-
"""Index description."""
|
|
122
|
-
|
|
123
|
-
annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
|
|
124
|
-
"""Additional annotations for this index."""
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
@dataclasses.dataclass
|
|
128
|
-
class Constraint:
|
|
129
|
-
"""Constraint description, this is a base class, actual constraints will be
|
|
130
|
-
instances of one of the subclasses.
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
name: str | None
|
|
134
|
-
"""Constraint name."""
|
|
135
|
-
|
|
136
|
-
id: str
|
|
137
|
-
"""Felis ID for this constraint."""
|
|
138
|
-
|
|
139
|
-
deferrable: bool = False
|
|
140
|
-
"""If `True` then this constraint will be declared as deferrable."""
|
|
141
|
-
|
|
142
|
-
initially: str | None = None
|
|
143
|
-
"""Value for ``INITIALLY`` clause, only used of ``deferrable`` is True."""
|
|
144
|
-
|
|
145
|
-
description: str | None = None
|
|
146
|
-
"""Constraint description."""
|
|
147
|
-
|
|
148
|
-
annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
|
|
149
|
-
"""Additional annotations for this constraint."""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
@dataclasses.dataclass
|
|
153
|
-
class UniqueConstraint(Constraint):
|
|
154
|
-
"""Description of unique constraint."""
|
|
155
|
-
|
|
156
|
-
columns: list[Column] = dataclasses.field(default_factory=list)
|
|
157
|
-
"""List of columns in this constraint, all columns belong to the same table
|
|
158
|
-
as the constraint itself.
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
@dataclasses.dataclass
|
|
163
|
-
class ForeignKeyConstraint(Constraint):
|
|
164
|
-
"""Description of foreign key constraint."""
|
|
165
|
-
|
|
166
|
-
columns: list[Column] = dataclasses.field(default_factory=list)
|
|
167
|
-
"""List of columns in this constraint, all columns belong to the same table
|
|
168
|
-
as the constraint itself.
|
|
169
|
-
"""
|
|
170
|
-
|
|
171
|
-
referenced_columns: list[Column] = dataclasses.field(default_factory=list)
|
|
172
|
-
"""List of referenced columns, the number of columns must be the same as in
|
|
173
|
-
``Constraint.columns`` list. All columns must belong to the same table,
|
|
174
|
-
which is different from the table of this constraint.
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
@dataclasses.dataclass
|
|
179
|
-
class CheckConstraint(Constraint):
|
|
180
|
-
"""Description of check constraint."""
|
|
181
|
-
|
|
182
|
-
expression: str = ""
|
|
183
|
-
"""Expression on one or more columns on the table, must be non-empty."""
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
@dataclasses.dataclass
|
|
187
|
-
class Table:
|
|
188
|
-
"""Description of a single table schema."""
|
|
189
|
-
|
|
190
|
-
name: str
|
|
191
|
-
"""Table name."""
|
|
192
|
-
|
|
193
|
-
id: str
|
|
194
|
-
"""Felis ID for this table."""
|
|
195
|
-
|
|
196
|
-
columns: list[Column]
|
|
197
|
-
"""List of Column instances."""
|
|
198
|
-
|
|
199
|
-
primary_key: list[Column]
|
|
200
|
-
"""List of Column that constitute a primary key, may be empty."""
|
|
201
|
-
|
|
202
|
-
constraints: list[Constraint]
|
|
203
|
-
"""List of Constraint instances, can be empty."""
|
|
204
|
-
|
|
205
|
-
indexes: list[Index]
|
|
206
|
-
"""List of Index instances, can be empty."""
|
|
207
|
-
|
|
208
|
-
description: str | None = None
|
|
209
|
-
"""Table description."""
|
|
210
|
-
|
|
211
|
-
annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
|
|
212
|
-
"""Additional annotations for this table."""
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
@dataclasses.dataclass
|
|
216
|
-
class SchemaVersion:
|
|
217
|
-
"""Schema versioning description."""
|
|
218
|
-
|
|
219
|
-
current: str
|
|
220
|
-
"""Current schema version defined by the document."""
|
|
221
|
-
|
|
222
|
-
compatible: list[str] | None = None
|
|
223
|
-
"""Optional list of versions which are compatible with current version."""
|
|
224
|
-
|
|
225
|
-
read_compatible: list[str] | None = None
|
|
226
|
-
"""Optional list of versions with which current version is read-compatible.
|
|
227
|
-
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
@dataclasses.dataclass
|
|
231
|
-
class Schema:
|
|
232
|
-
"""Complete schema description, collection of tables."""
|
|
233
|
-
|
|
234
|
-
name: str
|
|
235
|
-
"""Schema name."""
|
|
236
|
-
|
|
237
|
-
id: str
|
|
238
|
-
"""Felis ID for this schema."""
|
|
239
|
-
|
|
240
|
-
tables: list[Table]
|
|
241
|
-
"""Collection of table definitions."""
|
|
242
|
-
|
|
243
|
-
version: SchemaVersion | None = None
|
|
244
|
-
"""Schema version description."""
|
|
245
|
-
|
|
246
|
-
description: str | None = None
|
|
247
|
-
"""Schema description."""
|
|
248
|
-
|
|
249
|
-
annotations: Mapping[str, Any] = dataclasses.field(default_factory=dict)
|
|
250
|
-
"""Additional annotations for this table."""
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
class SimpleVisitor(Visitor[Schema, Table, Column, list[Column], Constraint, Index, SchemaVersion]):
|
|
254
|
-
"""Visitor implementation class that produces a simple in-memory
|
|
255
|
-
representation of Felis schema using classes `Schema`, `Table`, etc. from
|
|
256
|
-
this module.
|
|
257
|
-
|
|
258
|
-
Notes
|
|
259
|
-
-----
|
|
260
|
-
Implementation of this visitor class uses `FelisValidator` to validate the
|
|
261
|
-
contents of the schema. All visit methods can raise the same exceptions as
|
|
262
|
-
corresponding `FelisValidator` methods (usually `ValueError`).
|
|
263
|
-
"""
|
|
264
|
-
|
|
265
|
-
def __init__(self) -> None:
|
|
266
|
-
self.checker = FelisValidator()
|
|
267
|
-
self.column_ids: MutableMapping[str, Column] = {}
|
|
268
|
-
|
|
269
|
-
def visit_schema(self, schema_obj: _Mapping) -> Schema:
|
|
270
|
-
# Docstring is inherited.
|
|
271
|
-
self.checker.check_schema(schema_obj)
|
|
272
|
-
|
|
273
|
-
version_obj = schema_obj.get("version")
|
|
274
|
-
|
|
275
|
-
schema = Schema(
|
|
276
|
-
name=schema_obj["name"],
|
|
277
|
-
id=schema_obj["@id"],
|
|
278
|
-
tables=[self.visit_table(t, schema_obj) for t in schema_obj["tables"]],
|
|
279
|
-
version=self.visit_schema_version(version_obj, schema_obj) if version_obj is not None else None,
|
|
280
|
-
description=schema_obj.get("description"),
|
|
281
|
-
annotations=_strip_keys(schema_obj, ["name", "@id", "tables", "description"]),
|
|
282
|
-
)
|
|
283
|
-
return schema
|
|
284
|
-
|
|
285
|
-
def visit_schema_version(
|
|
286
|
-
self, version_obj: str | Mapping[str, Any], schema_obj: Mapping[str, Any]
|
|
287
|
-
) -> SchemaVersion:
|
|
288
|
-
# Docstring is inherited.
|
|
289
|
-
self.checker.check_schema_version(version_obj, schema_obj)
|
|
290
|
-
|
|
291
|
-
if isinstance(version_obj, str):
|
|
292
|
-
return SchemaVersion(current=version_obj)
|
|
293
|
-
else:
|
|
294
|
-
return SchemaVersion(
|
|
295
|
-
current=cast(str, version_obj["current"]),
|
|
296
|
-
compatible=version_obj.get("compatible"),
|
|
297
|
-
read_compatible=version_obj.get("read_compatible"),
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
def visit_table(self, table_obj: _Mapping, schema_obj: _Mapping) -> Table:
|
|
301
|
-
# Docstring is inherited.
|
|
302
|
-
self.checker.check_table(table_obj, schema_obj)
|
|
303
|
-
|
|
304
|
-
columns = [self.visit_column(c, table_obj) for c in table_obj["columns"]]
|
|
305
|
-
table = Table(
|
|
306
|
-
name=table_obj["name"],
|
|
307
|
-
id=table_obj["@id"],
|
|
308
|
-
columns=columns,
|
|
309
|
-
primary_key=self.visit_primary_key(table_obj.get("primaryKey", []), table_obj),
|
|
310
|
-
constraints=[self.visit_constraint(c, table_obj) for c in table_obj.get("constraints", [])],
|
|
311
|
-
indexes=[self.visit_index(i, table_obj) for i in table_obj.get("indexes", [])],
|
|
312
|
-
description=table_obj.get("description"),
|
|
313
|
-
annotations=_strip_keys(
|
|
314
|
-
table_obj, ["name", "@id", "columns", "primaryKey", "constraints", "indexes", "description"]
|
|
315
|
-
),
|
|
316
|
-
)
|
|
317
|
-
for column in columns:
|
|
318
|
-
column.table = table
|
|
319
|
-
return table
|
|
320
|
-
|
|
321
|
-
def visit_column(self, column_obj: _Mapping, table_obj: _Mapping) -> Column:
|
|
322
|
-
# Docstring is inherited.
|
|
323
|
-
self.checker.check_column(column_obj, table_obj)
|
|
324
|
-
|
|
325
|
-
datatype = FelisType.felis_type(column_obj["datatype"])
|
|
326
|
-
|
|
327
|
-
column = Column(
|
|
328
|
-
name=column_obj["name"],
|
|
329
|
-
id=column_obj["@id"],
|
|
330
|
-
datatype=datatype,
|
|
331
|
-
length=column_obj.get("length"),
|
|
332
|
-
value=column_obj.get("value"),
|
|
333
|
-
description=column_obj.get("description"),
|
|
334
|
-
nullable=column_obj.get("nullable", True),
|
|
335
|
-
autoincrement=column_obj.get("autoincrement"),
|
|
336
|
-
annotations=_strip_keys(
|
|
337
|
-
column_obj,
|
|
338
|
-
["name", "@id", "datatype", "length", "nullable", "value", "autoincrement", "description"],
|
|
339
|
-
),
|
|
340
|
-
)
|
|
341
|
-
if column.id in self.column_ids:
|
|
342
|
-
logger.warning(f"Duplication of @id {column.id}")
|
|
343
|
-
self.column_ids[column.id] = column
|
|
344
|
-
return column
|
|
345
|
-
|
|
346
|
-
def visit_primary_key(self, primary_key_obj: str | Iterable[str], table_obj: _Mapping) -> list[Column]:
|
|
347
|
-
# Docstring is inherited.
|
|
348
|
-
self.checker.check_primary_key(primary_key_obj, table_obj)
|
|
349
|
-
if primary_key_obj:
|
|
350
|
-
columns = [self.column_ids[c_id] for c_id in _make_iterable(primary_key_obj)]
|
|
351
|
-
return columns
|
|
352
|
-
return []
|
|
353
|
-
|
|
354
|
-
def visit_constraint(self, constraint_obj: _Mapping, table_obj: _Mapping) -> Constraint:
|
|
355
|
-
# Docstring is inherited.
|
|
356
|
-
self.checker.check_constraint(constraint_obj, table_obj)
|
|
357
|
-
|
|
358
|
-
constraint_type = constraint_obj["@type"]
|
|
359
|
-
if constraint_type == "Unique":
|
|
360
|
-
return UniqueConstraint(
|
|
361
|
-
name=constraint_obj.get("name"),
|
|
362
|
-
id=constraint_obj["@id"],
|
|
363
|
-
columns=[self.column_ids[c_id] for c_id in _make_iterable(constraint_obj["columns"])],
|
|
364
|
-
deferrable=constraint_obj.get("deferrable", False),
|
|
365
|
-
initially=constraint_obj.get("initially"),
|
|
366
|
-
description=constraint_obj.get("description"),
|
|
367
|
-
annotations=_strip_keys(
|
|
368
|
-
constraint_obj,
|
|
369
|
-
["name", "@type", "@id", "columns", "deferrable", "initially", "description"],
|
|
370
|
-
),
|
|
371
|
-
)
|
|
372
|
-
elif constraint_type == "ForeignKey":
|
|
373
|
-
return ForeignKeyConstraint(
|
|
374
|
-
name=constraint_obj.get("name"),
|
|
375
|
-
id=constraint_obj["@id"],
|
|
376
|
-
columns=[self.column_ids[c_id] for c_id in _make_iterable(constraint_obj["columns"])],
|
|
377
|
-
referenced_columns=[
|
|
378
|
-
self.column_ids[c_id] for c_id in _make_iterable(constraint_obj["referencedColumns"])
|
|
379
|
-
],
|
|
380
|
-
deferrable=constraint_obj.get("deferrable", False),
|
|
381
|
-
initially=constraint_obj.get("initially"),
|
|
382
|
-
description=constraint_obj.get("description"),
|
|
383
|
-
annotations=_strip_keys(
|
|
384
|
-
constraint_obj,
|
|
385
|
-
[
|
|
386
|
-
"name",
|
|
387
|
-
"@id",
|
|
388
|
-
"@type",
|
|
389
|
-
"columns",
|
|
390
|
-
"deferrable",
|
|
391
|
-
"initially",
|
|
392
|
-
"referencedColumns",
|
|
393
|
-
"description",
|
|
394
|
-
],
|
|
395
|
-
),
|
|
396
|
-
)
|
|
397
|
-
elif constraint_type == "Check":
|
|
398
|
-
return CheckConstraint(
|
|
399
|
-
name=constraint_obj.get("name"),
|
|
400
|
-
id=constraint_obj["@id"],
|
|
401
|
-
expression=constraint_obj["expression"],
|
|
402
|
-
deferrable=constraint_obj.get("deferrable", False),
|
|
403
|
-
initially=constraint_obj.get("initially"),
|
|
404
|
-
description=constraint_obj.get("description"),
|
|
405
|
-
annotations=_strip_keys(
|
|
406
|
-
constraint_obj,
|
|
407
|
-
["name", "@id", "@type", "expression", "deferrable", "initially", "description"],
|
|
408
|
-
),
|
|
409
|
-
)
|
|
410
|
-
else:
|
|
411
|
-
raise ValueError(f"Unexpected constrint type: {constraint_type}")
|
|
412
|
-
|
|
413
|
-
def visit_index(self, index_obj: _Mapping, table_obj: _Mapping) -> Index:
|
|
414
|
-
# Docstring is inherited.
|
|
415
|
-
self.checker.check_index(index_obj, table_obj)
|
|
416
|
-
|
|
417
|
-
return Index(
|
|
418
|
-
name=index_obj["name"],
|
|
419
|
-
id=index_obj["@id"],
|
|
420
|
-
columns=[self.column_ids[c_id] for c_id in _make_iterable(index_obj.get("columns", []))],
|
|
421
|
-
expressions=index_obj.get("expressions", []),
|
|
422
|
-
description=index_obj.get("description"),
|
|
423
|
-
annotations=_strip_keys(index_obj, ["name", "@id", "columns", "expressions", "description"]),
|
|
424
|
-
)
|
felis/utils.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# This file is part of felis.
|
|
2
|
-
#
|
|
3
|
-
# Developed for the LSST Data Management System.
|
|
4
|
-
# This product includes software developed by the LSST Project
|
|
5
|
-
# (https://www.lsst.org).
|
|
6
|
-
# See the COPYRIGHT file at the top-level directory of this distribution
|
|
7
|
-
# for details of code ownership.
|
|
8
|
-
#
|
|
9
|
-
# This program is free software: you can redistribute it and/or modify
|
|
10
|
-
# it under the terms of the GNU General Public License as published by
|
|
11
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
12
|
-
# (at your option) any later version.
|
|
13
|
-
#
|
|
14
|
-
# This program is distributed in the hope that it will be useful,
|
|
15
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
-
# GNU General Public License for more details.
|
|
18
|
-
#
|
|
19
|
-
# You should have received a copy of the GNU General Public License
|
|
20
|
-
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
|
-
|
|
22
|
-
from __future__ import annotations
|
|
23
|
-
|
|
24
|
-
from collections.abc import Iterable, Mapping, MutableMapping
|
|
25
|
-
from typing import Any
|
|
26
|
-
|
|
27
|
-
_Mapping = Mapping[str, Any]
|
|
28
|
-
_MutableMapping = MutableMapping[str, Any]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class ReorderingVisitor:
|
|
32
|
-
"""A visitor that reorders and optionally adds the "@type".
|
|
33
|
-
|
|
34
|
-
Parameters
|
|
35
|
-
----------
|
|
36
|
-
add_type : `bool`
|
|
37
|
-
If true, add the "@type" if it doesn't exist.
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
def __init__(self, add_type: bool = False):
|
|
41
|
-
self.add_type = add_type
|
|
42
|
-
|
|
43
|
-
def visit_schema(self, schema_obj: _MutableMapping) -> _Mapping:
|
|
44
|
-
"""Process schema, the input MUST be a normalized representation."""
|
|
45
|
-
# Override with default
|
|
46
|
-
tables = [self.visit_table(table_obj, schema_obj) for table_obj in schema_obj["tables"]]
|
|
47
|
-
schema_obj["tables"] = tables
|
|
48
|
-
if self.add_type:
|
|
49
|
-
schema_obj["@type"] = schema_obj.get("@type", "Schema")
|
|
50
|
-
return _new_order(
|
|
51
|
-
schema_obj, ["@context", "name", "@id", "@type", "description", "tables", "version"]
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
def visit_table(self, table_obj: _MutableMapping, schema_obj: _Mapping) -> _Mapping:
|
|
55
|
-
columns = [self.visit_column(c, table_obj) for c in table_obj["columns"]]
|
|
56
|
-
primary_key = self.visit_primary_key(table_obj.get("primaryKey", []), table_obj)
|
|
57
|
-
constraints = [self.visit_constraint(c, table_obj) for c in table_obj.get("constraints", [])]
|
|
58
|
-
indexes = [self.visit_index(i, table_obj) for i in table_obj.get("indexes", [])]
|
|
59
|
-
table_obj["columns"] = columns
|
|
60
|
-
if primary_key:
|
|
61
|
-
table_obj["primaryKey"] = primary_key
|
|
62
|
-
if constraints:
|
|
63
|
-
table_obj["constraints"] = constraints
|
|
64
|
-
if indexes:
|
|
65
|
-
table_obj["indexes"] = indexes
|
|
66
|
-
if self.add_type:
|
|
67
|
-
table_obj["@type"] = table_obj.get("@type", "Table")
|
|
68
|
-
return _new_order(
|
|
69
|
-
table_obj,
|
|
70
|
-
["name", "@id", "@type", "description", "columns", "primaryKey", "constraints", "indexes"],
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
def visit_column(self, column_obj: _MutableMapping, table_obj: _Mapping) -> _Mapping:
|
|
74
|
-
if self.add_type:
|
|
75
|
-
column_obj["@type"] = column_obj.get("@type", "Column")
|
|
76
|
-
return _new_order(column_obj, ["name", "@id", "@type", "description", "datatype"])
|
|
77
|
-
|
|
78
|
-
def visit_primary_key(self, primary_key_obj: _MutableMapping, table: _Mapping) -> _Mapping:
|
|
79
|
-
# FIXME: Handle Primary Keys
|
|
80
|
-
return primary_key_obj
|
|
81
|
-
|
|
82
|
-
def visit_constraint(self, constraint_obj: _MutableMapping, table: _Mapping) -> _Mapping:
|
|
83
|
-
# Type MUST be present... we can skip
|
|
84
|
-
return _new_order(constraint_obj, ["name", "@id", "@type", "description"])
|
|
85
|
-
|
|
86
|
-
def visit_index(self, index_obj: _MutableMapping, table: _Mapping) -> _Mapping:
|
|
87
|
-
if self.add_type:
|
|
88
|
-
index_obj["@type"] = index_obj.get("@type", "Index")
|
|
89
|
-
return _new_order(index_obj, ["name", "@id", "@type", "description"])
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def _new_order(obj: _Mapping, order: Iterable[str]) -> _Mapping:
|
|
93
|
-
reordered_object: _MutableMapping = {}
|
|
94
|
-
for name in order:
|
|
95
|
-
if name in obj:
|
|
96
|
-
reordered_object[name] = obj[name]
|
|
97
|
-
for key, value in obj.items():
|
|
98
|
-
if key not in reordered_object:
|
|
99
|
-
reordered_object[key] = value
|
|
100
|
-
return reordered_object
|