cloudoll 2.1.0__tar.gz → 2.2.0__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.
- {cloudoll-2.1.0 → cloudoll-2.2.0}/PKG-INFO +26 -10
- {cloudoll-2.1.0 → cloudoll-2.2.0}/README.md +25 -9
- cloudoll-2.2.0/cloudoll/__init__.py +1 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/cli.py +2 -3
- cloudoll-2.2.0/cloudoll/orm/__init__.py +193 -0
- cloudoll-2.2.0/cloudoll/orm/base.py +68 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/orm/field.py +17 -7
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/orm/model.py +106 -91
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/orm/mysql.py +6 -0
- cloudoll-2.2.0/cloudoll/orm/parse.py +139 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/orm/postgres.py +15 -9
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/utils/m2d.py +123 -51
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/__init__.py +91 -48
- cloudoll-2.2.0/cloudoll/web/exception.py +63 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/PKG-INFO +26 -10
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/SOURCES.txt +2 -0
- cloudoll-2.1.0/cloudoll/__init__.py +0 -1
- cloudoll-2.1.0/cloudoll/orm/base.py +0 -130
- cloudoll-2.1.0/cloudoll/utils/__init__.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/LICENSE +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/__main__.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/error/__init__.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/error/errors.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/logging/__init__.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/mail/__init__.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/mail/smtp.py +0 -0
- {cloudoll-2.1.0/cloudoll/orm → cloudoll-2.2.0/cloudoll/robot}/__init__.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/robot/dingtalk.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/robot/feishu.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/robot/slack.py +0 -0
- {cloudoll-2.1.0/cloudoll/robot → cloudoll-2.2.0/cloudoll/utils}/__init__.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/utils/common.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/utils/watch.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/html.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/http.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/jwt.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/settings.py +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/dependency_links.txt +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/entry_points.txt +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/requires.txt +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/top_level.txt +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/setup.cfg +0 -0
- {cloudoll-2.1.0 → cloudoll-2.2.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cloudoll
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: 辅助快速创建可分布的微服务。
|
|
5
5
|
Home-page: https://gitee.com/chuchur/cloudoll-py
|
|
6
6
|
Author: chuchur
|
|
@@ -350,20 +350,36 @@ server:
|
|
|
350
350
|
show_index: true
|
|
351
351
|
append_version: true
|
|
352
352
|
follow_symlinks: true
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
353
|
+
database:
|
|
354
|
+
mysql:
|
|
355
|
+
host: 127.0.0.1
|
|
356
|
+
port: 3306
|
|
357
|
+
user: root
|
|
358
|
+
password: abcd
|
|
359
|
+
db: blog
|
|
360
|
+
charset: utf8mb4
|
|
360
361
|
```
|
|
361
362
|
|
|
362
363
|
默认开发会使用默认的`local`作为配置。 启动时 通过 `env` 加载对应的配置。 如 `python3 app.py --env=prod` 会加载 `conf.prod.yaml`
|
|
363
364
|
|
|
365
|
+
# cli
|
|
364
366
|
|
|
365
|
-
|
|
367
|
+
## 生成模型
|
|
366
368
|
|
|
369
|
+
从数据库导出 `users` 表模型
|
|
367
370
|
```sh
|
|
368
|
-
|
|
371
|
+
cloudoll gen -t users
|
|
372
|
+
```
|
|
373
|
+
更多参数 :
|
|
374
|
+
- -p (--path) 导出的模型路径
|
|
375
|
+
- -c (--create) 值 model(默认) 生成模型, 值table 建表
|
|
376
|
+
- -t (--table) 要生的模型或要建的表名,以`,`分开, `ALL` 所有表
|
|
377
|
+
- -env (--environment) 读取配置名 ,值 local(默认) / test / prod
|
|
378
|
+
- -db (--database) 数据库实例名,取决于配置文件,如果有多个数据库
|
|
379
|
+
- -h (--help) 帮助
|
|
380
|
+
|
|
381
|
+
## 开发调试
|
|
382
|
+
|
|
383
|
+
```sh
|
|
384
|
+
cloudoll start
|
|
369
385
|
```
|
|
@@ -331,20 +331,36 @@ server:
|
|
|
331
331
|
show_index: true
|
|
332
332
|
append_version: true
|
|
333
333
|
follow_symlinks: true
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
334
|
+
database:
|
|
335
|
+
mysql:
|
|
336
|
+
host: 127.0.0.1
|
|
337
|
+
port: 3306
|
|
338
|
+
user: root
|
|
339
|
+
password: abcd
|
|
340
|
+
db: blog
|
|
341
|
+
charset: utf8mb4
|
|
341
342
|
```
|
|
342
343
|
|
|
343
344
|
默认开发会使用默认的`local`作为配置。 启动时 通过 `env` 加载对应的配置。 如 `python3 app.py --env=prod` 会加载 `conf.prod.yaml`
|
|
344
345
|
|
|
346
|
+
# cli
|
|
345
347
|
|
|
346
|
-
|
|
348
|
+
## 生成模型
|
|
347
349
|
|
|
350
|
+
从数据库导出 `users` 表模型
|
|
348
351
|
```sh
|
|
349
|
-
|
|
352
|
+
cloudoll gen -t users
|
|
353
|
+
```
|
|
354
|
+
更多参数 :
|
|
355
|
+
- -p (--path) 导出的模型路径
|
|
356
|
+
- -c (--create) 值 model(默认) 生成模型, 值table 建表
|
|
357
|
+
- -t (--table) 要生的模型或要建的表名,以`,`分开, `ALL` 所有表
|
|
358
|
+
- -env (--environment) 读取配置名 ,值 local(默认) / test / prod
|
|
359
|
+
- -db (--database) 数据库实例名,取决于配置文件,如果有多个数据库
|
|
360
|
+
- -h (--help) 帮助
|
|
361
|
+
|
|
362
|
+
## 开发调试
|
|
363
|
+
|
|
364
|
+
```sh
|
|
365
|
+
cloudoll start
|
|
350
366
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from .web.settings import get_config
|
|
3
|
-
from .orm
|
|
4
|
-
from .orm.postgres import Postgres
|
|
3
|
+
from .orm import create_engine
|
|
5
4
|
from .utils.m2d import create_models, create_tables
|
|
6
5
|
import asyncio
|
|
7
6
|
import os
|
|
@@ -50,7 +49,7 @@ def gen(path, create, table, environment, database) -> None:
|
|
|
50
49
|
if db_config is None:
|
|
51
50
|
raise ValueError(
|
|
52
51
|
f"Can't find the database config key ->{database} in conf.{environment}.yaml")
|
|
53
|
-
sa = await
|
|
52
|
+
sa = await create_engine(**db_config)
|
|
54
53
|
if sa.pool is None:
|
|
55
54
|
return
|
|
56
55
|
model_path = os.path.join(os.path.abspath("."), path)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from redis import asyncio as aioredis
|
|
2
|
+
from .parse import parse_coon
|
|
3
|
+
import aiopg, aiomysql
|
|
4
|
+
from .base import MeteBase, QueryTypes
|
|
5
|
+
from typing import Any
|
|
6
|
+
from ..logging import error
|
|
7
|
+
import traceback
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def create_engine(**kw):
|
|
11
|
+
url = kw.get("url")
|
|
12
|
+
driver = None
|
|
13
|
+
configs = {}
|
|
14
|
+
query = {}
|
|
15
|
+
|
|
16
|
+
if url is not None:
|
|
17
|
+
configs, query = parse_coon(url)
|
|
18
|
+
driver = configs["type"]
|
|
19
|
+
else:
|
|
20
|
+
driver = kw.get("type")
|
|
21
|
+
configs = kw
|
|
22
|
+
|
|
23
|
+
if driver == "mysql":
|
|
24
|
+
return await Mysql().create_engine(**configs, **query)
|
|
25
|
+
elif driver == "postgres":
|
|
26
|
+
return await Postgres().create_engine(**configs, **query)
|
|
27
|
+
elif driver == "redis" or driver == "rediss":
|
|
28
|
+
"""
|
|
29
|
+
redis://[[username]:[password]]@localhost:6379/0
|
|
30
|
+
rediss://[[username]:[password]]@localhost:6379/0
|
|
31
|
+
"""
|
|
32
|
+
if url is None:
|
|
33
|
+
url = f"{driver}://{configs['username']}:{configs['password']}@{configs['host']}:{configs['port']}/{configs['db']}"
|
|
34
|
+
return await aioredis.from_url(url, **query)
|
|
35
|
+
else:
|
|
36
|
+
error("Not suport this database type.")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Postgres(MeteBase):
|
|
40
|
+
def __init__(self, pool=None):
|
|
41
|
+
self.pool = pool
|
|
42
|
+
self.driver = "postgres"
|
|
43
|
+
|
|
44
|
+
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
|
45
|
+
self.__init__(*args, **kwds)
|
|
46
|
+
|
|
47
|
+
async def query(self, sql, params=None, query_type: QueryTypes = 2, size: int = 10):
|
|
48
|
+
sql = sql.replace("?", "%s").replace("`", "")
|
|
49
|
+
print("sql", sql, params)
|
|
50
|
+
if not self.pool:
|
|
51
|
+
raise ValueError("must be create_engine first.")
|
|
52
|
+
async with self.pool.acquire() as conn:
|
|
53
|
+
async with conn.cursor() as cursor:
|
|
54
|
+
# current_cursor = getattr(cursor, 'lastrowid', None)
|
|
55
|
+
await cursor.execute(sql, params)
|
|
56
|
+
|
|
57
|
+
# if query_type.value > 4:
|
|
58
|
+
# if self.driver == "mysql":
|
|
59
|
+
# await conn.commit()
|
|
60
|
+
# elif self.driver == "postgres":
|
|
61
|
+
columns = [desc[0] for desc in cursor.description]
|
|
62
|
+
result = None
|
|
63
|
+
|
|
64
|
+
if query_type == QueryTypes.ALL:
|
|
65
|
+
rows = await cursor.fetchall()
|
|
66
|
+
result = [dict(zip(columns, row)) for row in rows]
|
|
67
|
+
return result
|
|
68
|
+
elif query_type == QueryTypes.ONE:
|
|
69
|
+
rows = await cursor.fetchone()
|
|
70
|
+
result = dict(zip(columns, rows))
|
|
71
|
+
return result
|
|
72
|
+
elif query_type == QueryTypes.MANY:
|
|
73
|
+
rows = await cursor.fetchmany(size)
|
|
74
|
+
result = [dict(zip(columns, row)) for row in rows]
|
|
75
|
+
return result
|
|
76
|
+
elif query_type == QueryTypes.COUNT:
|
|
77
|
+
rs = await cursor.fetchone()
|
|
78
|
+
return rs[0]
|
|
79
|
+
elif query_type == QueryTypes.CREATE:
|
|
80
|
+
result = cursor.rowcount > 0
|
|
81
|
+
id = cursor.lastrowid
|
|
82
|
+
return result, id
|
|
83
|
+
elif query_type == QueryTypes.UPDATE:
|
|
84
|
+
return cursor.rowcount > 0
|
|
85
|
+
elif query_type == QueryTypes.DELETE:
|
|
86
|
+
return cursor.rowcount > 0
|
|
87
|
+
|
|
88
|
+
async def create_engine(self, **kw):
|
|
89
|
+
try:
|
|
90
|
+
host = (kw.get("host", "localhost"),)
|
|
91
|
+
port = (kw.get("port", 5432),)
|
|
92
|
+
user = (kw.get("username"),)
|
|
93
|
+
password = (str(kw.get("password", "")),)
|
|
94
|
+
db = (kw.get("db"),)
|
|
95
|
+
dsn = f"dbname={db[0]} user={user[0]} password={password[0]} host={host[0]} port={port[0]}"
|
|
96
|
+
self.pool = await aiopg.create_pool(
|
|
97
|
+
dsn=dsn,
|
|
98
|
+
timeout=kw.get("timeout"),
|
|
99
|
+
echo=kw.get("echo", False),
|
|
100
|
+
maxsize=kw.get("maxsize", 10),
|
|
101
|
+
minsize=kw.get("pool_size", 5),
|
|
102
|
+
)
|
|
103
|
+
print(f"Database connection successfuly for postgres/{kw.get('db')}")
|
|
104
|
+
except Exception as e:
|
|
105
|
+
error(e)
|
|
106
|
+
# print(traceback.format_exc())
|
|
107
|
+
error(f"Database connection failed,the instance : postgres/{kw.get('db')}")
|
|
108
|
+
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class AttrDict(dict):
|
|
113
|
+
"""Dict that can get attribute by dot, and doesn't raise KeyError"""
|
|
114
|
+
|
|
115
|
+
def __getattr__(self, name):
|
|
116
|
+
try:
|
|
117
|
+
return self[name]
|
|
118
|
+
except KeyError:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class AttrDictCursor(aiomysql.DictCursor):
|
|
123
|
+
dict_type = AttrDict
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class Mysql(MeteBase):
|
|
127
|
+
def __init__(self, pool=None):
|
|
128
|
+
self.pool = pool
|
|
129
|
+
self.driver = "mysql"
|
|
130
|
+
|
|
131
|
+
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
|
132
|
+
self.__init__(*args, **kwds)
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
async def query(self, sql, params=None, query_type: QueryTypes = 2, size: int = 10):
|
|
136
|
+
sql = sql.replace("?", "%s")
|
|
137
|
+
print("sql", sql, params)
|
|
138
|
+
if not self.pool:
|
|
139
|
+
raise ValueError("must be create_engine first.")
|
|
140
|
+
async with self.pool.acquire() as conn:
|
|
141
|
+
async with conn.cursor() as cursor:
|
|
142
|
+
# current_cursor = getattr(cursor, 'lastrowid', None)
|
|
143
|
+
await cursor.execute(sql, params)
|
|
144
|
+
|
|
145
|
+
# if query_type.value > 4:
|
|
146
|
+
# if self.driver == "mysql":
|
|
147
|
+
await conn.commit()
|
|
148
|
+
# elif self.driver == "postgres":
|
|
149
|
+
# columns = [desc[0] for desc in cursor.description]
|
|
150
|
+
|
|
151
|
+
if query_type == QueryTypes.ALL:
|
|
152
|
+
return await cursor.fetchall()
|
|
153
|
+
elif query_type == QueryTypes.ONE:
|
|
154
|
+
return await cursor.fetchone()
|
|
155
|
+
elif query_type == QueryTypes.MANY:
|
|
156
|
+
return await cursor.fetchmany(size)
|
|
157
|
+
elif query_type == QueryTypes.COUNT:
|
|
158
|
+
rs = await cursor.fetchone()
|
|
159
|
+
value = 0
|
|
160
|
+
for r in rs:
|
|
161
|
+
value = rs[r]
|
|
162
|
+
return value
|
|
163
|
+
elif query_type == QueryTypes.CREATE:
|
|
164
|
+
result = cursor.rowcount > 0
|
|
165
|
+
id = cursor.lastrowid
|
|
166
|
+
return result, id
|
|
167
|
+
elif query_type == QueryTypes.UPDATE:
|
|
168
|
+
return cursor.rowcount > 0
|
|
169
|
+
elif query_type == QueryTypes.DELETE:
|
|
170
|
+
return cursor.rowcount > 0
|
|
171
|
+
|
|
172
|
+
async def create_engine(self, loop=None, **kw):
|
|
173
|
+
try:
|
|
174
|
+
self.pool = await aiomysql.create_pool(
|
|
175
|
+
host=kw.get("host", "localhost"),
|
|
176
|
+
port=kw.get("port", 3306),
|
|
177
|
+
user=kw.get("username"),
|
|
178
|
+
password=str(kw.get("password", "")),
|
|
179
|
+
db=kw.get("db"),
|
|
180
|
+
echo=kw.get("echo", False),
|
|
181
|
+
charset=kw.get("charset", "utf8"),
|
|
182
|
+
autocommit=False, # kw.get("autocommit", False),
|
|
183
|
+
maxsize=kw.get("maxsize", 10),
|
|
184
|
+
minsize=kw.get("pool_size", 5),
|
|
185
|
+
cursorclass=AttrDictCursor,
|
|
186
|
+
loop=loop,
|
|
187
|
+
)
|
|
188
|
+
print(f"Database connection successfuly for mysql/{kw.get('db')}.")
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print(e)
|
|
191
|
+
# print(traceback.format_exc())
|
|
192
|
+
error(f"Database connection failed,the instance : mysql/{kw.get('db')}")
|
|
193
|
+
return self
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class QueryTypes(Enum):
|
|
5
|
+
ALL = 1
|
|
6
|
+
ONE = 2
|
|
7
|
+
MANY = 3
|
|
8
|
+
COUNT = 4
|
|
9
|
+
CREATE = 5
|
|
10
|
+
UPDATE = 6
|
|
11
|
+
DELETE = 7
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MeteBase:
|
|
15
|
+
|
|
16
|
+
async def __aexit__(self):
|
|
17
|
+
await self.close()
|
|
18
|
+
|
|
19
|
+
async def close(self):
|
|
20
|
+
if self.pool:
|
|
21
|
+
self.pool.close()
|
|
22
|
+
await self.pool.wait_closed()
|
|
23
|
+
|
|
24
|
+
# async def begin_transaction(self):
|
|
25
|
+
# conn = await self._set_conn()
|
|
26
|
+
# await conn.begin()
|
|
27
|
+
# return conn
|
|
28
|
+
|
|
29
|
+
# async def begin_transaction_scope(self, fun):
|
|
30
|
+
# conn = await self._set_conn()
|
|
31
|
+
# await conn.begin()
|
|
32
|
+
# cursor = None
|
|
33
|
+
# if isfunction(fun):
|
|
34
|
+
# try:
|
|
35
|
+
# cursor = await conn.cursor()
|
|
36
|
+
# await fun(cursor)
|
|
37
|
+
# await conn.commit()
|
|
38
|
+
# except Exception as e:
|
|
39
|
+
# error(e)
|
|
40
|
+
# await conn.rollback()
|
|
41
|
+
# finally:
|
|
42
|
+
# if cursor:
|
|
43
|
+
# await cursor.close()
|
|
44
|
+
# self.pool.release(conn)
|
|
45
|
+
async def query(
|
|
46
|
+
self, sql, params=None, query_type: QueryTypes = 2, size: int = 10
|
|
47
|
+
): ...
|
|
48
|
+
|
|
49
|
+
async def all(self, sql, params):
|
|
50
|
+
return await self.query(sql, params, QueryTypes.ALL)
|
|
51
|
+
|
|
52
|
+
async def one(self, sql, params):
|
|
53
|
+
return await self.query(sql, params, QueryTypes.ONE)
|
|
54
|
+
|
|
55
|
+
async def many(self, sql, params, size: int):
|
|
56
|
+
return await self.query(sql, params, QueryTypes.MANY, size)
|
|
57
|
+
|
|
58
|
+
async def count(self, sql, params):
|
|
59
|
+
return await self.query(sql, params, QueryTypes.COUNT)
|
|
60
|
+
|
|
61
|
+
async def update(self, sql, params):
|
|
62
|
+
return await self.query(sql, params, QueryTypes.UPDATE)
|
|
63
|
+
|
|
64
|
+
async def delete(self, sql, params):
|
|
65
|
+
return await self.query(sql, params, QueryTypes.DELETE)
|
|
66
|
+
|
|
67
|
+
async def create(self, sql, params):
|
|
68
|
+
return await self.query(sql, params, QueryTypes.CREATE)
|
|
@@ -35,6 +35,7 @@ OP = objdict(
|
|
|
35
35
|
JSON_CONTAINS_OBJECT="JSON_CONTAINS_OBJECT",
|
|
36
36
|
JSON_CONTAINS_ARRAY="JSON_CONTAINS_ARRAY",
|
|
37
37
|
COUNT="COUNT",
|
|
38
|
+
COUNT_WHEN="COUNT_WHEN",
|
|
38
39
|
IS_NOT="IS NOT",
|
|
39
40
|
IS_NOT_NULL="IS NOT NULL",
|
|
40
41
|
IS_NULL="IS NULL",
|
|
@@ -102,6 +103,9 @@ class FieldBase:
|
|
|
102
103
|
def count(self):
|
|
103
104
|
return Function(self, OP.COUNT)
|
|
104
105
|
|
|
106
|
+
def count_when(self, value):
|
|
107
|
+
return Function(self, OP.COUNT_WHEN, value)
|
|
108
|
+
|
|
105
109
|
def sum(self):
|
|
106
110
|
return Function(self, OP.SUM)
|
|
107
111
|
|
|
@@ -195,6 +199,8 @@ class Function(FieldBase):
|
|
|
195
199
|
return f"json_contains({col_name},json_array(?))", [self.rpt]
|
|
196
200
|
elif op == OP.COUNT:
|
|
197
201
|
return f"COUNT({col_name})", None
|
|
202
|
+
elif op == OP.COUNT_WHEN:
|
|
203
|
+
return f"COUNT(CASE WHEN {col_name} = ? THEN 1 END)", [self.rpt]
|
|
198
204
|
|
|
199
205
|
|
|
200
206
|
class Expression(FieldBase):
|
|
@@ -267,9 +273,9 @@ class Expression(FieldBase):
|
|
|
267
273
|
|
|
268
274
|
def __repr__(self):
|
|
269
275
|
# return self.__value
|
|
270
|
-
cal = f"{self.lhs.
|
|
276
|
+
cal = f"{self.lhs.value}{self.op}"
|
|
271
277
|
if isinstance(self.rhs, FieldBase):
|
|
272
|
-
return str(eval(f"{cal}{self.rhs.
|
|
278
|
+
return str(eval(f"{cal}{self.rhs.value}"))
|
|
273
279
|
return str(eval(f"{cal}{self.rhs}"))
|
|
274
280
|
|
|
275
281
|
|
|
@@ -311,11 +317,15 @@ class Field(FieldBase):
|
|
|
311
317
|
# "<%s, %s:%s>" % (self.__class__.__name__, self.column_type, self.name)
|
|
312
318
|
# return self.name
|
|
313
319
|
|
|
320
|
+
def __repr__(self):
|
|
321
|
+
return str(self._value)
|
|
322
|
+
|
|
314
323
|
# def __getattr__(self, item):
|
|
315
324
|
# return self[item]
|
|
316
|
-
|
|
317
|
-
def
|
|
318
|
-
self._value = value
|
|
319
|
-
|
|
320
|
-
def get_value(self):
|
|
325
|
+
@property
|
|
326
|
+
def value(self):
|
|
321
327
|
return self._value
|
|
328
|
+
|
|
329
|
+
@value.setter
|
|
330
|
+
def value(self, value):
|
|
331
|
+
self._value = value
|