lsst-felis 26.2024.900__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.
Files changed (39) hide show
  1. felis/__init__.py +10 -24
  2. felis/cli.py +437 -341
  3. felis/config/tap_schema/columns.csv +33 -0
  4. felis/config/tap_schema/key_columns.csv +8 -0
  5. felis/config/tap_schema/keys.csv +8 -0
  6. felis/config/tap_schema/schemas.csv +2 -0
  7. felis/config/tap_schema/tables.csv +6 -0
  8. felis/config/tap_schema/tap_schema_std.yaml +273 -0
  9. felis/datamodel.py +1386 -193
  10. felis/db/dialects.py +116 -0
  11. felis/db/schema.py +62 -0
  12. felis/db/sqltypes.py +275 -48
  13. felis/db/utils.py +409 -0
  14. felis/db/variants.py +159 -0
  15. felis/diff.py +234 -0
  16. felis/metadata.py +385 -0
  17. felis/tap_schema.py +767 -0
  18. felis/tests/__init__.py +0 -0
  19. felis/tests/postgresql.py +134 -0
  20. felis/tests/run_cli.py +79 -0
  21. felis/types.py +57 -9
  22. lsst_felis-29.2025.4500.dist-info/METADATA +38 -0
  23. lsst_felis-29.2025.4500.dist-info/RECORD +31 -0
  24. {lsst_felis-26.2024.900.dist-info → lsst_felis-29.2025.4500.dist-info}/WHEEL +1 -1
  25. {lsst_felis-26.2024.900.dist-info → lsst_felis-29.2025.4500.dist-info/licenses}/COPYRIGHT +1 -1
  26. felis/check.py +0 -381
  27. felis/simple.py +0 -424
  28. felis/sql.py +0 -275
  29. felis/tap.py +0 -433
  30. felis/utils.py +0 -100
  31. felis/validation.py +0 -103
  32. felis/version.py +0 -2
  33. felis/visitor.py +0 -180
  34. lsst_felis-26.2024.900.dist-info/METADATA +0 -28
  35. lsst_felis-26.2024.900.dist-info/RECORD +0 -23
  36. {lsst_felis-26.2024.900.dist-info → lsst_felis-29.2025.4500.dist-info}/entry_points.txt +0 -0
  37. {lsst_felis-26.2024.900.dist-info → lsst_felis-29.2025.4500.dist-info/licenses}/LICENSE +0 -0
  38. {lsst_felis-26.2024.900.dist-info → lsst_felis-29.2025.4500.dist-info}/top_level.txt +0 -0
  39. {lsst_felis-26.2024.900.dist-info → lsst_felis-29.2025.4500.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/sql.py DELETED
@@ -1,275 +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__ = ["SQLVisitor"]
25
-
26
- import logging
27
- import re
28
- from collections.abc import Iterable, Mapping, MutableMapping
29
- from typing import Any, NamedTuple
30
-
31
- from sqlalchemy import (
32
- CheckConstraint,
33
- Column,
34
- Constraint,
35
- ForeignKeyConstraint,
36
- Index,
37
- MetaData,
38
- Numeric,
39
- PrimaryKeyConstraint,
40
- UniqueConstraint,
41
- types,
42
- )
43
- from sqlalchemy.dialects import mysql, oracle, postgresql, sqlite
44
- from sqlalchemy.schema import Table
45
-
46
- from .check import FelisValidator
47
- from .db import sqltypes
48
- from .types import FelisType
49
- from .visitor import Visitor
50
-
51
- _Mapping = Mapping[str, Any]
52
- _MutableMapping = MutableMapping[str, Any]
53
-
54
- logger = logging.getLogger("felis")
55
-
56
- MYSQL = "mysql"
57
- ORACLE = "oracle"
58
- POSTGRES = "postgresql"
59
- SQLITE = "sqlite"
60
-
61
- TABLE_OPTS = {
62
- "mysql:engine": "mysql_engine",
63
- "mysql:charset": "mysql_charset",
64
- "oracle:compress": "oracle_compress",
65
- }
66
-
67
- COLUMN_VARIANT_OVERRIDE = {
68
- "mysql:datatype": "mysql",
69
- "oracle:datatype": "oracle",
70
- "postgresql:datatype": "postgresql",
71
- "sqlite:datatype": "sqlite",
72
- }
73
-
74
- DIALECT_MODULES = {MYSQL: mysql, ORACLE: oracle, SQLITE: sqlite, POSTGRES: postgresql}
75
-
76
- length_regex = re.compile(r"\((.+)\)")
77
-
78
-
79
- class Schema(NamedTuple):
80
- name: str | None
81
- tables: list[Table]
82
- metadata: MetaData
83
- graph_index: Mapping[str, Any]
84
-
85
-
86
- class SQLVisitor(Visitor[Schema, Table, Column, PrimaryKeyConstraint | None, Constraint, Index, None]):
87
- """A Felis Visitor which populates a SQLAlchemy metadata object.
88
-
89
- Parameters
90
- ----------
91
- schema_name : `str`, optional
92
- Override for the schema name.
93
- """
94
-
95
- def __init__(self, schema_name: str | None = None):
96
- self.metadata = MetaData()
97
- self.schema_name = schema_name
98
- self.checker = FelisValidator()
99
- self.graph_index: MutableMapping[str, Any] = {}
100
-
101
- def visit_schema(self, schema_obj: _Mapping) -> Schema:
102
- # Docstring is inherited.
103
- self.checker.check_schema(schema_obj)
104
- if (version_obj := schema_obj.get("version")) is not None:
105
- self.visit_schema_version(version_obj, schema_obj)
106
-
107
- # Create tables but don't add constraints yet.
108
- tables = [self.visit_table(t, schema_obj) for t in schema_obj["tables"]]
109
-
110
- # Process constraints after the tables are created so that all
111
- # referenced columns are available.
112
- for table_obj in schema_obj["tables"]:
113
- constraints = [
114
- self.visit_constraint(constraint, table_obj)
115
- for constraint in table_obj.get("constraints", [])
116
- ]
117
- table = self.graph_index[table_obj["@id"]]
118
- for constraint in constraints:
119
- table.append_constraint(constraint)
120
-
121
- schema = Schema(
122
- name=self.schema_name or schema_obj["name"],
123
- tables=tables,
124
- metadata=self.metadata,
125
- graph_index=self.graph_index,
126
- )
127
- return schema
128
-
129
- def visit_schema_version(
130
- self, version_obj: str | Mapping[str, Any], schema_obj: Mapping[str, Any]
131
- ) -> None:
132
- # Docstring is inherited.
133
-
134
- # For now we ignore schema versioning completely, still do some checks.
135
- self.checker.check_schema_version(version_obj, schema_obj)
136
-
137
- def visit_table(self, table_obj: _Mapping, schema_obj: _Mapping) -> Table:
138
- # Docstring is inherited.
139
- self.checker.check_table(table_obj, schema_obj)
140
- columns = [self.visit_column(c, table_obj) for c in table_obj["columns"]]
141
-
142
- name = table_obj["name"]
143
- table_id = table_obj["@id"]
144
- description = table_obj.get("description")
145
- schema_name = self.schema_name or schema_obj["name"]
146
-
147
- table = Table(name, self.metadata, *columns, schema=schema_name, comment=description)
148
-
149
- primary_key = self.visit_primary_key(table_obj.get("primaryKey", []), table_obj)
150
- if primary_key:
151
- table.append_constraint(primary_key)
152
-
153
- indexes = [self.visit_index(i, table_obj) for i in table_obj.get("indexes", [])]
154
- for index in indexes:
155
- # FIXME: Hack because there's no table.add_index
156
- index._set_parent(table)
157
- table.indexes.add(index)
158
- self.graph_index[table_id] = table
159
- return table
160
-
161
- def visit_column(self, column_obj: _Mapping, table_obj: _Mapping) -> Column:
162
- # Docstring is inherited.
163
- self.checker.check_column(column_obj, table_obj)
164
- column_name = column_obj["name"]
165
- column_id = column_obj["@id"]
166
- datatype_name = column_obj["datatype"]
167
- column_description = column_obj.get("description")
168
- column_default = column_obj.get("value")
169
- column_length = column_obj.get("length")
170
-
171
- kwargs = {}
172
- for column_opt in column_obj.keys():
173
- if column_opt in COLUMN_VARIANT_OVERRIDE:
174
- dialect = COLUMN_VARIANT_OVERRIDE[column_opt]
175
- variant = _process_variant_override(dialect, column_obj[column_opt])
176
- kwargs[dialect] = variant
177
-
178
- felis_type = FelisType.felis_type(datatype_name)
179
- datatype_fun = getattr(sqltypes, datatype_name)
180
-
181
- if felis_type.is_sized:
182
- datatype = datatype_fun(column_length, **kwargs)
183
- else:
184
- datatype = datatype_fun(**kwargs)
185
-
186
- nullable_default = True
187
- if isinstance(datatype, Numeric):
188
- nullable_default = False
189
-
190
- column_nullable = column_obj.get("nullable", nullable_default)
191
- column_autoincrement = column_obj.get("autoincrement", "auto")
192
-
193
- column: Column = Column(
194
- column_name,
195
- datatype,
196
- comment=column_description,
197
- autoincrement=column_autoincrement,
198
- nullable=column_nullable,
199
- server_default=column_default,
200
- )
201
- if column_id in self.graph_index:
202
- logger.warning(f"Duplication of @id {column_id}")
203
- self.graph_index[column_id] = column
204
- return column
205
-
206
- def visit_primary_key(
207
- self, primary_key_obj: str | Iterable[str], table_obj: _Mapping
208
- ) -> PrimaryKeyConstraint | None:
209
- # Docstring is inherited.
210
- self.checker.check_primary_key(primary_key_obj, table_obj)
211
- if primary_key_obj:
212
- if isinstance(primary_key_obj, str):
213
- primary_key_obj = [primary_key_obj]
214
- columns = [self.graph_index[c_id] for c_id in primary_key_obj]
215
- return PrimaryKeyConstraint(*columns)
216
- return None
217
-
218
- def visit_constraint(self, constraint_obj: _Mapping, table_obj: _Mapping) -> Constraint:
219
- # Docstring is inherited.
220
- self.checker.check_constraint(constraint_obj, table_obj)
221
- constraint_type = constraint_obj["@type"]
222
- constraint_id = constraint_obj["@id"]
223
-
224
- constraint_args: _MutableMapping = {}
225
- # The following are not used on every constraint
226
- _set_if("name", constraint_obj.get("name"), constraint_args)
227
- _set_if("info", constraint_obj.get("description"), constraint_args)
228
- _set_if("expression", constraint_obj.get("expression"), constraint_args)
229
- _set_if("deferrable", constraint_obj.get("deferrable"), constraint_args)
230
- _set_if("initially", constraint_obj.get("initially"), constraint_args)
231
-
232
- columns = [self.graph_index[c_id] for c_id in constraint_obj.get("columns", [])]
233
- constraint: Constraint
234
- if constraint_type == "ForeignKey":
235
- refcolumns = [self.graph_index[c_id] for c_id in constraint_obj.get("referencedColumns", [])]
236
- constraint = ForeignKeyConstraint(columns, refcolumns, **constraint_args)
237
- elif constraint_type == "Check":
238
- expression = constraint_obj["expression"]
239
- constraint = CheckConstraint(expression, **constraint_args)
240
- elif constraint_type == "Unique":
241
- constraint = UniqueConstraint(*columns, **constraint_args)
242
- else:
243
- raise ValueError(f"Unexpected constraint type: {constraint_type}")
244
- self.graph_index[constraint_id] = constraint
245
- return constraint
246
-
247
- def visit_index(self, index_obj: _Mapping, table_obj: _Mapping) -> Index:
248
- # Docstring is inherited.
249
- self.checker.check_index(index_obj, table_obj)
250
- name = index_obj["name"]
251
- description = index_obj.get("description")
252
- columns = [self.graph_index[c_id] for c_id in index_obj.get("columns", [])]
253
- expressions = index_obj.get("expressions", [])
254
- return Index(name, *columns, *expressions, info=description)
255
-
256
-
257
- def _set_if(key: str, value: Any, mapping: _MutableMapping) -> None:
258
- if value is not None:
259
- mapping[key] = value
260
-
261
-
262
- def _process_variant_override(dialect_name: str, variant_override_str: str) -> types.TypeEngine:
263
- """Return variant type for given dialect."""
264
- match = length_regex.search(variant_override_str)
265
- dialect = DIALECT_MODULES[dialect_name]
266
- variant_type_name = variant_override_str.split("(")[0]
267
-
268
- # Process Variant Type
269
- if variant_type_name not in dir(dialect):
270
- raise ValueError(f"Type {variant_type_name} not found in dialect {dialect_name}")
271
- variant_type = getattr(dialect, variant_type_name)
272
- length_params = []
273
- if match:
274
- length_params.extend([int(i) for i in match.group(1).split(",")])
275
- return variant_type(*length_params)