lsst-felis 27.2024.2500__py3-none-any.whl → 27.2024.2700__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 +145 -29
- felis/datamodel.py +334 -89
- felis/db/dialects.py +65 -12
- felis/db/sqltypes.py +255 -16
- felis/db/utils.py +108 -52
- felis/db/variants.py +66 -8
- felis/metadata.py +70 -54
- felis/tap.py +180 -18
- felis/types.py +56 -8
- felis/version.py +1 -1
- {lsst_felis-27.2024.2500.dist-info → lsst_felis-27.2024.2700.dist-info}/COPYRIGHT +1 -1
- {lsst_felis-27.2024.2500.dist-info → lsst_felis-27.2024.2700.dist-info}/METADATA +4 -2
- lsst_felis-27.2024.2700.dist-info/RECORD +21 -0
- {lsst_felis-27.2024.2500.dist-info → lsst_felis-27.2024.2700.dist-info}/WHEEL +1 -1
- lsst_felis-27.2024.2500.dist-info/RECORD +0 -21
- {lsst_felis-27.2024.2500.dist-info → lsst_felis-27.2024.2700.dist-info}/LICENSE +0 -0
- {lsst_felis-27.2024.2500.dist-info → lsst_felis-27.2024.2700.dist-info}/entry_points.txt +0 -0
- {lsst_felis-27.2024.2500.dist-info → lsst_felis-27.2024.2700.dist-info}/top_level.txt +0 -0
- {lsst_felis-27.2024.2500.dist-info → lsst_felis-27.2024.2700.dist-info}/zip-safe +0 -0
felis/db/utils.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Database utility functions and classes."""
|
|
2
|
+
|
|
1
3
|
# This file is part of felis.
|
|
2
4
|
#
|
|
3
5
|
# Developed for the LSST Data Management System.
|
|
@@ -36,17 +38,44 @@ from sqlalchemy.types import TypeEngine
|
|
|
36
38
|
|
|
37
39
|
from .dialects import get_dialect_module
|
|
38
40
|
|
|
41
|
+
__all__ = ["string_to_typeengine", "SQLWriter", "ConnectionWrapper", "DatabaseContext"]
|
|
42
|
+
|
|
39
43
|
logger = logging.getLogger("felis")
|
|
40
44
|
|
|
41
45
|
_DATATYPE_REGEXP = re.compile(r"(\w+)(\((.*)\))?")
|
|
42
|
-
"""Regular expression to match data types
|
|
46
|
+
"""Regular expression to match data types with parameters in parentheses."""
|
|
43
47
|
|
|
44
48
|
|
|
45
49
|
def string_to_typeengine(
|
|
46
50
|
type_string: str, dialect: Dialect | None = None, length: int | None = None
|
|
47
51
|
) -> TypeEngine:
|
|
48
|
-
"""Convert a string representation of a
|
|
49
|
-
|
|
52
|
+
"""Convert a string representation of a datatype to a SQLAlchemy type.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
type_string
|
|
57
|
+
The string representation of the data type.
|
|
58
|
+
dialect
|
|
59
|
+
The SQLAlchemy dialect to use. If None, the default dialect will be
|
|
60
|
+
used.
|
|
61
|
+
length
|
|
62
|
+
The length of the data type. If the data type does not have a length
|
|
63
|
+
attribute, this parameter will be ignored.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
`sqlalchemy.types.TypeEngine`
|
|
68
|
+
The SQLAlchemy type engine object.
|
|
69
|
+
|
|
70
|
+
Raises
|
|
71
|
+
------
|
|
72
|
+
ValueError
|
|
73
|
+
If the type string is invalid or the type is not supported.
|
|
74
|
+
|
|
75
|
+
Notes
|
|
76
|
+
-----
|
|
77
|
+
This function is used when converting type override strings defined in
|
|
78
|
+
fields such as ``mysql:datatype`` in the schema data.
|
|
50
79
|
"""
|
|
51
80
|
match = _DATATYPE_REGEXP.search(type_string)
|
|
52
81
|
if not match:
|
|
@@ -78,17 +107,17 @@ def string_to_typeengine(
|
|
|
78
107
|
|
|
79
108
|
|
|
80
109
|
class SQLWriter:
|
|
81
|
-
"""
|
|
110
|
+
"""Write SQL statements to stdout or a file.
|
|
82
111
|
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
file
|
|
115
|
+
The file to write the SQL statements to. If None, the statements
|
|
116
|
+
will be written to stdout.
|
|
117
|
+
"""
|
|
85
118
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
file : `io.TextIOBase` or `None`, optional
|
|
89
|
-
The file to write the SQL statements to. If None, the statements
|
|
90
|
-
will be written to stdout.
|
|
91
|
-
"""
|
|
119
|
+
def __init__(self, file: IO[str] | None = None) -> None:
|
|
120
|
+
"""Initialize the SQL writer."""
|
|
92
121
|
self.file = file
|
|
93
122
|
self.dialect: Dialect | None = None
|
|
94
123
|
|
|
@@ -100,12 +129,17 @@ class SQLWriter:
|
|
|
100
129
|
|
|
101
130
|
Parameters
|
|
102
131
|
----------
|
|
103
|
-
sql
|
|
132
|
+
sql
|
|
104
133
|
The SQL statement to write.
|
|
105
|
-
multiparams
|
|
134
|
+
*multiparams
|
|
106
135
|
The multiparams to use for the SQL statement.
|
|
107
|
-
params
|
|
136
|
+
**params
|
|
108
137
|
The params to use for the SQL statement.
|
|
138
|
+
|
|
139
|
+
Notes
|
|
140
|
+
-----
|
|
141
|
+
The functions arguments are typed very loosely because this method in
|
|
142
|
+
SQLAlchemy is untyped, amd we do not call it directly.
|
|
109
143
|
"""
|
|
110
144
|
compiled = sql.compile(dialect=self.dialect)
|
|
111
145
|
sql_str = str(compiled) + ";"
|
|
@@ -126,22 +160,37 @@ class SQLWriter:
|
|
|
126
160
|
|
|
127
161
|
|
|
128
162
|
class ConnectionWrapper:
|
|
129
|
-
"""
|
|
130
|
-
|
|
163
|
+
"""Wrap a SQLAlchemy engine or mock connection to provide a consistent
|
|
164
|
+
interface for executing SQL statements.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
engine
|
|
169
|
+
The SQLAlchemy engine or mock connection to wrap.
|
|
131
170
|
"""
|
|
132
171
|
|
|
133
172
|
def __init__(self, engine: Engine | MockConnection):
|
|
134
|
-
"""Initialize the connection wrapper.
|
|
173
|
+
"""Initialize the connection wrapper."""
|
|
174
|
+
self.engine = engine
|
|
175
|
+
|
|
176
|
+
def execute(self, statement: Any) -> ResultProxy:
|
|
177
|
+
"""Execute a SQL statement on the engine and return the result.
|
|
135
178
|
|
|
136
179
|
Parameters
|
|
137
180
|
----------
|
|
138
|
-
|
|
139
|
-
The
|
|
181
|
+
statement
|
|
182
|
+
The SQL statement to execute.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
``sqlalchemy.engine.ResultProxy``
|
|
187
|
+
The result of the statement execution.
|
|
188
|
+
|
|
189
|
+
Notes
|
|
190
|
+
-----
|
|
191
|
+
The statement will be executed in a transaction block if not using
|
|
192
|
+
a mock connection.
|
|
140
193
|
"""
|
|
141
|
-
self.engine = engine
|
|
142
|
-
|
|
143
|
-
def execute(self, statement: Any) -> ResultProxy:
|
|
144
|
-
"""Execute a SQL statement on the engine and return the result."""
|
|
145
194
|
if isinstance(statement, str):
|
|
146
195
|
statement = text(statement)
|
|
147
196
|
if isinstance(self.engine, MockConnection):
|
|
@@ -153,19 +202,19 @@ class ConnectionWrapper:
|
|
|
153
202
|
|
|
154
203
|
|
|
155
204
|
class DatabaseContext:
|
|
156
|
-
"""
|
|
205
|
+
"""Manage the database connection and SQLAlchemy metadata.
|
|
157
206
|
|
|
158
|
-
|
|
159
|
-
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
metadata
|
|
210
|
+
The SQLAlchemy metadata object.
|
|
160
211
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
The SQLAlchemy metadata object.
|
|
212
|
+
engine
|
|
213
|
+
The SQLAlchemy engine or mock connection object.
|
|
214
|
+
"""
|
|
165
215
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"""
|
|
216
|
+
def __init__(self, metadata: MetaData, engine: Engine | MockConnection):
|
|
217
|
+
"""Initialize the database context."""
|
|
169
218
|
self.engine = engine
|
|
170
219
|
self.dialect_name = engine.dialect.name
|
|
171
220
|
self.metadata = metadata
|
|
@@ -174,16 +223,18 @@ class DatabaseContext:
|
|
|
174
223
|
def create_if_not_exists(self) -> None:
|
|
175
224
|
"""Create the schema in the database if it does not exist.
|
|
176
225
|
|
|
177
|
-
|
|
226
|
+
Raises
|
|
227
|
+
------
|
|
228
|
+
ValueError
|
|
229
|
+
If the database is not supported.
|
|
230
|
+
sqlalchemy.exc.SQLAlchemyError
|
|
231
|
+
If there is an error creating the schema.
|
|
232
|
+
|
|
233
|
+
Notes
|
|
234
|
+
-----
|
|
235
|
+
In MySQL, this will create a new database and, in PostgreSQL, it will
|
|
178
236
|
create a new schema. For other variants, this is an unsupported
|
|
179
237
|
operation.
|
|
180
|
-
|
|
181
|
-
Parameters
|
|
182
|
-
----------
|
|
183
|
-
engine: `sqlalchemy.Engine`
|
|
184
|
-
The SQLAlchemy engine object.
|
|
185
|
-
schema_name: `str`
|
|
186
|
-
The name of the schema (or database) to create.
|
|
187
238
|
"""
|
|
188
239
|
schema_name = self.metadata.schema
|
|
189
240
|
try:
|
|
@@ -202,15 +253,15 @@ class DatabaseContext:
|
|
|
202
253
|
def drop_if_exists(self) -> None:
|
|
203
254
|
"""Drop the schema in the database if it exists.
|
|
204
255
|
|
|
205
|
-
|
|
206
|
-
|
|
256
|
+
Raises
|
|
257
|
+
------
|
|
258
|
+
ValueError
|
|
259
|
+
If the database is not supported.
|
|
207
260
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
schema_name: `str`
|
|
213
|
-
The name of the schema (or database) to drop.
|
|
261
|
+
Notes
|
|
262
|
+
-----
|
|
263
|
+
In MySQL, this will drop a database. In PostgreSQL, it will drop a
|
|
264
|
+
schema. For other variants, this is an unsupported operation.
|
|
214
265
|
"""
|
|
215
266
|
schema_name = self.metadata.schema
|
|
216
267
|
try:
|
|
@@ -236,11 +287,16 @@ class DatabaseContext:
|
|
|
236
287
|
|
|
237
288
|
Parameters
|
|
238
289
|
----------
|
|
239
|
-
engine_url
|
|
290
|
+
engine_url
|
|
240
291
|
The SQLAlchemy engine URL.
|
|
241
|
-
output_file
|
|
292
|
+
output_file
|
|
242
293
|
The file to write the SQL statements to. If None, the statements
|
|
243
294
|
will be written to stdout.
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
``sqlalchemy.engine.mock.MockConnection``
|
|
299
|
+
The mock connection object.
|
|
244
300
|
"""
|
|
245
301
|
writer = SQLWriter(output_file)
|
|
246
302
|
engine = create_mock_engine(engine_url, executor=writer.write)
|
felis/db/variants.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Handle variant overrides for a Felis column."""
|
|
2
|
+
|
|
1
3
|
# This file is part of felis.
|
|
2
4
|
#
|
|
3
5
|
# Developed for the LSST Data Management System.
|
|
@@ -19,7 +21,11 @@
|
|
|
19
21
|
# You should have received a copy of the GNU General Public License
|
|
20
22
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
21
23
|
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
22
26
|
import re
|
|
27
|
+
from collections.abc import Mapping
|
|
28
|
+
from types import MappingProxyType
|
|
23
29
|
from typing import Any
|
|
24
30
|
|
|
25
31
|
from sqlalchemy import types
|
|
@@ -28,26 +34,55 @@ from sqlalchemy.types import TypeEngine
|
|
|
28
34
|
from ..datamodel import Column
|
|
29
35
|
from .dialects import get_dialect_module, get_supported_dialects
|
|
30
36
|
|
|
37
|
+
__all__ = ["make_variant_dict"]
|
|
38
|
+
|
|
31
39
|
|
|
32
40
|
def _create_column_variant_overrides() -> dict[str, str]:
|
|
33
|
-
"""
|
|
41
|
+
"""Map column variant overrides to their dialect name.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
column_variant_overrides : `dict` [ `str`, `str` ]
|
|
46
|
+
A mapping of column variant overrides to their dialect name.
|
|
47
|
+
|
|
48
|
+
Notes
|
|
49
|
+
-----
|
|
50
|
+
This function is intended for internal use only.
|
|
51
|
+
"""
|
|
34
52
|
column_variant_overrides = {}
|
|
35
53
|
for dialect_name in get_supported_dialects().keys():
|
|
36
54
|
column_variant_overrides[f"{dialect_name}_datatype"] = dialect_name
|
|
37
55
|
return column_variant_overrides
|
|
38
56
|
|
|
39
57
|
|
|
40
|
-
_COLUMN_VARIANT_OVERRIDES = _create_column_variant_overrides()
|
|
58
|
+
_COLUMN_VARIANT_OVERRIDES = MappingProxyType(_create_column_variant_overrides())
|
|
59
|
+
"""Map of column variant overrides to their dialect name."""
|
|
60
|
+
|
|
41
61
|
|
|
62
|
+
def _get_column_variant_overrides() -> Mapping[str, str]:
|
|
63
|
+
"""Get a dictionary of column variant overrides.
|
|
42
64
|
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
column_variant_overrides : `dict` [ `str`, `str` ]
|
|
68
|
+
A mapping of column variant overrides to their dialect name.
|
|
69
|
+
"""
|
|
45
70
|
return _COLUMN_VARIANT_OVERRIDES
|
|
46
71
|
|
|
47
72
|
|
|
48
73
|
def _get_column_variant_override(field_name: str) -> str:
|
|
49
|
-
"""
|
|
74
|
+
"""Get the dialect name from an override field name on the column like
|
|
50
75
|
``mysql_datatype``.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
dialect_name : `str`
|
|
80
|
+
The name of the dialect.
|
|
81
|
+
|
|
82
|
+
Raises
|
|
83
|
+
------
|
|
84
|
+
ValueError
|
|
85
|
+
If the field name is not found in the column variant overrides.
|
|
51
86
|
"""
|
|
52
87
|
if field_name not in _COLUMN_VARIANT_OVERRIDES:
|
|
53
88
|
raise ValueError(f"Field name {field_name} not found in column variant overrides")
|
|
@@ -59,7 +94,30 @@ _length_regex = re.compile(r"\((\d+)\)")
|
|
|
59
94
|
|
|
60
95
|
|
|
61
96
|
def _process_variant_override(dialect_name: str, variant_override_str: str) -> types.TypeEngine:
|
|
62
|
-
"""
|
|
97
|
+
"""Get the variant type for the given dialect.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
dialect_name
|
|
102
|
+
The name of the dialect to create.
|
|
103
|
+
variant_override_str
|
|
104
|
+
The string representation of the variant override.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
variant_type : `~sqlalchemy.types.TypeEngine`
|
|
109
|
+
The variant type for the given dialect.
|
|
110
|
+
|
|
111
|
+
Raises
|
|
112
|
+
------
|
|
113
|
+
ValueError
|
|
114
|
+
If the type is not found in the dialect.
|
|
115
|
+
|
|
116
|
+
Notes
|
|
117
|
+
-----
|
|
118
|
+
This function converts a string representation of a variant override
|
|
119
|
+
into a `sqlalchemy.types.TypeEngine` object.
|
|
120
|
+
"""
|
|
63
121
|
dialect = get_dialect_module(dialect_name)
|
|
64
122
|
variant_type_name = variant_override_str.split("(")[0]
|
|
65
123
|
|
|
@@ -82,12 +140,12 @@ def make_variant_dict(column_obj: Column) -> dict[str, TypeEngine[Any]]:
|
|
|
82
140
|
|
|
83
141
|
Parameters
|
|
84
142
|
----------
|
|
85
|
-
column_obj
|
|
143
|
+
column_obj
|
|
86
144
|
The column object from which to build the variant dictionary.
|
|
87
145
|
|
|
88
146
|
Returns
|
|
89
147
|
-------
|
|
90
|
-
|
|
148
|
+
`dict` [ `str`, `~sqlalchemy.types.TypeEngine` ]
|
|
91
149
|
The dictionary of `str` to `sqlalchemy.types.TypeEngine` containing
|
|
92
150
|
variant datatype information (e.g., for mysql, postgresql, etc).
|
|
93
151
|
"""
|
felis/metadata.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Build SQLAlchemy metadata from a Felis schema."""
|
|
2
|
+
|
|
1
3
|
# This file is part of felis.
|
|
2
4
|
#
|
|
3
5
|
# Developed for the LSST Data Management System.
|
|
@@ -47,18 +49,25 @@ from . import datamodel
|
|
|
47
49
|
from .db import sqltypes
|
|
48
50
|
from .types import FelisType
|
|
49
51
|
|
|
52
|
+
__all__ = ("MetaDataBuilder", "get_datatype_with_variants")
|
|
53
|
+
|
|
50
54
|
logger = logging.getLogger(__name__)
|
|
51
55
|
|
|
52
56
|
|
|
53
57
|
def get_datatype_with_variants(column_obj: datamodel.Column) -> TypeEngine:
|
|
54
58
|
"""Use the Felis type system to get a SQLAlchemy datatype with variant
|
|
55
|
-
overrides from the information in a
|
|
59
|
+
overrides from the information in a Felis column object.
|
|
56
60
|
|
|
57
61
|
Parameters
|
|
58
62
|
----------
|
|
59
|
-
column_obj
|
|
63
|
+
column_obj
|
|
60
64
|
The column object from which to get the datatype.
|
|
61
65
|
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
`~sqlalchemy.types.TypeEngine`
|
|
69
|
+
The SQLAlchemy datatype object.
|
|
70
|
+
|
|
62
71
|
Raises
|
|
63
72
|
------
|
|
64
73
|
ValueError
|
|
@@ -80,22 +89,22 @@ _VALID_SERVER_DEFAULTS = ("CURRENT_TIMESTAMP", "NOW()", "LOCALTIMESTAMP", "NULL"
|
|
|
80
89
|
|
|
81
90
|
|
|
82
91
|
class MetaDataBuilder:
|
|
83
|
-
"""
|
|
92
|
+
"""Build a SQLAlchemy metadata object from a Felis schema.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
schema
|
|
97
|
+
The schema object from which to build the SQLAlchemy metadata.
|
|
98
|
+
apply_schema_to_metadata
|
|
99
|
+
Whether to apply the schema name to the metadata object.
|
|
100
|
+
apply_schema_to_tables
|
|
101
|
+
Whether to apply the schema name to the tables.
|
|
102
|
+
"""
|
|
84
103
|
|
|
85
104
|
def __init__(
|
|
86
105
|
self, schema: Schema, apply_schema_to_metadata: bool = True, apply_schema_to_tables: bool = True
|
|
87
106
|
) -> None:
|
|
88
|
-
"""Initialize the metadata builder.
|
|
89
|
-
|
|
90
|
-
Parameters
|
|
91
|
-
----------
|
|
92
|
-
schema : `felis.datamodel.Schema`
|
|
93
|
-
The schema object from which to build the SQLAlchemy metadata.
|
|
94
|
-
apply_schema_to_metadata : `bool`, optional
|
|
95
|
-
Whether to apply the schema name to the metadata object.
|
|
96
|
-
apply_schema_to_tables : `bool`, optional
|
|
97
|
-
Whether to apply the schema name to the tables.
|
|
98
|
-
"""
|
|
107
|
+
"""Initialize the metadata builder."""
|
|
99
108
|
self.schema = schema
|
|
100
109
|
if not apply_schema_to_metadata:
|
|
101
110
|
logger.debug("Schema name will not be applied to metadata")
|
|
@@ -106,20 +115,25 @@ class MetaDataBuilder:
|
|
|
106
115
|
self.apply_schema_to_tables = apply_schema_to_tables
|
|
107
116
|
|
|
108
117
|
def build(self) -> MetaData:
|
|
109
|
-
"""Build the SQLAlchemy tables and constraints from the schema.
|
|
118
|
+
"""Build the SQLAlchemy tables and constraints from the schema.
|
|
119
|
+
|
|
120
|
+
Notes
|
|
121
|
+
-----
|
|
122
|
+
This first builds the tables and then makes a second pass to build the
|
|
123
|
+
constraints. This is necessary because the constraints may reference
|
|
124
|
+
objects that are not yet created when the tables are built.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
`~sqlalchemy.sql.schema.MetaData`
|
|
129
|
+
The SQLAlchemy metadata object.
|
|
130
|
+
"""
|
|
110
131
|
self.build_tables()
|
|
111
132
|
self.build_constraints()
|
|
112
133
|
return self.metadata
|
|
113
134
|
|
|
114
135
|
def build_tables(self) -> None:
|
|
115
|
-
"""Build the SQLAlchemy tables from the schema.
|
|
116
|
-
|
|
117
|
-
Notes
|
|
118
|
-
-----
|
|
119
|
-
This function builds all the tables by calling ``build_table`` on
|
|
120
|
-
each Pydantic object. It also calls ``build_primary_key`` to create the
|
|
121
|
-
primary key constraints.
|
|
122
|
-
"""
|
|
136
|
+
"""Build the SQLAlchemy tables from the schema."""
|
|
123
137
|
for table in self.schema.tables:
|
|
124
138
|
self.build_table(table)
|
|
125
139
|
if table.primary_key:
|
|
@@ -127,40 +141,44 @@ class MetaDataBuilder:
|
|
|
127
141
|
self._objects[table.id].append_constraint(primary_key)
|
|
128
142
|
|
|
129
143
|
def build_primary_key(self, primary_key_columns: str | list[str]) -> PrimaryKeyConstraint:
|
|
130
|
-
"""Build a
|
|
131
|
-
or a list.
|
|
132
|
-
|
|
133
|
-
The `primary_key_columns` are strings or a list of strings representing
|
|
134
|
-
IDs pointing to columns that will be looked up in the internal object
|
|
135
|
-
dictionary.
|
|
144
|
+
"""Build a SQAlchemy ``PrimaryKeyConstraint`` from a single column ID
|
|
145
|
+
or a list of them.
|
|
136
146
|
|
|
137
147
|
Parameters
|
|
138
148
|
----------
|
|
139
|
-
primary_key_columns
|
|
149
|
+
primary_key_columns
|
|
140
150
|
The column ID or list of column IDs from which to build the primary
|
|
141
151
|
key.
|
|
142
152
|
|
|
143
153
|
Returns
|
|
144
154
|
-------
|
|
145
|
-
|
|
155
|
+
`~sqlalchemy.sql.schema.PrimaryKeyConstraint`
|
|
146
156
|
The SQLAlchemy primary key constraint object.
|
|
157
|
+
|
|
158
|
+
Notes
|
|
159
|
+
-----
|
|
160
|
+
The ``primary_key_columns`` is a string or a list of strings
|
|
161
|
+
representing IDs which will be used to find the columnn objects in the
|
|
162
|
+
builder's internal ID map.
|
|
147
163
|
"""
|
|
148
164
|
return PrimaryKeyConstraint(
|
|
149
165
|
*[self._objects[column_id] for column_id in ensure_iterable(primary_key_columns)]
|
|
150
166
|
)
|
|
151
167
|
|
|
152
168
|
def build_table(self, table_obj: datamodel.Table) -> None:
|
|
153
|
-
"""Build a
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
Several MySQL table options are handled by annotations on the table,
|
|
157
|
-
including the engine and charset. This is not needed for Postgres,
|
|
158
|
-
which does not have equivalent options.
|
|
169
|
+
"""Build a SQLAlchemy ``Table`` from a Felis table and add it to the
|
|
170
|
+
metadata.
|
|
159
171
|
|
|
160
172
|
Parameters
|
|
161
173
|
----------
|
|
162
|
-
table_obj
|
|
163
|
-
The table object to build the SQLAlchemy table
|
|
174
|
+
table_obj
|
|
175
|
+
The Felis table object from which to build the SQLAlchemy table.
|
|
176
|
+
|
|
177
|
+
Notes
|
|
178
|
+
-----
|
|
179
|
+
Several MySQL table options, including the engine and charset, are
|
|
180
|
+
handled by adding annotations to the table. This is not needed for
|
|
181
|
+
Postgres, as Felis does not support any table options for this dialect.
|
|
164
182
|
"""
|
|
165
183
|
# Process mysql table options.
|
|
166
184
|
optargs = {}
|
|
@@ -192,16 +210,16 @@ class MetaDataBuilder:
|
|
|
192
210
|
self._objects[id] = table
|
|
193
211
|
|
|
194
212
|
def build_column(self, column_obj: datamodel.Column) -> Column:
|
|
195
|
-
"""Build a SQLAlchemy
|
|
213
|
+
"""Build a SQLAlchemy ``Column`` from a Felis column object.
|
|
196
214
|
|
|
197
215
|
Parameters
|
|
198
216
|
----------
|
|
199
|
-
column_obj
|
|
217
|
+
column_obj
|
|
200
218
|
The column object from which to build the SQLAlchemy column.
|
|
201
219
|
|
|
202
220
|
Returns
|
|
203
221
|
-------
|
|
204
|
-
|
|
222
|
+
`~sqlalchemy.sql.schema.Column`
|
|
205
223
|
The SQLAlchemy column object.
|
|
206
224
|
"""
|
|
207
225
|
# Get basic column attributes.
|
|
@@ -244,8 +262,8 @@ class MetaDataBuilder:
|
|
|
244
262
|
return column
|
|
245
263
|
|
|
246
264
|
def build_constraints(self) -> None:
|
|
247
|
-
"""Build the SQLAlchemy constraints
|
|
248
|
-
to the associated
|
|
265
|
+
"""Build the SQLAlchemy constraints from the Felis schema and append
|
|
266
|
+
them to the associated table in the metadata.
|
|
249
267
|
|
|
250
268
|
Notes
|
|
251
269
|
-----
|
|
@@ -260,18 +278,16 @@ class MetaDataBuilder:
|
|
|
260
278
|
table.append_constraint(constraint)
|
|
261
279
|
|
|
262
280
|
def build_constraint(self, constraint_obj: datamodel.Constraint) -> Constraint:
|
|
263
|
-
"""Build a SQLAlchemy
|
|
264
|
-
object.
|
|
281
|
+
"""Build a SQLAlchemy ``Constraint`` from a Felis constraint.
|
|
265
282
|
|
|
266
283
|
Parameters
|
|
267
284
|
----------
|
|
268
|
-
constraint_obj
|
|
269
|
-
The
|
|
270
|
-
constraint.
|
|
285
|
+
constraint_obj
|
|
286
|
+
The Felis object from which to build the constraint.
|
|
271
287
|
|
|
272
288
|
Returns
|
|
273
289
|
-------
|
|
274
|
-
|
|
290
|
+
`~sqlalchemy.sql.schema.Constraint`
|
|
275
291
|
The SQLAlchemy constraint object.
|
|
276
292
|
|
|
277
293
|
Raises
|
|
@@ -311,16 +327,16 @@ class MetaDataBuilder:
|
|
|
311
327
|
return constraint
|
|
312
328
|
|
|
313
329
|
def build_index(self, index_obj: datamodel.Index) -> Index:
|
|
314
|
-
"""Build a SQLAlchemy
|
|
330
|
+
"""Build a SQLAlchemy ``Index`` from a Felis `~felis.datamodel.Index`.
|
|
315
331
|
|
|
316
332
|
Parameters
|
|
317
333
|
----------
|
|
318
|
-
index_obj
|
|
319
|
-
The
|
|
334
|
+
index_obj
|
|
335
|
+
The Felis object from which to build the SQLAlchemy index.
|
|
320
336
|
|
|
321
337
|
Returns
|
|
322
338
|
-------
|
|
323
|
-
|
|
339
|
+
`~sqlalchemy.sql.schema.Index`
|
|
324
340
|
The SQLAlchemy index object.
|
|
325
341
|
"""
|
|
326
342
|
columns = [self._objects[c_id] for c_id in (index_obj.columns if index_obj.columns else [])]
|