sql-athame 0.4.0a6__py3-none-any.whl → 0.4.0a8__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.
sql_athame/dataclasses.py CHANGED
@@ -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 = False
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._constraints,
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, tuple[str, bool]] = {
65
- Optional[bool]: ("BOOLEAN", True),
66
- Optional[bytes]: ("BYTEA", True),
67
- Optional[datetime.date]: ("DATE", True),
68
- Optional[datetime.datetime]: ("TIMESTAMP", True),
69
- Optional[float]: ("DOUBLE PRECISION", True),
70
- Optional[int]: ("INTEGER", True),
71
- Optional[str]: ("TEXT", True),
72
- Optional[uuid.UUID]: ("UUID", True),
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, ColumnInfo]]
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) -> ColumnInfo:
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.__origin__ # type: ignore
146
- for md in type_info.__metadata__: # type: ignore
147
- if isinstance(md, ColumnInfo):
148
- return md
149
- type, nullable = sql_type_map[base_type]
150
- return ColumnInfo(type=type, nullable=nullable)
151
-
152
- @classmethod
153
- def column_info(cls, column: str) -> ColumnInfo:
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql-athame
3
- Version: 0.4.0a6
3
+ Version: 0.4.0a8
4
4
  Summary: Python tool for slicing and dicing SQL
5
5
  Home-page: https://github.com/bdowning/sql-athame
6
6
  License: MIT
@@ -1,11 +1,11 @@
1
1
  sql_athame/__init__.py,sha256=7OBIMZOcrD2pvfIL-rjD1IGZ3TNQbwyu76a9PWk-yYg,79
2
2
  sql_athame/base.py,sha256=FR7EmC0VkX1VRgvAutSEfYSWhlEYpoqS1Kqxp1jHp6Y,10293
3
- sql_athame/dataclasses.py,sha256=qb4EESR6J-iv6UScktMLuKAwH3ZA3IOwCM0v6oMv8Q8,20848
3
+ sql_athame/dataclasses.py,sha256=NRPGOlqaTMj49B4gbvC5nCRrFZFrivoRdhSmG8VTkAg,22167
4
4
  sql_athame/escape.py,sha256=kK101xXeFitlvuG-L_hvhdpgGJCtmRTprsn1yEfZKws,758
5
5
  sql_athame/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  sql_athame/sqlalchemy.py,sha256=aWopfPh3j71XwKmcN_VcHRNlhscI0Sckd4AiyGf8Tpw,1293
7
7
  sql_athame/types.py,sha256=FQ06l9Uc-vo57UrAarvnukILdV2gN1IaYUnHJ_bNYic,475
8
- sql_athame-0.4.0a6.dist-info/LICENSE,sha256=xqV29vPFqITcKifYrGPgVIBjq4fdmLSwY3gRUtDKafg,1076
9
- sql_athame-0.4.0a6.dist-info/METADATA,sha256=8Ov07iCKPAo35uWW3t9WRRlXIgRlNGGrMpyXnOI6TRs,12845
10
- sql_athame-0.4.0a6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
11
- sql_athame-0.4.0a6.dist-info/RECORD,,
8
+ sql_athame-0.4.0a8.dist-info/LICENSE,sha256=xqV29vPFqITcKifYrGPgVIBjq4fdmLSwY3gRUtDKafg,1076
9
+ sql_athame-0.4.0a8.dist-info/METADATA,sha256=dXFzs8L8wxnzsjKcL8jj3NyMJPi6l5tIbdZ01ALkzc8,12845
10
+ sql_athame-0.4.0a8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
11
+ sql_athame-0.4.0a8.dist-info/RECORD,,