sqla-fancy-core 1.0.0__py3-none-any.whl → 1.2.2__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 sqla-fancy-core might be problematic. Click here for more details.

@@ -1,159 +1,11 @@
1
1
  """SQLAlchemy core, but fancier."""
2
2
 
3
- from typing import Optional, Union, overload
4
-
5
- import sqlalchemy as sa
6
-
7
-
8
- class TableFactory:
9
- """A factory for creating SQLAlchemy columns with default values."""
10
-
11
- def __init__(self, metadata: Optional[sa.MetaData] = None):
12
- """Initialize the factory with default values."""
13
- if metadata is None:
14
- metadata = sa.MetaData()
15
- self.metadata = metadata
16
- self.c = []
17
-
18
- def col(self, *args, **kwargs) -> sa.Column:
19
- col = sa.Column(*args, **kwargs)
20
- self.c.append(col)
21
- return col
22
-
23
- def integer(self, name: str, *args, **kwargs) -> sa.Column:
24
- return self.col(name, sa.Integer, *args, **kwargs)
25
-
26
- def string(self, name: str, *args, **kwargs) -> sa.Column:
27
- return self.col(name, sa.String, *args, **kwargs)
28
-
29
- def text(self, name: str, *args, **kwargs) -> sa.Column:
30
- return self.col(name, sa.Text, *args, **kwargs)
31
-
32
- def float(self, name: str, *args, **kwargs) -> sa.Column:
33
- return self.col(name, sa.Float, *args, **kwargs)
34
-
35
- def numeric(self, name: str, *args, **kwargs) -> sa.Column:
36
- return self.col(name, sa.Numeric, *args, **kwargs)
37
-
38
- def bigint(self, name: str, *args, **kwargs) -> sa.Column:
39
- return self.col(name, sa.BigInteger, *args, **kwargs)
40
-
41
- def smallint(self, name: str, *args, **kwargs) -> sa.Column:
42
- return self.col(name, sa.SmallInteger, *args, **kwargs)
43
-
44
- def timestamp(self, name: str, *args, **kwargs) -> sa.Column:
45
- return self.col(name, sa.TIMESTAMP, *args, **kwargs)
46
-
47
- def date(self, name: str, *args, **kwargs) -> sa.Column:
48
- return self.col(name, sa.Date, *args, **kwargs)
49
-
50
- def datetime(self, name: str, *args, **kwargs) -> sa.Column:
51
- return self.col(name, sa.DateTime, *args, **kwargs)
52
-
53
- def today(self, name: str, *args, **kwargs) -> sa.Column:
54
- return self.date(name, default=sa.func.now(), *args, **kwargs)
55
-
56
- def time(self, name: str, *args, **kwargs) -> sa.Column:
57
- return self.col(name, sa.Time, *args, **kwargs)
58
-
59
- def timenow(self, name: str, *args, **kwargs) -> sa.Column:
60
- return self.time(name, default=sa.func.now(), *args, **kwargs)
61
-
62
- def now(self, name: str, *args, **kwargs) -> sa.Column:
63
- return self.datetime(name, default=sa.func.now(), *args, **kwargs)
64
-
65
- def boolean(self, name: str, *args, **kwargs) -> sa.Column:
66
- return self.col(name, sa.Boolean, *args, **kwargs)
67
-
68
- def true(self, name: str, *args, **kwargs):
69
- return self.boolean(name, default=True, *args, **kwargs)
70
-
71
- def false(self, name: str, *args, **kwargs):
72
- return self.boolean(name, default=False, *args, **kwargs)
73
-
74
- def foreign_key(self, name: str, ref: Union[str, sa.Column], *args, **kwargs):
75
- return self.col(name, sa.ForeignKey(ref), *args, **kwargs)
76
-
77
- def enum(self, name: str, enum: type, *args, **kwargs) -> sa.Column:
78
- return self.col(name, sa.Enum(enum), *args, **kwargs)
79
-
80
- def json(self, name: str, *args, **kwargs) -> sa.Column:
81
- return self.col(name, sa.JSON, *args, **kwargs)
82
-
83
- def array(self, name: str, *args, **kwargs) -> sa.Column:
84
- return self.col(name, sa.ARRAY, *args, **kwargs)
85
-
86
- def array_int(self, name: str, *args, **kwargs) -> sa.Column:
87
- return self.array(name, sa.Integer, *args, **kwargs)
88
-
89
- def array_str(self, name: str, *args, **kwargs) -> sa.Column:
90
- return self.array(name, sa.String, *args, **kwargs)
91
-
92
- def array_text(self, name: str, *args, **kwargs) -> sa.Column:
93
- return self.array(name, sa.Text, *args, **kwargs)
94
-
95
- def array_float(self, name: str, *args, **kwargs) -> sa.Column:
96
- return self.array(name, sa.Float, *args, **kwargs)
97
-
98
- def array_numeric(self, name: str, *args, **kwargs) -> sa.Column:
99
- return self.array(name, sa.Numeric, *args, **kwargs)
100
-
101
- def array_bigint(self, name: str, *args, **kwargs) -> sa.Column:
102
- return self.array(name, sa.BigInteger, *args, **kwargs)
103
-
104
- def array_smallint(self, name: str, *args, **kwargs) -> sa.Column:
105
- return self.array(name, sa.SmallInteger, *args, **kwargs)
106
-
107
- def array_timestamp(self, name: str, *args, **kwargs) -> sa.Column:
108
- return self.array(name, sa.TIMESTAMP, *args, **kwargs)
109
-
110
- def array_date(self, name: str, *args, **kwargs) -> sa.Column:
111
- return self.array(name, sa.Date, *args, **kwargs)
112
-
113
- def array_datetime(self, name: str, *args, **kwargs) -> sa.Column:
114
- return self.array(name, sa.DateTime, *args, **kwargs)
115
-
116
- def array_time(self, name: str, *args, **kwargs) -> sa.Column:
117
- return self.array(name, sa.Time, *args, **kwargs)
118
-
119
- def array_boolean(self, name: str, *args, **kwargs) -> sa.Column:
120
- return self.array(name, sa.Boolean, *args, **kwargs)
121
-
122
- def array_enum(self, name: str, enum: type, *args, **kwargs) -> sa.Column:
123
- return self.array(name, sa.Enum(enum), *args, **kwargs)
124
-
125
- def auto_id(self, name="id", *args, **kwargs) -> sa.Column:
126
- return self.integer(
127
- name, primary_key=True, index=True, autoincrement=True, *args, **kwargs
128
- )
129
-
130
- def updated_at(self, name="updated_at", *args, **kwargs) -> sa.Column:
131
- return self.datetime(
132
- name, default=sa.func.now(), onupdate=sa.func.now(), *args, **kwargs
133
- )
134
-
135
- def created_at(self, name="created_at", *args, **kwargs) -> sa.Column:
136
- return self.datetime(name, default=sa.func.now(), *args, **kwargs)
137
-
138
- @overload
139
- def __call__(self, arg1: str, *args, **kwargs) -> sa.Table: ...
140
- @overload
141
- def __call__(self, arg1: sa.Column, *args, **kwargs) -> sa.Column: ...
142
- @overload
143
- def __call__(self, arg1: sa.Table, *args, **kwargs) -> sa.Table: ...
144
- def __call__(self, arg1, *args, **kwargs):
145
- if isinstance(arg1, str):
146
- cols = self.c
147
- self.c = []
148
- return sa.Table(arg1, self.metadata, *args, *cols, **kwargs)
149
- elif isinstance(arg1, sa.Column):
150
- arg1.info["args"] = args
151
- arg1.info["kwargs"] = kwargs
152
- self.c.append(arg1)
153
- return arg1
154
- elif isinstance(arg1, sa.Table):
155
- cols = self.c
156
- self.c = []
157
- return sa.Table(arg1.name, self.metadata, *args, *cols, **kwargs)
158
- else:
159
- raise TypeError(f"Expected a string or Column, got {type(arg1).__name__}")
3
+ from sqla_fancy_core.factories import TableFactory # noqa
4
+ from sqla_fancy_core.wrappers import ( # noqa
5
+ FancyEngineWrapper,
6
+ AsyncFancyEngineWrapper,
7
+ fancy,
8
+ FancyError,
9
+ AtomicContextError,
10
+ )
11
+ from sqla_fancy_core.decorators import transact, Inject # noqa
@@ -0,0 +1,196 @@
1
+ """Some decorators for fun times with SQLAlchemy core."""
2
+
3
+ import functools
4
+ import inspect
5
+ from typing import Union, overload
6
+
7
+ import sqlalchemy as sa
8
+ from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine
9
+
10
+ from sqla_fancy_core.wrappers import AsyncFancyEngineWrapper, FancyEngineWrapper
11
+
12
+ EngineType = Union[sa.Engine, AsyncEngine, FancyEngineWrapper, AsyncFancyEngineWrapper]
13
+
14
+
15
+ class _Injectable:
16
+ def __init__(self, engine: EngineType):
17
+ self.engine = engine
18
+
19
+
20
+ @overload
21
+ def Inject(engine: Union[sa.Engine, FancyEngineWrapper]) -> sa.Connection: ...
22
+ @overload
23
+ def Inject(engine: Union[AsyncEngine, AsyncFancyEngineWrapper]) -> AsyncConnection: ...
24
+ def Inject(engine: EngineType): # type: ignore
25
+ """A marker class for dependency injection."""
26
+ return _Injectable(engine)
27
+
28
+
29
+ def transact(func):
30
+ """A decorator that provides a transactional context.
31
+
32
+ If the decorated function is called with a connection object, that
33
+ connection is used. Otherwise, a new transaction is started from the
34
+ engine, and the new connection is injected to the function.
35
+
36
+ Example: ::
37
+ @transact
38
+ def create_user(name: str, conn: sa.Connection = Inject(engine)):
39
+ conn.execute(...)
40
+
41
+ # This will create a new transaction
42
+ create_user("test")
43
+
44
+ # This will use the existing connection
45
+ with engine.connect() as conn:
46
+ create_user(name="existing", conn=conn)
47
+ """
48
+
49
+ sig = inspect.signature(func)
50
+ inject_param_name = None
51
+ for name, param in sig.parameters.items():
52
+ if param.default is not inspect.Parameter.empty and isinstance(
53
+ param.default, _Injectable
54
+ ):
55
+ inject_param_name = name
56
+ break
57
+ if inject_param_name is None:
58
+ return func # No injection needed
59
+
60
+ engine_arg = sig.parameters[inject_param_name].default.engine
61
+ if isinstance(engine_arg, sa.Engine):
62
+ is_async = False
63
+ engine = engine_arg
64
+ elif isinstance(engine_arg, FancyEngineWrapper):
65
+ is_async = False
66
+ engine = engine_arg.engine
67
+ elif isinstance(engine_arg, AsyncEngine):
68
+ is_async = True
69
+ engine = engine_arg
70
+ elif isinstance(engine_arg, AsyncFancyEngineWrapper):
71
+ is_async = True
72
+ engine = engine_arg.engine
73
+ else:
74
+ raise TypeError("Unsupported engine type")
75
+
76
+ if is_async:
77
+
78
+ @functools.wraps(func)
79
+ async def async_wrapper(*args, **kwargs):
80
+ conn = kwargs.get(inject_param_name)
81
+ if isinstance(conn, AsyncConnection):
82
+ if conn.in_transaction():
83
+ return await func(*args, **kwargs)
84
+ else:
85
+ async with conn.begin():
86
+ return await func(*args, **kwargs)
87
+ elif isinstance(conn, sa.Connection):
88
+ if conn.in_transaction():
89
+ return await func(*args, **kwargs)
90
+ else:
91
+ with conn.begin():
92
+ return await func(*args, **kwargs)
93
+ else:
94
+ async with engine.begin() as conn: # type: ignore
95
+ kwargs[inject_param_name] = conn
96
+ return await func(*args, **kwargs)
97
+
98
+ return async_wrapper
99
+
100
+ else:
101
+
102
+ @functools.wraps(func)
103
+ def sync_wrapper(*args, **kwargs):
104
+ conn = kwargs.get(inject_param_name)
105
+ if isinstance(conn, sa.Connection):
106
+ if conn.in_transaction():
107
+ return func(*args, **kwargs)
108
+ else:
109
+ with conn.begin():
110
+ return func(*args, **kwargs)
111
+ elif isinstance(conn, AsyncConnection):
112
+ raise TypeError("AsyncConnection cannot be used in sync function")
113
+ else:
114
+ with engine.begin() as conn: # type: ignore
115
+ kwargs[inject_param_name] = conn
116
+ return func(*args, **kwargs)
117
+
118
+ return sync_wrapper
119
+
120
+
121
+ def connect(func):
122
+ """A decorator that provides a connection context.
123
+
124
+ If the decorated function is called with a connection object, that
125
+ connection is used. Otherwise, a new connection is created from the
126
+ engine, and the new connection is injected to the function.
127
+
128
+ Example: ::
129
+ @connect
130
+ def get_user_count(conn: sa.Connection = Inject(engine)) -> int:
131
+ return conn.execute(...).scalar_one()
132
+
133
+ # This will create a new connection
134
+ count = get_user_count()
135
+
136
+ # This will use the existing connection
137
+ with engine.connect() as conn:
138
+ count = get_user_count(conn)
139
+ """
140
+
141
+ sig = inspect.signature(func)
142
+ inject_param_name = None
143
+ for name, param in sig.parameters.items():
144
+ if param.default is not inspect.Parameter.empty and isinstance(
145
+ param.default, _Injectable
146
+ ):
147
+ inject_param_name = name
148
+ break
149
+ if inject_param_name is None:
150
+ return func # No injection needed
151
+
152
+ engine_arg = sig.parameters[inject_param_name].default.engine
153
+ if isinstance(engine_arg, sa.Engine):
154
+ is_async = False
155
+ engine = engine_arg
156
+ elif isinstance(engine_arg, FancyEngineWrapper):
157
+ is_async = False
158
+ engine = engine_arg.engine
159
+ elif isinstance(engine_arg, AsyncEngine):
160
+ is_async = True
161
+ engine = engine_arg
162
+ elif isinstance(engine_arg, AsyncFancyEngineWrapper):
163
+ is_async = True
164
+ engine = engine_arg.engine
165
+ else:
166
+ raise TypeError("Unsupported engine type")
167
+
168
+ if is_async:
169
+
170
+ @functools.wraps(func)
171
+ async def async_wrapper(*args, **kwargs):
172
+ conn = kwargs.get(inject_param_name)
173
+ if isinstance(conn, (AsyncConnection, sa.Connection)):
174
+ return await func(*args, **kwargs)
175
+ else:
176
+ async with engine.connect() as conn: # type: ignore
177
+ kwargs[inject_param_name] = conn
178
+ return await func(*args, **kwargs)
179
+
180
+ return async_wrapper
181
+
182
+ else:
183
+
184
+ @functools.wraps(func)
185
+ def sync_wrapper(*args, **kwargs):
186
+ conn = kwargs.get(inject_param_name)
187
+ if isinstance(conn, sa.Connection):
188
+ return func(*args, **kwargs)
189
+ elif isinstance(conn, AsyncConnection):
190
+ raise TypeError("AsyncConnection cannot be used in sync function")
191
+ else:
192
+ with engine.connect() as conn: # type: ignore
193
+ kwargs[inject_param_name] = conn
194
+ return func(*args, **kwargs)
195
+
196
+ return sync_wrapper
@@ -0,0 +1,156 @@
1
+ """Some factories for fun times with SQLAlchemy core."""
2
+
3
+ from typing import Optional, Union, overload
4
+
5
+ import sqlalchemy as sa
6
+
7
+
8
+ class TableFactory:
9
+ """A factory for creating SQLAlchemy columns with default values."""
10
+
11
+ def __init__(self, metadata: Optional[sa.MetaData] = None):
12
+ """Initialize the factory with default values."""
13
+ if metadata is None:
14
+ self.metadata = sa.MetaData()
15
+ else:
16
+ self.metadata = metadata
17
+ self.c = []
18
+
19
+ def col(self, *args, **kwargs) -> sa.Column:
20
+ col = sa.Column(*args, **kwargs)
21
+ return self(col)
22
+
23
+ def integer(self, name: str, *args, **kwargs) -> sa.Column:
24
+ return self.col(name, sa.Integer, *args, **kwargs)
25
+
26
+ def string(self, name: str, *args, **kwargs) -> sa.Column:
27
+ return self.col(name, sa.String, *args, **kwargs)
28
+
29
+ def text(self, name: str, *args, **kwargs) -> sa.Column:
30
+ return self.col(name, sa.Text, *args, **kwargs)
31
+
32
+ def float(self, name: str, *args, **kwargs) -> sa.Column:
33
+ return self.col(name, sa.Float, *args, **kwargs)
34
+
35
+ def numeric(self, name: str, *args, **kwargs) -> sa.Column:
36
+ return self.col(name, sa.Numeric, *args, **kwargs)
37
+
38
+ def bigint(self, name: str, *args, **kwargs) -> sa.Column:
39
+ return self.col(name, sa.BigInteger, *args, **kwargs)
40
+
41
+ def smallint(self, name: str, *args, **kwargs) -> sa.Column:
42
+ return self.col(name, sa.SmallInteger, *args, **kwargs)
43
+
44
+ def timestamp(self, name: str, *args, **kwargs) -> sa.Column:
45
+ return self.col(name, sa.TIMESTAMP, *args, **kwargs)
46
+
47
+ def date(self, name: str, *args, **kwargs) -> sa.Column:
48
+ return self.col(name, sa.Date, *args, **kwargs)
49
+
50
+ def datetime(self, name: str, *args, **kwargs) -> sa.Column:
51
+ return self.col(name, sa.DateTime, *args, **kwargs)
52
+
53
+ def today(self, name: str, *args, **kwargs) -> sa.Column:
54
+ return self.date(name, default=sa.func.now(), *args, **kwargs)
55
+
56
+ def time(self, name: str, *args, **kwargs) -> sa.Column:
57
+ return self.col(name, sa.Time, *args, **kwargs)
58
+
59
+ def timenow(self, name: str, *args, **kwargs) -> sa.Column:
60
+ return self.time(name, default=sa.func.now(), *args, **kwargs)
61
+
62
+ def now(self, name: str, *args, **kwargs) -> sa.Column:
63
+ return self.datetime(name, default=sa.func.now(), *args, **kwargs)
64
+
65
+ def boolean(self, name: str, *args, **kwargs) -> sa.Column:
66
+ return self.col(name, sa.Boolean, *args, **kwargs)
67
+
68
+ def true(self, name: str, *args, **kwargs):
69
+ return self.boolean(name, default=True, *args, **kwargs)
70
+
71
+ def false(self, name: str, *args, **kwargs):
72
+ return self.boolean(name, default=False, *args, **kwargs)
73
+
74
+ def foreign_key(self, name: str, ref: Union[str, sa.Column], *args, **kwargs):
75
+ return self.col(name, sa.ForeignKey(ref), *args, **kwargs)
76
+
77
+ def enum(self, name: str, enum: type, *args, **kwargs) -> sa.Column:
78
+ return self.col(name, sa.Enum(enum), *args, **kwargs)
79
+
80
+ def json(self, name: str, *args, **kwargs) -> sa.Column:
81
+ return self.col(name, sa.JSON, *args, **kwargs)
82
+
83
+ def array(self, name: str, *args, **kwargs) -> sa.Column:
84
+ return self.col(name, sa.ARRAY, *args, **kwargs)
85
+
86
+ def array_int(self, name: str, *args, **kwargs) -> sa.Column:
87
+ return self.array(name, sa.Integer, *args, **kwargs)
88
+
89
+ def array_str(self, name: str, *args, **kwargs) -> sa.Column:
90
+ return self.array(name, sa.String, *args, **kwargs)
91
+
92
+ def array_text(self, name: str, *args, **kwargs) -> sa.Column:
93
+ return self.array(name, sa.Text, *args, **kwargs)
94
+
95
+ def array_float(self, name: str, *args, **kwargs) -> sa.Column:
96
+ return self.array(name, sa.Float, *args, **kwargs)
97
+
98
+ def array_numeric(self, name: str, *args, **kwargs) -> sa.Column:
99
+ return self.array(name, sa.Numeric, *args, **kwargs)
100
+
101
+ def array_bigint(self, name: str, *args, **kwargs) -> sa.Column:
102
+ return self.array(name, sa.BigInteger, *args, **kwargs)
103
+
104
+ def array_smallint(self, name: str, *args, **kwargs) -> sa.Column:
105
+ return self.array(name, sa.SmallInteger, *args, **kwargs)
106
+
107
+ def array_timestamp(self, name: str, *args, **kwargs) -> sa.Column:
108
+ return self.array(name, sa.TIMESTAMP, *args, **kwargs)
109
+
110
+ def array_date(self, name: str, *args, **kwargs) -> sa.Column:
111
+ return self.array(name, sa.Date, *args, **kwargs)
112
+
113
+ def array_datetime(self, name: str, *args, **kwargs) -> sa.Column:
114
+ return self.array(name, sa.DateTime, *args, **kwargs)
115
+
116
+ def array_time(self, name: str, *args, **kwargs) -> sa.Column:
117
+ return self.array(name, sa.Time, *args, **kwargs)
118
+
119
+ def array_boolean(self, name: str, *args, **kwargs) -> sa.Column:
120
+ return self.array(name, sa.Boolean, *args, **kwargs)
121
+
122
+ def array_enum(self, name: str, enum: type, *args, **kwargs) -> sa.Column:
123
+ return self.array(name, sa.Enum(enum), *args, **kwargs)
124
+
125
+ def auto_id(self, name="id", *args, **kwargs) -> sa.Column:
126
+ return self.integer(
127
+ name, primary_key=True, index=True, autoincrement=True, *args, **kwargs
128
+ )
129
+
130
+ def updated_at(self, name="updated_at", *args, **kwargs) -> sa.Column:
131
+ return self.datetime(
132
+ name, default=sa.func.now(), onupdate=sa.func.now(), *args, **kwargs
133
+ )
134
+
135
+ def created_at(self, name="created_at", *args, **kwargs) -> sa.Column:
136
+ return self.datetime(name, default=sa.func.now(), *args, **kwargs)
137
+
138
+ @overload
139
+ def __call__(self, arg1: str, *args, **kwargs) -> sa.Table: ...
140
+ @overload
141
+ def __call__(self, arg1: sa.Column, *args, **kwargs) -> sa.Column: ...
142
+ @overload
143
+ def __call__(self, arg1: sa.Table, *args, **kwargs) -> sa.Table: ...
144
+ def __call__(self, arg1, *args, **kwargs):
145
+ if isinstance(arg1, sa.Column):
146
+ arg1.info["args"] = args
147
+ arg1.info["kwargs"] = kwargs
148
+ self.c.append(arg1)
149
+ return arg1
150
+ elif isinstance(arg1, Union[str, sa.Table]):
151
+ cols = self.c
152
+ tablename = arg1.name if isinstance(arg1, sa.Table) else arg1
153
+ self.c = []
154
+ return sa.Table(tablename, self.metadata, *args, *cols, **kwargs)
155
+ else:
156
+ raise TypeError(f"Expected a string or Column, got {type(arg1).__name__}")