sql-athame 0.4.0a6__tar.gz → 0.4.0a8__tar.gz
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.
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/PKG-INFO +1 -1
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/pyproject.toml +1 -1
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/sql_athame/dataclasses.py +78 -35
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/LICENSE +0 -0
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/README.md +0 -0
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/sql_athame/__init__.py +0 -0
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/sql_athame/base.py +0 -0
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/sql_athame/escape.py +0 -0
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/sql_athame/py.typed +0 -0
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/sql_athame/sqlalchemy.py +0 -0
- {sql_athame-0.4.0a6 → sql_athame-0.4.0a8}/sql_athame/types.py +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
import datetime
|
2
|
+
import functools
|
2
3
|
import uuid
|
3
4
|
from collections.abc import AsyncGenerator, Iterable, Mapping
|
4
5
|
from dataclasses import Field, InitVar, dataclass, fields
|
@@ -9,6 +10,7 @@ from typing import (
|
|
9
10
|
Optional,
|
10
11
|
TypeVar,
|
11
12
|
Union,
|
13
|
+
get_args,
|
12
14
|
get_origin,
|
13
15
|
get_type_hints,
|
14
16
|
)
|
@@ -29,31 +31,76 @@ Pool: TypeAlias = Any
|
|
29
31
|
|
30
32
|
@dataclass
|
31
33
|
class ColumnInfo:
|
32
|
-
type: str
|
33
|
-
create_type: str =
|
34
|
-
nullable: bool =
|
34
|
+
type: Optional[str] = None
|
35
|
+
create_type: Optional[str] = None
|
36
|
+
nullable: Optional[bool] = None
|
35
37
|
_constraints: tuple[str, ...] = ()
|
36
38
|
|
37
39
|
constraints: InitVar[Union[str, Iterable[str], None]] = None
|
38
40
|
|
39
41
|
def __post_init__(self, constraints: Union[str, Iterable[str], None]) -> None:
|
40
|
-
if self.create_type == "":
|
41
|
-
self.create_type = self.type
|
42
|
-
self.type = sql_create_type_map.get(self.type.upper(), self.type)
|
43
42
|
if constraints is not None:
|
44
43
|
if type(constraints) is str:
|
45
44
|
constraints = (constraints,)
|
46
45
|
self._constraints = tuple(constraints)
|
47
46
|
|
47
|
+
@staticmethod
|
48
|
+
def merge(a: "ColumnInfo", b: "ColumnInfo") -> "ColumnInfo":
|
49
|
+
return ColumnInfo(
|
50
|
+
type=b.type if b.type is not None else a.type,
|
51
|
+
create_type=b.create_type if b.create_type is not None else a.create_type,
|
52
|
+
nullable=b.nullable if b.nullable is not None else a.nullable,
|
53
|
+
_constraints=(*a._constraints, *b._constraints),
|
54
|
+
)
|
55
|
+
|
56
|
+
|
57
|
+
@dataclass
|
58
|
+
class ConcreteColumnInfo:
|
59
|
+
type: str
|
60
|
+
create_type: str
|
61
|
+
nullable: bool
|
62
|
+
constraints: tuple[str, ...]
|
63
|
+
|
64
|
+
@staticmethod
|
65
|
+
def from_column_info(name: str, *args: ColumnInfo) -> "ConcreteColumnInfo":
|
66
|
+
info = functools.reduce(ColumnInfo.merge, args, ColumnInfo())
|
67
|
+
if info.create_type is None and info.type is not None:
|
68
|
+
info.create_type = info.type
|
69
|
+
info.type = sql_create_type_map.get(info.type.upper(), info.type)
|
70
|
+
if type(info.type) is not str or type(info.create_type) is not str:
|
71
|
+
raise ValueError(f"Missing SQL type for column {name!r}")
|
72
|
+
return ConcreteColumnInfo(
|
73
|
+
type=info.type,
|
74
|
+
create_type=info.create_type,
|
75
|
+
nullable=bool(info.nullable),
|
76
|
+
constraints=info._constraints,
|
77
|
+
)
|
78
|
+
|
48
79
|
def create_table_string(self) -> str:
|
49
80
|
parts = (
|
50
81
|
self.create_type,
|
51
82
|
*(() if self.nullable else ("NOT NULL",)),
|
52
|
-
*self.
|
83
|
+
*self.constraints,
|
53
84
|
)
|
54
85
|
return " ".join(parts)
|
55
86
|
|
56
87
|
|
88
|
+
NULLABLE_TYPES = (type(None), Any, object)
|
89
|
+
|
90
|
+
|
91
|
+
def split_nullable(typ: type) -> tuple[bool, type]:
|
92
|
+
nullable = typ in NULLABLE_TYPES
|
93
|
+
if get_origin(typ) is Union:
|
94
|
+
args = []
|
95
|
+
for arg in get_args(typ):
|
96
|
+
if arg in NULLABLE_TYPES:
|
97
|
+
nullable = True
|
98
|
+
else:
|
99
|
+
args.append(arg)
|
100
|
+
return nullable, Union[tuple(args)] # type: ignore
|
101
|
+
return nullable, typ
|
102
|
+
|
103
|
+
|
57
104
|
sql_create_type_map = {
|
58
105
|
"BIGSERIAL": "BIGINT",
|
59
106
|
"SERIAL": "INTEGER",
|
@@ -61,23 +108,15 @@ sql_create_type_map = {
|
|
61
108
|
}
|
62
109
|
|
63
110
|
|
64
|
-
sql_type_map: dict[Any,
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
bool: ("BOOLEAN", False),
|
74
|
-
bytes: ("BYTEA", False),
|
75
|
-
datetime.date: ("DATE", False),
|
76
|
-
datetime.datetime: ("TIMESTAMP", False),
|
77
|
-
float: ("DOUBLE PRECISION", False),
|
78
|
-
int: ("INTEGER", False),
|
79
|
-
str: ("TEXT", False),
|
80
|
-
uuid.UUID: ("UUID", False),
|
111
|
+
sql_type_map: dict[Any, str] = {
|
112
|
+
bool: "BOOLEAN",
|
113
|
+
bytes: "BYTEA",
|
114
|
+
datetime.date: "DATE",
|
115
|
+
datetime.datetime: "TIMESTAMP",
|
116
|
+
float: "DOUBLE PRECISION",
|
117
|
+
int: "INTEGER",
|
118
|
+
str: "TEXT",
|
119
|
+
uuid.UUID: "UUID",
|
81
120
|
}
|
82
121
|
|
83
122
|
|
@@ -86,7 +125,7 @@ U = TypeVar("U")
|
|
86
125
|
|
87
126
|
|
88
127
|
class ModelBase:
|
89
|
-
_column_info: Optional[dict[str,
|
128
|
+
_column_info: Optional[dict[str, ConcreteColumnInfo]]
|
90
129
|
_cache: dict[tuple, Any]
|
91
130
|
table_name: str
|
92
131
|
primary_key_names: tuple[str, ...]
|
@@ -138,19 +177,23 @@ class ModelBase:
|
|
138
177
|
return cls._type_hints
|
139
178
|
|
140
179
|
@classmethod
|
141
|
-
def column_info_for_field(cls, field: Field) ->
|
180
|
+
def column_info_for_field(cls, field: Field) -> ConcreteColumnInfo:
|
142
181
|
type_info = cls.type_hints()[field.name]
|
143
182
|
base_type = type_info
|
183
|
+
metadata = []
|
144
184
|
if get_origin(type_info) is Annotated:
|
145
|
-
base_type = type_info
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
185
|
+
base_type, *metadata = get_args(type_info)
|
186
|
+
nullable, base_type = split_nullable(base_type)
|
187
|
+
info = [ColumnInfo(nullable=nullable)]
|
188
|
+
if base_type in sql_type_map:
|
189
|
+
info.append(ColumnInfo(type=sql_type_map[base_type]))
|
190
|
+
for md in metadata:
|
191
|
+
if isinstance(md, ColumnInfo):
|
192
|
+
info.append(md)
|
193
|
+
return ConcreteColumnInfo.from_column_info(field.name, *info)
|
194
|
+
|
195
|
+
@classmethod
|
196
|
+
def column_info(cls, column: str) -> ConcreteColumnInfo:
|
154
197
|
try:
|
155
198
|
return cls._column_info[column] # type: ignore
|
156
199
|
except AttributeError:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|