cloudoll 2.0.24__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.
Files changed (43) hide show
  1. {cloudoll-2.0.24 → cloudoll-2.2.0}/PKG-INFO +26 -10
  2. {cloudoll-2.0.24 → cloudoll-2.2.0}/README.md +25 -9
  3. cloudoll-2.2.0/cloudoll/__init__.py +1 -0
  4. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/cli.py +2 -3
  5. cloudoll-2.2.0/cloudoll/orm/__init__.py +193 -0
  6. cloudoll-2.2.0/cloudoll/orm/base.py +68 -0
  7. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/orm/field.py +142 -58
  8. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/orm/model.py +146 -123
  9. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/orm/mysql.py +15 -7
  10. cloudoll-2.2.0/cloudoll/orm/parse.py +139 -0
  11. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/orm/postgres.py +15 -9
  12. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/utils/m2d.py +123 -51
  13. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/web/__init__.py +92 -49
  14. cloudoll-2.2.0/cloudoll/web/exception.py +63 -0
  15. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/web/settings.py +2 -1
  16. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll.egg-info/PKG-INFO +26 -10
  17. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll.egg-info/SOURCES.txt +2 -0
  18. cloudoll-2.0.24/cloudoll/__init__.py +0 -1
  19. cloudoll-2.0.24/cloudoll/orm/base.py +0 -87
  20. cloudoll-2.0.24/cloudoll/utils/__init__.py +0 -0
  21. {cloudoll-2.0.24 → cloudoll-2.2.0}/LICENSE +0 -0
  22. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/__main__.py +0 -0
  23. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/error/__init__.py +0 -0
  24. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/error/errors.py +0 -0
  25. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/logging/__init__.py +0 -0
  26. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/mail/__init__.py +0 -0
  27. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/mail/smtp.py +0 -0
  28. {cloudoll-2.0.24/cloudoll/orm → cloudoll-2.2.0/cloudoll/robot}/__init__.py +0 -0
  29. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/robot/dingtalk.py +0 -0
  30. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/robot/feishu.py +0 -0
  31. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/robot/slack.py +0 -0
  32. {cloudoll-2.0.24/cloudoll/robot → cloudoll-2.2.0/cloudoll/utils}/__init__.py +0 -0
  33. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/utils/common.py +0 -0
  34. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/utils/watch.py +0 -0
  35. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/web/html.py +0 -0
  36. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/web/http.py +0 -0
  37. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll/web/jwt.py +0 -0
  38. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll.egg-info/dependency_links.txt +0 -0
  39. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll.egg-info/entry_points.txt +0 -0
  40. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll.egg-info/requires.txt +0 -0
  41. {cloudoll-2.0.24 → cloudoll-2.2.0}/cloudoll.egg-info/top_level.txt +0 -0
  42. {cloudoll-2.0.24 → cloudoll-2.2.0}/setup.cfg +0 -0
  43. {cloudoll-2.0.24 → 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.0.24
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
- mysql:
354
- host: 127.0.0.1
355
- port: 3306
356
- user: root
357
- password: abcd
358
- db: blog
359
- charset: utf8mb4
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
- adev runserver app.py
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
- mysql:
335
- host: 127.0.0.1
336
- port: 3306
337
- user: root
338
- password: abcd
339
- db: blog
340
- charset: utf8mb4
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
- adev runserver app.py
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.mysql import Mysql
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 Mysql().create_engine(**db_config)
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)
@@ -7,45 +7,50 @@ class objdict(dict):
7
7
 
8
8
 
9
9
  OP = objdict(
10
- AND='AND',
11
- OR='OR',
12
- ADD='+',
13
- SUB='-',
14
- MUL='*',
15
- DIV='/',
16
- BIN_AND='&',
17
- BIN_OR='|',
18
- XOR='#',
19
- MOD='%',
20
- EQ='=',
21
- LT='<',
22
- LTE='<=',
23
- GT='>',
24
- GTE='>=',
25
- NE='!=',
26
- IN='IN',
27
- NOT_IN='NOT IN',
28
- IS='IS',
29
- DESC='DESC',
30
- ASC='ASC',
31
- AVG='AVG',
32
- AS='AS',
33
- SUM='SUM',
34
- COUNT='COUNT',
35
- IS_NOT='IS NOT',
36
- IS_NOT_NULL='IS NOT NULL',
37
- IS_NULL='IS NULL',
38
- LIKE='LIKE',
39
- NOT_LIKE='NOT LIKE',
40
- ILIKE='ILIKE',
41
- BETWEEN='BETWEEN',
42
- CONTAINS='CONTAINS',
43
- NOT_BETWEEN='NOT BETWEEN',
44
- REGEXP='REGEXP',
45
- IREGEXP='IREGEXP',
46
- DISTINCT='DISTINCT',
47
- CONCAT='||',
48
- BITWISE_NEGATION='~')
10
+ AND="AND",
11
+ OR="OR",
12
+ ADD="+",
13
+ SUB="-",
14
+ MUL="*",
15
+ DIV="/",
16
+ BIN_AND="&",
17
+ BIN_OR="|",
18
+ XOR="#",
19
+ MOD="%",
20
+ EQ="=",
21
+ LT="<",
22
+ LTE="<=",
23
+ GT=">",
24
+ GTE=">=",
25
+ NE="!=",
26
+ IN="IN",
27
+ NOT_IN="NOT IN",
28
+ IS="IS",
29
+ DESC="DESC",
30
+ ASC="ASC",
31
+ AVG="AVG",
32
+ AS="AS",
33
+ SUM="SUM",
34
+ DATE_FORMAT="DATE_FORMAT",
35
+ JSON_CONTAINS_OBJECT="JSON_CONTAINS_OBJECT",
36
+ JSON_CONTAINS_ARRAY="JSON_CONTAINS_ARRAY",
37
+ COUNT="COUNT",
38
+ COUNT_WHEN="COUNT_WHEN",
39
+ IS_NOT="IS NOT",
40
+ IS_NOT_NULL="IS NOT NULL",
41
+ IS_NULL="IS NULL",
42
+ LIKE="LIKE",
43
+ NOT_LIKE="NOT LIKE",
44
+ ILIKE="ILIKE",
45
+ BETWEEN="BETWEEN",
46
+ CONTAINS="CONTAINS",
47
+ NOT_BETWEEN="NOT BETWEEN",
48
+ REGEXP="REGEXP",
49
+ IREGEXP="IREGEXP",
50
+ DISTINCT="DISTINCT",
51
+ CONCAT="||",
52
+ BITWISE_NEGATION="~",
53
+ )
49
54
 
50
55
 
51
56
  class FieldBase:
@@ -54,6 +59,7 @@ class FieldBase:
54
59
  if inverse:
55
60
  return Expression(rhs, op, self)
56
61
  return Expression(self, op, rhs)
62
+
57
63
  return inner
58
64
 
59
65
  def __eq__(self, rhs):
@@ -73,7 +79,7 @@ class FieldBase:
73
79
  __add__ = _op(OP.ADD) # +
74
80
  __radd__ = _op(OP.ADD, True) # +=
75
81
  __sub__ = _op(OP.SUB) # -
76
- __sub__ = _op(OP.SUB, True) # -=
82
+ __rsub__ = _op(OP.SUB, True) # -=
77
83
  __mul__ = _op(OP.MUL) # *
78
84
  __rmul__ = _op(OP.MUL, True) # *=
79
85
  __div__ = _op(OP.DIV) # /
@@ -97,12 +103,24 @@ class FieldBase:
97
103
  def count(self):
98
104
  return Function(self, OP.COUNT)
99
105
 
106
+ def count_when(self, value):
107
+ return Function(self, OP.COUNT_WHEN, value)
108
+
100
109
  def sum(self):
101
110
  return Function(self, OP.SUM)
102
111
 
103
112
  def avg(self):
104
113
  return Function(self, OP.AVG)
105
114
 
115
+ def date_format(self, args):
116
+ return Function(self, OP.DATE_FORMAT, args)
117
+
118
+ def json_contains_object(self, key, value):
119
+ return Function(self, OP.JSON_CONTAINS_OBJECT, (key, value))
120
+
121
+ def json_contains_array(self, args):
122
+ return Function(self, OP.JSON_CONTAINS_ARRAY, args)
123
+
106
124
  def contains(self, args):
107
125
  return Function(self, OP.CONTAINS, args)
108
126
  # return AO(f"contains({self.full_name},?)", args)
@@ -137,16 +155,16 @@ class ExpList(FieldBase):
137
155
  q = []
138
156
  if isinstance(lpt, Exception):
139
157
  _q, _p = lpt.sql()
140
- q = q+_q
141
- p = p+_p
158
+ q = q + _q
159
+ p = p + _p
142
160
  else:
143
161
  q.append("?")
144
162
  p.append(lpt)
145
163
  q.append(f" {self.op} ")
146
164
  if isinstance(rpt, Exception):
147
165
  _q, _p = lpt.sql()
148
- q = q+_q
149
- p = p+_p
166
+ q = q + _q
167
+ p = p + _p
150
168
  else:
151
169
  q.append("?")
152
170
  p.append(lpt)
@@ -162,8 +180,27 @@ class Function(FieldBase):
162
180
  self.rpt = rpt
163
181
 
164
182
  def sql(self):
183
+ op = self.op
184
+ col_name = self.col.full_name
185
+ is_field = isinstance(self.rpt, Field)
165
186
  # todo:
166
- return f"{self.op}({self.col.full_name} {','+str(self.rpt) if self.rpt else ''})"
187
+ if op == OP.DATE_FORMAT:
188
+ return f"{op}({col_name},?)", [self.rpt]
189
+ elif op == OP.JSON_CONTAINS_OBJECT:
190
+ _k, _v = self.rpt
191
+ key = _k.full_name if isinstance(_k, Field) else f"'{_k}'"
192
+ return f"json_contains({col_name},json_object({key},?))", [_v]
193
+ elif op == OP.JSON_CONTAINS_ARRAY:
194
+ if is_field:
195
+ return (
196
+ f"json_contains({col_name},json_array({self.rpt.full_name}))",
197
+ None,
198
+ )
199
+ return f"json_contains({col_name},json_array(?))", [self.rpt]
200
+ elif op == OP.COUNT:
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]
167
204
 
168
205
 
169
206
  class Expression(FieldBase):
@@ -171,32 +208,75 @@ class Expression(FieldBase):
171
208
  self.lhs = lhs
172
209
  self.op = op
173
210
  self.rhs = rhs
211
+ # self.__value = ""
212
+ # if op in "+-*/|&~" and lhs._value:
213
+ # cal = f"{lhs._value}{op}"
214
+ # if isinstance(rhs, FieldBase):
215
+ # self.__value = str(eval(f"{cal}{rhs._value}"))
216
+ # else:
217
+ # self.__value = str(eval(f"{cal}{rhs}"))
174
218
 
175
219
  def sql(self):
176
220
  l = self.lhs
177
221
  r = self.rhs
222
+ is_fun = isinstance(l, Function)
178
223
  p = []
179
- q = ["("]
224
+ q = []
225
+ if not is_fun:
226
+ q.append("(")
180
227
  if isinstance(l, Field):
181
228
  q.append(l.full_name)
182
229
  elif isinstance(l, Expression):
183
230
  _q, _p = l.sql()
184
231
  q.append(_q)
185
- p = p+_p
232
+ p = p + _p
233
+ elif is_fun:
234
+ _q, _p = l.sql()
235
+ q.append(_q)
236
+ if _p:
237
+ p = p + _p
238
+ # return l.sql()
239
+
186
240
  q.append(f" {self.op} ")
187
241
 
188
242
  if isinstance(r, Field):
189
- q.append(r.full_name)
243
+ if r._value is not None:
244
+ # _value = r._value if r._value is not None else r.full_name
245
+ q.append("?")
246
+ p.append(r._value)
247
+ else:
248
+ q.append(r.full_name)
249
+ # q.append(str(r._value) if r._value is not None else r.full_name)
190
250
  elif isinstance(r, FieldBase):
191
251
  _q, _p = r.sql()
192
252
  q.append(_q)
193
- p = p+_p
194
- else:
195
- q.append('?')
253
+ p = p + _p
254
+ elif self.op == OP.AS:
255
+ # is_fun or (isinstance(r, str) and (self.op == OP.AND or self.op == OP.OR)):
256
+ q.append(r)
257
+ elif r is not None:
258
+ q.append("?")
196
259
  p.append(r)
197
- q.append(')')
260
+ if not is_fun:
261
+ q.append(")")
262
+ # q.append(")")
263
+ q = [str(num) for num in q]
264
+ # print("".join(q))
198
265
  return "".join(q), p
199
- # return f"({l.full_name if isinstance(l,Field) else l.sql()} {self.op} {r.full_name if isinstance(r,Field) else (r.sql() if isinstance(r,FieldBase) else str(r))})"
266
+
267
+ # def __str__(self):
268
+ # return self.__value
269
+ # cal = f"{self.lhs._value}{self.op}"
270
+ # if isinstance(self.rhs, FieldBase):
271
+ # return str(eval(f"{cal}{self.rhs._value}"))
272
+ # return str(eval(f"{cal}{self.rhs}"))
273
+
274
+ def __repr__(self):
275
+ # return self.__value
276
+ cal = f"{self.lhs.value}{self.op}"
277
+ if isinstance(self.rhs, FieldBase):
278
+ return str(eval(f"{cal}{self.rhs.value}"))
279
+ return str(eval(f"{cal}{self.rhs}"))
200
280
 
201
281
 
202
282
  class Field(FieldBase):
@@ -237,11 +317,15 @@ class Field(FieldBase):
237
317
  # "<%s, %s:%s>" % (self.__class__.__name__, self.column_type, self.name)
238
318
  # return self.name
239
319
 
320
+ def __repr__(self):
321
+ return str(self._value)
322
+
240
323
  # def __getattr__(self, item):
241
324
  # return self[item]
242
-
243
- def set_value(self, value):
244
- self._value = value
245
-
246
- def get_value(self):
325
+ @property
326
+ def value(self):
247
327
  return self._value
328
+
329
+ @value.setter
330
+ def value(self, value):
331
+ self._value = value