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/dialects.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Get SQLAlchemy dialects and their type modules."""
|
|
2
|
+
|
|
1
3
|
# This file is part of felis.
|
|
2
4
|
#
|
|
3
5
|
# Developed for the LSST Data Management System.
|
|
@@ -19,8 +21,10 @@
|
|
|
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
|
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from collections.abc import Mapping
|
|
27
|
+
from types import MappingProxyType, ModuleType
|
|
24
28
|
|
|
25
29
|
from sqlalchemy import dialects
|
|
26
30
|
from sqlalchemy.engine import Dialect
|
|
@@ -28,36 +32,85 @@ from sqlalchemy.engine.mock import create_mock_engine
|
|
|
28
32
|
|
|
29
33
|
from .sqltypes import MYSQL, ORACLE, POSTGRES, SQLITE
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
__all__ = ["get_supported_dialects", "get_dialect_module"]
|
|
36
|
+
|
|
37
|
+
_DIALECT_NAMES = (MYSQL, POSTGRES, SQLITE, ORACLE)
|
|
38
|
+
"""List of supported dialect names.
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
This list is used to create the dialect and module dictionaries.
|
|
41
|
+
"""
|
|
34
42
|
|
|
35
43
|
|
|
36
44
|
def _dialect(dialect_name: str) -> Dialect:
|
|
37
|
-
"""Create the SQLAlchemy dialect for the given name.
|
|
45
|
+
"""Create the SQLAlchemy dialect for the given name using a mock engine.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
dialect_name
|
|
50
|
+
The name of the dialect to create.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
`~sqlalchemy.engine.Dialect`
|
|
55
|
+
The SQLAlchemy dialect.
|
|
56
|
+
"""
|
|
38
57
|
return create_mock_engine(f"{dialect_name}://", executor=None).dialect
|
|
39
58
|
|
|
40
59
|
|
|
41
|
-
_DIALECTS = {name: _dialect(name) for name in _DIALECT_NAMES}
|
|
60
|
+
_DIALECTS = MappingProxyType({name: _dialect(name) for name in _DIALECT_NAMES})
|
|
42
61
|
"""Dictionary of dialect names to SQLAlchemy dialects."""
|
|
43
62
|
|
|
44
63
|
|
|
45
|
-
def get_supported_dialects() ->
|
|
46
|
-
"""Get a dictionary of the supported SQLAlchemy dialects.
|
|
64
|
+
def get_supported_dialects() -> Mapping[str, Dialect]:
|
|
65
|
+
"""Get a dictionary of the supported SQLAlchemy dialects.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
`dict` [ `str`, `~sqlalchemy.engine.Dialect`]
|
|
70
|
+
A dictionary of the supported SQLAlchemy dialects.
|
|
71
|
+
|
|
72
|
+
Notes
|
|
73
|
+
-----
|
|
74
|
+
The dictionary is keyed by the dialect name and the value is the SQLAlchemy
|
|
75
|
+
dialect object. This function is intended as the primary interface for
|
|
76
|
+
getting the supported dialects.
|
|
77
|
+
"""
|
|
47
78
|
return _DIALECTS
|
|
48
79
|
|
|
49
80
|
|
|
50
81
|
def _dialect_module(dialect_name: str) -> ModuleType:
|
|
51
|
-
"""Get the SQLAlchemy dialect module for the given name.
|
|
82
|
+
"""Get the SQLAlchemy dialect module for the given name.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
dialect_name
|
|
87
|
+
The name of the dialect module to get from the SQLAlchemy package.
|
|
88
|
+
"""
|
|
52
89
|
return getattr(dialects, dialect_name)
|
|
53
90
|
|
|
54
91
|
|
|
55
|
-
_DIALECT_MODULES = {name: _dialect_module(name) for name in _DIALECT_NAMES}
|
|
56
|
-
"""Dictionary of dialect names to SQLAlchemy modules
|
|
92
|
+
_DIALECT_MODULES = MappingProxyType({name: _dialect_module(name) for name in _DIALECT_NAMES})
|
|
93
|
+
"""Dictionary of dialect names to SQLAlchemy modules."""
|
|
57
94
|
|
|
58
95
|
|
|
59
96
|
def get_dialect_module(dialect_name: str) -> ModuleType:
|
|
60
|
-
"""Get the SQLAlchemy dialect module for the given name.
|
|
97
|
+
"""Get the SQLAlchemy dialect module for the given name.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
dialect_name
|
|
102
|
+
The name of the dialect module to get from the SQLAlchemy package.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
`~types.ModuleType`
|
|
107
|
+
The SQLAlchemy dialect module.
|
|
108
|
+
|
|
109
|
+
Raises
|
|
110
|
+
------
|
|
111
|
+
ValueError
|
|
112
|
+
If the dialect name is not supported.
|
|
113
|
+
"""
|
|
61
114
|
if dialect_name not in _DIALECT_MODULES:
|
|
62
115
|
raise ValueError(f"Unsupported dialect: {dialect_name}")
|
|
63
116
|
return _DIALECT_MODULES[dialect_name]
|
felis/db/sqltypes.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Map Felis types to SQLAlchemy types."""
|
|
2
|
+
|
|
1
3
|
# This file is part of felis.
|
|
2
4
|
#
|
|
3
5
|
# Developed for the LSST Data Management System.
|
|
@@ -19,6 +21,8 @@
|
|
|
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 builtins
|
|
23
27
|
from collections.abc import Callable, Mapping
|
|
24
28
|
from typing import Any
|
|
@@ -27,6 +31,23 @@ from sqlalchemy import SmallInteger, types
|
|
|
27
31
|
from sqlalchemy.dialects import mysql, oracle, postgresql
|
|
28
32
|
from sqlalchemy.ext.compiler import compiles
|
|
29
33
|
|
|
34
|
+
__all__ = [
|
|
35
|
+
"boolean",
|
|
36
|
+
"byte",
|
|
37
|
+
"short",
|
|
38
|
+
"int",
|
|
39
|
+
"long",
|
|
40
|
+
"float",
|
|
41
|
+
"double",
|
|
42
|
+
"char",
|
|
43
|
+
"string",
|
|
44
|
+
"unicode",
|
|
45
|
+
"text",
|
|
46
|
+
"binary",
|
|
47
|
+
"timestamp",
|
|
48
|
+
"get_type_func",
|
|
49
|
+
]
|
|
50
|
+
|
|
30
51
|
MYSQL = "mysql"
|
|
31
52
|
ORACLE = "oracle"
|
|
32
53
|
POSTGRES = "postgresql"
|
|
@@ -40,8 +61,28 @@ class TINYINT(SmallInteger):
|
|
|
40
61
|
|
|
41
62
|
|
|
42
63
|
@compiles(TINYINT)
|
|
43
|
-
def compile_tinyint(type_: Any, compiler: Any, **
|
|
44
|
-
"""
|
|
64
|
+
def compile_tinyint(type_: Any, compiler: Any, **kwargs: Any) -> str:
|
|
65
|
+
"""Compile the non-standard ``TINYINT`` type to SQL.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
type_
|
|
70
|
+
The type object.
|
|
71
|
+
compiler
|
|
72
|
+
The compiler object.
|
|
73
|
+
**kwargs
|
|
74
|
+
Additional keyword arguments.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
`str`
|
|
79
|
+
The compiled SQL for TINYINT.
|
|
80
|
+
|
|
81
|
+
Notes
|
|
82
|
+
-----
|
|
83
|
+
This function returns the SQL for the the TINYINT type. The function
|
|
84
|
+
signature and parameters are defined by SQLAlchemy.
|
|
85
|
+
"""
|
|
45
86
|
return "TINYINT"
|
|
46
87
|
|
|
47
88
|
|
|
@@ -117,72 +158,245 @@ binary_map: _TypeMap = {
|
|
|
117
158
|
|
|
118
159
|
|
|
119
160
|
def boolean(**kwargs: Any) -> types.TypeEngine:
|
|
120
|
-
"""
|
|
161
|
+
"""Get the SQL type for Felis `~felis.types.Boolean` with variants.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
**kwargs
|
|
166
|
+
Additional keyword arguments to pass to the type object.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
`~sqlalchemy.types.TypeEngine`
|
|
171
|
+
The SQL type for a Felis boolean.
|
|
172
|
+
"""
|
|
121
173
|
return _vary(types.BOOLEAN(), boolean_map, kwargs)
|
|
122
174
|
|
|
123
175
|
|
|
124
176
|
def byte(**kwargs: Any) -> types.TypeEngine:
|
|
125
|
-
"""
|
|
177
|
+
"""Get the SQL type for Felis `~felis.types.Byte` with variants.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
**kwargs
|
|
182
|
+
Additional keyword arguments to pass to the type object.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
`~sqlalchemy.types.TypeEngine`
|
|
187
|
+
The SQL type for a Felis byte.
|
|
188
|
+
"""
|
|
126
189
|
return _vary(TINYINT(), byte_map, kwargs)
|
|
127
190
|
|
|
128
191
|
|
|
129
192
|
def short(**kwargs: Any) -> types.TypeEngine:
|
|
130
|
-
"""
|
|
193
|
+
"""Get the SQL type for Felis `~felis.types.Short` with variants.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
**kwargs
|
|
198
|
+
Additional keyword arguments to pass to the type object.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
`~sqlalchemy.types.TypeEngine`
|
|
203
|
+
The SQL type for a Felis short.
|
|
204
|
+
"""
|
|
131
205
|
return _vary(types.SMALLINT(), short_map, kwargs)
|
|
132
206
|
|
|
133
207
|
|
|
134
208
|
def int(**kwargs: Any) -> types.TypeEngine:
|
|
135
|
-
"""
|
|
209
|
+
"""Get the SQL type for Felis `~felis.types.Int` with variants.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
**kwargs
|
|
214
|
+
Additional keyword arguments to pass to the type object.
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
`~sqlalchemy.types.TypeEngine`
|
|
219
|
+
The SQL type for a Felis int.
|
|
220
|
+
"""
|
|
136
221
|
return _vary(types.INTEGER(), int_map, kwargs)
|
|
137
222
|
|
|
138
223
|
|
|
139
224
|
def long(**kwargs: Any) -> types.TypeEngine:
|
|
140
|
-
"""
|
|
225
|
+
"""Get the SQL type for Felis `~felis.types.Long` with variants.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
**kwargs
|
|
230
|
+
Additional keyword arguments to pass to the type object.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
`~sqlalchemy.types.TypeEngine`
|
|
235
|
+
The SQL type for a Felis long.
|
|
236
|
+
"""
|
|
141
237
|
return _vary(types.BIGINT(), long_map, kwargs)
|
|
142
238
|
|
|
143
239
|
|
|
144
240
|
def float(**kwargs: Any) -> types.TypeEngine:
|
|
145
|
-
"""
|
|
241
|
+
"""Get the SQL type for Felis `~felis.types.Float` with variants.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
**kwargs
|
|
246
|
+
Additional keyword arguments to pass to the type object.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
`~sqlalchemy.types.TypeEngine`
|
|
251
|
+
The SQL type for a Felis float.
|
|
252
|
+
"""
|
|
146
253
|
return _vary(types.FLOAT(), float_map, kwargs)
|
|
147
254
|
|
|
148
255
|
|
|
149
256
|
def double(**kwargs: Any) -> types.TypeEngine:
|
|
150
|
-
"""
|
|
257
|
+
"""Get the SQL type for Felis `~felis.types.Double` with variants.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
**kwargs
|
|
262
|
+
Additional keyword arguments to pass to the type object.
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
`~sqlalchemy.types.TypeEngine`
|
|
267
|
+
The SQL type for a Felis double.
|
|
268
|
+
"""
|
|
151
269
|
return _vary(types.DOUBLE(), double_map, kwargs)
|
|
152
270
|
|
|
153
271
|
|
|
154
272
|
def char(length: builtins.int, **kwargs: Any) -> types.TypeEngine:
|
|
155
|
-
"""
|
|
273
|
+
"""Get the SQL type for Felis `~felis.types.Char` with variants.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
length
|
|
278
|
+
The length of the character field.
|
|
279
|
+
**kwargs
|
|
280
|
+
Additional keyword arguments to pass to the type object.
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
`~sqlalchemy.types.TypeEngine`
|
|
285
|
+
The SQL type for a Felis char.
|
|
286
|
+
"""
|
|
156
287
|
return _vary(types.CHAR(length), char_map, kwargs, length)
|
|
157
288
|
|
|
158
289
|
|
|
159
290
|
def string(length: builtins.int, **kwargs: Any) -> types.TypeEngine:
|
|
160
|
-
"""
|
|
291
|
+
"""Get the SQL type for Felis `~felis.types.String` with variants.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
length
|
|
296
|
+
The length of the string field.
|
|
297
|
+
**kwargs
|
|
298
|
+
Additional keyword arguments to pass to the type object.
|
|
299
|
+
|
|
300
|
+
Returns
|
|
301
|
+
-------
|
|
302
|
+
`~sqlalchemy.types.TypeEngine`
|
|
303
|
+
The SQL type for a Felis string.
|
|
304
|
+
"""
|
|
161
305
|
return _vary(types.VARCHAR(length), string_map, kwargs, length)
|
|
162
306
|
|
|
163
307
|
|
|
164
308
|
def unicode(length: builtins.int, **kwargs: Any) -> types.TypeEngine:
|
|
165
|
-
"""
|
|
309
|
+
"""Get the SQL type for Felis `~felis.types.Unicode` with variants.
|
|
310
|
+
|
|
311
|
+
Parameters
|
|
312
|
+
----------
|
|
313
|
+
length
|
|
314
|
+
The length of the unicode string field.
|
|
315
|
+
**kwargs
|
|
316
|
+
Additional keyword arguments to pass to the type object.
|
|
317
|
+
|
|
318
|
+
Returns
|
|
319
|
+
-------
|
|
320
|
+
`~sqlalchemy.types.TypeEngine`
|
|
321
|
+
The SQL type for a Felis unicode string.
|
|
322
|
+
"""
|
|
166
323
|
return _vary(types.NVARCHAR(length), unicode_map, kwargs, length)
|
|
167
324
|
|
|
168
325
|
|
|
169
326
|
def text(**kwargs: Any) -> types.TypeEngine:
|
|
170
|
-
"""
|
|
327
|
+
"""Get the SQL type for Felis `~felis.types.Text` with variants.
|
|
328
|
+
|
|
329
|
+
Parameters
|
|
330
|
+
----------
|
|
331
|
+
**kwargs
|
|
332
|
+
Additional keyword arguments to pass to the type object.
|
|
333
|
+
|
|
334
|
+
Returns
|
|
335
|
+
-------
|
|
336
|
+
`~sqlalchemy.types.TypeEngine`
|
|
337
|
+
The SQL type for Felis text.
|
|
338
|
+
"""
|
|
171
339
|
return _vary(types.TEXT(), text_map, kwargs)
|
|
172
340
|
|
|
173
341
|
|
|
174
342
|
def binary(length: builtins.int, **kwargs: Any) -> types.TypeEngine:
|
|
175
|
-
"""
|
|
343
|
+
"""Get the SQL type for Felis `~felis.types.Binary` with variants.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
length
|
|
348
|
+
The length of the binary field.
|
|
349
|
+
**kwargs
|
|
350
|
+
Additional keyword arguments to pass to the type object.
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
`~sqlalchemy.types.TypeEngine`
|
|
355
|
+
The SQL type for Felis binary.
|
|
356
|
+
"""
|
|
176
357
|
return _vary(types.BLOB(length), binary_map, kwargs, length)
|
|
177
358
|
|
|
178
359
|
|
|
179
360
|
def timestamp(**kwargs: Any) -> types.TypeEngine:
|
|
180
|
-
"""
|
|
361
|
+
"""Get the SQL type for Felis `~felis.types.Timestamp` with variants.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
**kwargs
|
|
366
|
+
Additional keyword arguments to pass to the type object.
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
`~sqlalchemy.types.TypeEngine`
|
|
371
|
+
The SQL type for a Felis timestamp.
|
|
372
|
+
"""
|
|
181
373
|
return types.TIMESTAMP()
|
|
182
374
|
|
|
183
375
|
|
|
184
376
|
def get_type_func(type_name: str) -> Callable:
|
|
185
|
-
"""
|
|
377
|
+
"""Find the function which creates a specific SQL type by its Felis type
|
|
378
|
+
name.
|
|
379
|
+
|
|
380
|
+
Parameters
|
|
381
|
+
----------
|
|
382
|
+
type_name
|
|
383
|
+
The name of the type function to get.
|
|
384
|
+
|
|
385
|
+
Returns
|
|
386
|
+
-------
|
|
387
|
+
`Callable`
|
|
388
|
+
The function for the type.
|
|
389
|
+
|
|
390
|
+
Raises
|
|
391
|
+
------
|
|
392
|
+
ValueError
|
|
393
|
+
If the type name is not recognized.
|
|
394
|
+
|
|
395
|
+
Notes
|
|
396
|
+
-----
|
|
397
|
+
This maps the type name to the function that creates the SQL type. This is
|
|
398
|
+
the main way to get the type functions from the type names.
|
|
399
|
+
"""
|
|
186
400
|
if type_name not in globals():
|
|
187
401
|
raise ValueError(f"Unknown type: {type_name}")
|
|
188
402
|
return globals()[type_name]
|
|
@@ -194,6 +408,31 @@ def _vary(
|
|
|
194
408
|
overrides: _TypeMap,
|
|
195
409
|
*args: Any,
|
|
196
410
|
) -> types.TypeEngine:
|
|
411
|
+
"""Add datatype variants and overrides to a SQLAlchemy type.
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
type_
|
|
416
|
+
The base SQLAlchemy type object. This is essentially a default
|
|
417
|
+
SQLAlchemy ``TypeEngine`` object, which will apply if there is no
|
|
418
|
+
variant or type override from the schema.
|
|
419
|
+
variant_map
|
|
420
|
+
The dictionary of dialects to types. Each key is a string representing
|
|
421
|
+
a dialect name, and each value is either an instance of
|
|
422
|
+
``TypeEngine`` representing the variant type object or a callable
|
|
423
|
+
reference to its class type that will be instantiated later.
|
|
424
|
+
overrides
|
|
425
|
+
The dictionary of dialects to types to override the defaults. Each key
|
|
426
|
+
is a string representing a dialect name and type with a similar
|
|
427
|
+
structure as the `variant_map`.
|
|
428
|
+
args
|
|
429
|
+
The extra arguments to pass to the type object.
|
|
430
|
+
|
|
431
|
+
Notes
|
|
432
|
+
-----
|
|
433
|
+
This function is intended for internal use only. It builds a SQLAlchemy
|
|
434
|
+
``TypeEngine`` that includes variants and overrides defined by Felis.
|
|
435
|
+
"""
|
|
197
436
|
variants: dict[str, types.TypeEngine | type[types.TypeEngine]] = dict(variant_map)
|
|
198
437
|
variants.update(overrides)
|
|
199
438
|
for dialect, variant in variants.items():
|