Coverage for python / felis / db / _sqltypes.py: 59%
68 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:37 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:37 +0000
1"""Map Felis types to SQLAlchemy types."""
3# This file is part of felis.
4#
5# Developed for the LSST Data Management System.
6# This product includes software developed by the LSST Project
7# (https://www.lsst.org).
8# See the COPYRIGHT file at the top-level directory of this distribution
9# for details of code ownership.
10#
11# This program is free software: you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation, either version 3 of the License, or
14# (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program. If not, see <https://www.gnu.org/licenses/>.
24from __future__ import annotations
26import builtins
27from collections.abc import Callable, Mapping
28from typing import Any
30from sqlalchemy import SmallInteger, types
31from sqlalchemy.dialects import mysql, postgresql
32from sqlalchemy.ext.compiler import compiles
34__all__ = [
35 "binary",
36 "boolean",
37 "byte",
38 "char",
39 "double",
40 "float",
41 "get_type_func",
42 "int",
43 "long",
44 "short",
45 "string",
46 "text",
47 "timestamp",
48 "unicode",
49]
51MYSQL = "mysql"
52POSTGRES = "postgresql"
53SQLITE = "sqlite"
56class TINYINT(SmallInteger):
57 """The non-standard TINYINT type."""
59 __visit_name__ = "TINYINT"
62@compiles(TINYINT)
63def compile_tinyint(type_: Any, compiler: Any, **kwargs: Any) -> str:
64 """Compile the non-standard ``TINYINT`` type to SQL.
66 Parameters
67 ----------
68 type_
69 The type object.
70 compiler
71 The compiler object.
72 **kwargs
73 Additional keyword arguments.
75 Returns
76 -------
77 `str`
78 The compiled SQL for TINYINT.
80 Notes
81 -----
82 This function returns the SQL for the the TINYINT type. The function
83 signature and parameters are defined by SQLAlchemy.
84 """
85 return "TINYINT"
88_TypeMap = Mapping[str, types.TypeEngine | type[types.TypeEngine]]
90boolean_map: _TypeMap = {MYSQL: mysql.BOOLEAN, POSTGRES: postgresql.BOOLEAN()}
92byte_map: _TypeMap = {
93 MYSQL: mysql.TINYINT(),
94 POSTGRES: postgresql.SMALLINT(),
95}
97short_map: _TypeMap = {
98 MYSQL: mysql.SMALLINT(),
99 POSTGRES: postgresql.SMALLINT(),
100}
102int_map: _TypeMap = {
103 MYSQL: mysql.INTEGER(),
104 POSTGRES: postgresql.INTEGER(),
105}
107long_map: _TypeMap = {
108 MYSQL: mysql.BIGINT(),
109 POSTGRES: postgresql.BIGINT(),
110}
112float_map: _TypeMap = {
113 MYSQL: mysql.FLOAT(),
114 POSTGRES: postgresql.FLOAT(),
115}
117double_map: _TypeMap = {
118 MYSQL: mysql.DOUBLE(),
119 POSTGRES: postgresql.DOUBLE_PRECISION(),
120}
122char_map: _TypeMap = {
123 MYSQL: mysql.CHAR,
124 POSTGRES: postgresql.CHAR,
125}
127string_map: _TypeMap = {
128 MYSQL: mysql.VARCHAR,
129 POSTGRES: postgresql.VARCHAR,
130}
132unicode_map: _TypeMap = {
133 MYSQL: mysql.NVARCHAR,
134 POSTGRES: postgresql.VARCHAR,
135}
137text_map: _TypeMap = {
138 MYSQL: mysql.LONGTEXT,
139 POSTGRES: postgresql.TEXT,
140}
142binary_map: _TypeMap = {
143 MYSQL: mysql.LONGBLOB,
144 POSTGRES: postgresql.BYTEA,
145}
147timestamp_map: _TypeMap = {
148 MYSQL: mysql.DATETIME(timezone=False),
149 POSTGRES: postgresql.TIMESTAMP(timezone=False),
150}
153def boolean(**kwargs: Any) -> types.TypeEngine:
154 """Get the SQL type for Felis `~felis.types.Boolean` with variants.
156 Parameters
157 ----------
158 **kwargs
159 Additional keyword arguments to pass to the type object.
161 Returns
162 -------
163 `~sqlalchemy.types.TypeEngine`
164 The SQL type for a Felis boolean.
165 """
166 return _vary(types.BOOLEAN(), boolean_map, kwargs)
169def byte(**kwargs: Any) -> types.TypeEngine:
170 """Get the SQL type for Felis `~felis.types.Byte` with variants.
172 Parameters
173 ----------
174 **kwargs
175 Additional keyword arguments to pass to the type object.
177 Returns
178 -------
179 `~sqlalchemy.types.TypeEngine`
180 The SQL type for a Felis byte.
181 """
182 return _vary(TINYINT(), byte_map, kwargs)
185def short(**kwargs: Any) -> types.TypeEngine:
186 """Get the SQL type for Felis `~felis.types.Short` with variants.
188 Parameters
189 ----------
190 **kwargs
191 Additional keyword arguments to pass to the type object.
193 Returns
194 -------
195 `~sqlalchemy.types.TypeEngine`
196 The SQL type for a Felis short.
197 """
198 return _vary(types.SMALLINT(), short_map, kwargs)
201def int(**kwargs: Any) -> types.TypeEngine:
202 """Get the SQL type for Felis `~felis.types.Int` with variants.
204 Parameters
205 ----------
206 **kwargs
207 Additional keyword arguments to pass to the type object.
209 Returns
210 -------
211 `~sqlalchemy.types.TypeEngine`
212 The SQL type for a Felis int.
213 """
214 return _vary(types.INTEGER(), int_map, kwargs)
217def long(**kwargs: Any) -> types.TypeEngine:
218 """Get the SQL type for Felis `~felis.types.Long` with variants.
220 Parameters
221 ----------
222 **kwargs
223 Additional keyword arguments to pass to the type object.
225 Returns
226 -------
227 `~sqlalchemy.types.TypeEngine`
228 The SQL type for a Felis long.
229 """
230 return _vary(types.BIGINT(), long_map, kwargs)
233def float(**kwargs: Any) -> types.TypeEngine:
234 """Get the SQL type for Felis `~felis.types.Float` with variants.
236 Parameters
237 ----------
238 **kwargs
239 Additional keyword arguments to pass to the type object.
241 Returns
242 -------
243 `~sqlalchemy.types.TypeEngine`
244 The SQL type for a Felis float.
245 """
246 return _vary(types.FLOAT(), float_map, kwargs)
249def double(**kwargs: Any) -> types.TypeEngine:
250 """Get the SQL type for Felis `~felis.types.Double` with variants.
252 Parameters
253 ----------
254 **kwargs
255 Additional keyword arguments to pass to the type object.
257 Returns
258 -------
259 `~sqlalchemy.types.TypeEngine`
260 The SQL type for a Felis double.
261 """
262 return _vary(types.DOUBLE(), double_map, kwargs)
265def char(length: builtins.int, **kwargs: Any) -> types.TypeEngine:
266 """Get the SQL type for Felis `~felis.types.Char` with variants.
268 Parameters
269 ----------
270 length
271 The length of the character field.
272 **kwargs
273 Additional keyword arguments to pass to the type object.
275 Returns
276 -------
277 `~sqlalchemy.types.TypeEngine`
278 The SQL type for a Felis char.
279 """
280 return _vary(types.CHAR(length), char_map, kwargs, length)
283def string(length: builtins.int, **kwargs: Any) -> types.TypeEngine:
284 """Get the SQL type for Felis `~felis.types.String` with variants.
286 Parameters
287 ----------
288 length
289 The length of the string field.
290 **kwargs
291 Additional keyword arguments to pass to the type object.
293 Returns
294 -------
295 `~sqlalchemy.types.TypeEngine`
296 The SQL type for a Felis string.
297 """
298 return _vary(types.VARCHAR(length), string_map, kwargs, length)
301def unicode(length: builtins.int, **kwargs: Any) -> types.TypeEngine:
302 """Get the SQL type for Felis `~felis.types.Unicode` with variants.
304 Parameters
305 ----------
306 length
307 The length of the unicode string field.
308 **kwargs
309 Additional keyword arguments to pass to the type object.
311 Returns
312 -------
313 `~sqlalchemy.types.TypeEngine`
314 The SQL type for a Felis unicode string.
315 """
316 return _vary(types.NVARCHAR(length), unicode_map, kwargs, length)
319def text(**kwargs: Any) -> types.TypeEngine:
320 """Get the SQL type for Felis `~felis.types.Text` with variants.
322 Parameters
323 ----------
324 **kwargs
325 Additional keyword arguments to pass to the type object.
327 Returns
328 -------
329 `~sqlalchemy.types.TypeEngine`
330 The SQL type for Felis text.
331 """
332 return _vary(types.TEXT(), text_map, kwargs)
335def binary(length: builtins.int, **kwargs: Any) -> types.TypeEngine:
336 """Get the SQL type for Felis `~felis.types.Binary` with variants.
338 Parameters
339 ----------
340 length
341 The length of the binary field.
342 **kwargs
343 Additional keyword arguments to pass to the type object.
345 Returns
346 -------
347 `~sqlalchemy.types.TypeEngine`
348 The SQL type for Felis binary.
349 """
350 return _vary(types.BLOB(length), binary_map, kwargs, length)
353def timestamp(**kwargs: Any) -> types.TypeEngine:
354 """Get the SQL type for Felis `~felis.types.Timestamp` with variants.
356 Parameters
357 ----------
358 **kwargs
359 Additional keyword arguments to pass to the type object.
361 Returns
362 -------
363 `~sqlalchemy.types.TypeEngine`
364 The SQL type for a Felis timestamp.
365 """
366 return _vary(types.TIMESTAMP(timezone=False), timestamp_map, kwargs)
369def get_type_func(type_name: str) -> Callable:
370 """Find the function which creates a specific SQL type by its Felis type
371 name.
373 Parameters
374 ----------
375 type_name
376 The name of the type function to get.
378 Returns
379 -------
380 `Callable`
381 The function for the type.
383 Raises
384 ------
385 ValueError
386 Raised if the type name is not recognized.
388 Notes
389 -----
390 This maps the type name to the function that creates the SQL type. This is
391 the main way to get the type functions from the type names.
392 """
393 if type_name not in globals():
394 raise ValueError(f"Unknown type: {type_name}")
395 return globals()[type_name]
398def _vary(
399 type_: types.TypeEngine,
400 variant_map: _TypeMap,
401 overrides: _TypeMap,
402 *args: Any,
403) -> types.TypeEngine:
404 """Add datatype variants and overrides to a SQLAlchemy type.
406 Parameters
407 ----------
408 type_
409 The base SQLAlchemy type object. This is essentially a default
410 SQLAlchemy ``TypeEngine`` object, which will apply if there is no
411 variant or type override from the schema.
412 variant_map
413 The dictionary of dialects to types. Each key is a string representing
414 a dialect name, and each value is either an instance of
415 ``TypeEngine`` representing the variant type object or a callable
416 reference to its class type that will be instantiated later.
417 overrides
418 The dictionary of dialects to types to override the defaults. Each key
419 is a string representing a dialect name and type with a similar
420 structure as the `variant_map`.
421 args
422 The extra arguments to pass to the type object.
424 Notes
425 -----
426 This function is intended for internal use only. It builds a SQLAlchemy
427 ``TypeEngine`` that includes variants and overrides defined by Felis.
428 """
429 variants: dict[str, types.TypeEngine | type[types.TypeEngine]] = dict(variant_map)
430 variants.update(overrides)
431 for dialect, variant in variants.items():
432 # If this is a class and not an instance, instantiate
433 if callable(variant):
434 variant = variant(*args)
435 type_ = type_.with_variant(variant, dialect)
436 return type_