cloudoll 2.2.20__tar.gz → 2.2.24__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 (44) hide show
  1. {cloudoll-2.2.20 → cloudoll-2.2.24}/PKG-INFO +1 -1
  2. cloudoll-2.2.24/cloudoll/__init__.py +1 -0
  3. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/orm/__init__.py +115 -21
  4. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/orm/base.py +8 -0
  5. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/orm/field.py +2 -0
  6. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/orm/model.py +123 -15
  7. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/robot/feishu.py +2 -1
  8. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/utils/m2d.py +32 -14
  9. cloudoll-2.2.24/cloudoll/utils/snowflake.py +86 -0
  10. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll.egg-info/PKG-INFO +1 -1
  11. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll.egg-info/SOURCES.txt +1 -0
  12. cloudoll-2.2.20/cloudoll/__init__.py +0 -1
  13. {cloudoll-2.2.20 → cloudoll-2.2.24}/LICENSE +0 -0
  14. {cloudoll-2.2.20 → cloudoll-2.2.24}/README.md +0 -0
  15. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/__main__.py +0 -0
  16. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/cli.py +0 -0
  17. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/error/__init__.py +0 -0
  18. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/error/errors.py +0 -0
  19. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/logging/__init__.py +0 -0
  20. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/mail/__init__.py +0 -0
  21. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/mail/smtp.py +0 -0
  22. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/orm/err.py +0 -0
  23. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/orm/mysql.py +0 -0
  24. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/orm/parse.py +0 -0
  25. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/orm/postgres.py +0 -0
  26. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/robot/__init__.py +0 -0
  27. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/robot/dingtalk.py +0 -0
  28. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/robot/slack.py +0 -0
  29. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/utils/__init__.py +0 -0
  30. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/utils/common.py +0 -0
  31. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/utils/patch_loop.py +0 -0
  32. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/utils/watch.py +0 -0
  33. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/web/__init__.py +0 -0
  34. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/web/exception.py +0 -0
  35. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/web/html.py +0 -0
  36. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/web/http.py +0 -0
  37. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/web/jwt.py +0 -0
  38. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll/web/settings.py +0 -0
  39. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll.egg-info/dependency_links.txt +0 -0
  40. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll.egg-info/entry_points.txt +0 -0
  41. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll.egg-info/requires.txt +0 -0
  42. {cloudoll-2.2.20 → cloudoll-2.2.24}/cloudoll.egg-info/top_level.txt +0 -0
  43. {cloudoll-2.2.20 → cloudoll-2.2.24}/setup.cfg +0 -0
  44. {cloudoll-2.2.20 → cloudoll-2.2.24}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudoll
3
- Version: 2.2.20
3
+ Version: 2.2.24
4
4
  Summary: 辅助快速创建可分布的微服务。
5
5
  Home-page: https://gitee.com/chuchur/cloudoll-py
6
6
  Author: chuchur
@@ -0,0 +1 @@
1
+ __version__ = "2.2.24"
@@ -1,13 +1,20 @@
1
1
  from redis import asyncio as aioredis
2
2
  from .parse import parse_coon
3
- import aiopg, aiomysql
4
3
  from .base import MeteBase, QueryTypes
5
4
  from typing import Any
6
5
  from ..logging import print_info, print_error
7
6
  import traceback
7
+ import aiomysql
8
8
  from aiomysql import Pool as MYPool
9
+
10
+ import aiopg as pg
9
11
  from aiopg.pool import Pool as PGPool
10
12
 
13
+ # import asyncpg as pg
14
+ # from asyncpg.pool import Pool as PGPool
15
+
16
+ __ALL__ = "create_engine"
17
+
11
18
 
12
19
  async def create_engine(**kw):
13
20
  url = kw.get("url")
@@ -26,7 +33,7 @@ async def create_engine(**kw):
26
33
 
27
34
  if driver == "mysql":
28
35
  return await Mysql().create_engine(**configs, **query)
29
- elif driver == "postgres":
36
+ elif driver == "postgres" or driver == "postgressql":
30
37
  return await Postgres().create_engine(**configs, **query)
31
38
  elif driver == "redis" or driver == "rediss":
32
39
  """
@@ -48,6 +55,65 @@ class Postgres(MeteBase):
48
55
  def __call__(self, *args: Any, **kwds: Any) -> Any:
49
56
  self.__init__(*args, **kwds)
50
57
 
58
+ # async def query(self, sql, params=None, query_type: QueryTypes = 2, size: int = 10):
59
+ # sql = sql.replace("?", "%s").replace("`", "")
60
+ # print_info("sql", sql, params)
61
+ # if not self.pool:
62
+ # raise ValueError("must be create_engine first.")
63
+ # if self.pool._closing or self.pool._closed:
64
+ # return None
65
+
66
+ # # async with self.pool.acquire() as conn:
67
+ # async with self.pool.acquire() as conn:
68
+ # # current_cursor = getattr(cursor, 'lastrowid', None)
69
+ # if (
70
+ # query_type == QueryTypes.CREATEBATCH
71
+ # or query_type == QueryTypes.UPDATEBATCH
72
+ # ):
73
+ # result = await conn.executemany(sql, params) # aiopg don't support executemany
74
+ # else:
75
+ # result = await conn.execute(sql, params)
76
+
77
+ # # result = None
78
+
79
+ # return result
80
+
81
+ # if query_type == QueryTypes.ALL:
82
+ # columns = [desc[0] for desc in cursor.description]
83
+ # rows = await cursor.fetchall()
84
+ # result = [dict(zip(columns, row)) for row in rows]
85
+ # return result
86
+ # elif query_type == QueryTypes.ONE:
87
+ # columns = [desc[0] for desc in cursor.description]
88
+ # row = await cursor.fetchone()
89
+ # # print("rows_reuslt", rows)
90
+ # result = dict(zip(columns, row)) if row else {}
91
+ # return result
92
+ # elif query_type == QueryTypes.MANY:
93
+ # columns = [desc[0] for desc in cursor.description]
94
+ # rows = await cursor.fetchmany(size)
95
+ # result = [dict(zip(columns, row)) for row in rows]
96
+ # return result
97
+ # elif query_type == QueryTypes.COUNT:
98
+ # rs = await cursor.fetchone()
99
+ # return rs[0] if row else 0
100
+ # elif query_type == QueryTypes.CREATE:
101
+ # result = cursor.rowcount > 0
102
+ # id = cursor.lastrowid
103
+ # return result, id
104
+ # elif query_type == QueryTypes.CREATEBATCH:
105
+ # count = cursor.rowcount
106
+ # id = cursor.lastrowid
107
+ # return count, id
108
+ # elif query_type == QueryTypes.UPDATE:
109
+ # return cursor.rowcount > 0
110
+ # elif query_type == QueryTypes.UPDATEBATCH:
111
+ # return cursor.rowcount
112
+ # elif query_type == QueryTypes.DELETE:
113
+ # return cursor.rowcount > 0
114
+
115
+ # self.pool.release(cursor)
116
+
51
117
  async def query(self, sql, params=None, query_type: QueryTypes = 2, size: int = 10):
52
118
  sql = sql.replace("?", "%s").replace("`", "")
53
119
  print_info("sql", sql, params)
@@ -55,43 +121,56 @@ class Postgres(MeteBase):
55
121
  raise ValueError("must be create_engine first.")
56
122
  if self.pool._closing or self.pool._closed:
57
123
  return None
124
+
58
125
  async with self.pool.acquire() as conn:
59
126
  async with conn.cursor() as cursor:
60
127
  # current_cursor = getattr(cursor, 'lastrowid', None)
61
- await cursor.execute(sql, params)
128
+ if (
129
+ query_type == QueryTypes.CREATEBATCH
130
+ or query_type == QueryTypes.UPDATEBATCH
131
+ ):
132
+ # aiopg don't support executemany
133
+ await cursor.executemany(sql, params)
134
+ else:
135
+ await cursor.execute(sql, params)
62
136
 
63
- # if query_type.value > 4:
64
- # if self.driver == "mysql":
65
- # await conn.commit()
66
- # elif self.driver == "postgres":
67
- columns = [desc[0] for desc in cursor.description]
68
137
  result = None
69
138
 
70
139
  if query_type == QueryTypes.ALL:
140
+ columns = [desc[0] for desc in cursor.description]
71
141
  rows = await cursor.fetchall()
72
142
  result = [dict(zip(columns, row)) for row in rows]
73
143
  return result
74
144
  elif query_type == QueryTypes.ONE:
75
- rows = await cursor.fetchone()
76
- result = dict(zip(columns, rows))
145
+ columns = [desc[0] for desc in cursor.description]
146
+ row = await cursor.fetchone()
147
+ # print("rows_reuslt", rows)
148
+ result = dict(zip(columns, row)) if row else {}
77
149
  return result
78
150
  elif query_type == QueryTypes.MANY:
151
+ columns = [desc[0] for desc in cursor.description]
79
152
  rows = await cursor.fetchmany(size)
80
153
  result = [dict(zip(columns, row)) for row in rows]
81
154
  return result
82
155
  elif query_type == QueryTypes.COUNT:
83
156
  rs = await cursor.fetchone()
84
- return rs[0]
157
+ return rs[0] if row else 0
85
158
  elif query_type == QueryTypes.CREATE:
86
159
  result = cursor.rowcount > 0
87
160
  id = cursor.lastrowid
88
161
  return result, id
162
+ elif query_type == QueryTypes.CREATEBATCH:
163
+ count = cursor.rowcount
164
+ id = cursor.lastrowid
165
+ return count, id
89
166
  elif query_type == QueryTypes.UPDATE:
90
167
  return cursor.rowcount > 0
168
+ elif query_type == QueryTypes.UPDATEBATCH:
169
+ return cursor.rowcount
91
170
  elif query_type == QueryTypes.DELETE:
92
171
  return cursor.rowcount > 0
93
172
 
94
- self.pool.release(conn)
173
+ self.pool.release(cursor)
95
174
 
96
175
  async def create_engine(self, **kw):
97
176
  try:
@@ -100,17 +179,20 @@ class Postgres(MeteBase):
100
179
  user = (kw.get("username"),)
101
180
  password = (str(kw.get("password", "")),)
102
181
  db = (kw.get("db"),)
103
- dsn = f"dbname={db[0]} user={user[0]} password={password[0]} host={host[0]} port={port[0]}"
104
- self.pool = await aiopg.create_pool(
182
+ dsn = f"dbname={db[0]} user={user[0]} password={password[0]} host={host[0]} port={port[0]}" # aiopg
183
+ # dsn = f"postgres://{user[0]}:{password[0]}@{host[0]}:{port[0]}/{db[0]}" #asyncpg
184
+ self.pool = await pg.create_pool(
105
185
  dsn=dsn,
106
186
  timeout=kw.get("timeout"),
107
- echo=kw.get("echo", False),
108
- maxsize=kw.get("maxsize", 10),
109
- minsize=kw.get("pool_size", 5),
187
+ echo=kw.get("echo", False), # aiopg
188
+ # max_size=kw.get("maxsize", 10), #asyncpg
189
+ # min_size=kw.get("minsize", 5), #asyncpg
190
+ maxsize=kw.get("maxsize", 10), # aiopg
191
+ minsize=kw.get("minsize", 5), # aiopg
110
192
  )
111
193
  print_info(f"Database connection successfuly for postgres/{kw.get('db')}")
112
194
  except Exception as e:
113
- # print_error(e)
195
+ print_error(e)
114
196
  # print(traceback.format_exc())
115
197
  print_error(
116
198
  f"Database connection failed,the instance : postgres/{kw.get('db')}"
@@ -144,7 +226,7 @@ class Mysql(MeteBase):
144
226
 
145
227
  async def query(self, sql, params=None, query_type: QueryTypes = 2, size: int = 10):
146
228
  sql = sql.replace("?", "%s")
147
- print_info("sql", sql, params)
229
+ # print_info("sql", sql, params)
148
230
  if not self.pool:
149
231
  raise ValueError("must be create_engine first.")
150
232
  if self.pool._closing or self.pool._closed:
@@ -152,7 +234,13 @@ class Mysql(MeteBase):
152
234
  async with self.pool.acquire() as conn:
153
235
  async with conn.cursor() as cursor:
154
236
  # current_cursor = getattr(cursor, 'lastrowid', None)
155
- await cursor.execute(sql, params)
237
+ if (
238
+ query_type == QueryTypes.CREATEBATCH
239
+ or query_type == QueryTypes.UPDATEBATCH
240
+ ):
241
+ await cursor.executemany(sql, params)
242
+ else:
243
+ await cursor.execute(sql, params)
156
244
 
157
245
  # if query_type.value > 4:
158
246
  # if self.driver == "mysql":
@@ -176,8 +264,14 @@ class Mysql(MeteBase):
176
264
  result = cursor.rowcount > 0
177
265
  id = cursor.lastrowid
178
266
  return result, id
267
+ elif query_type == QueryTypes.CREATEBATCH:
268
+ count = cursor.rowcount
269
+ id = cursor.lastrowid
270
+ return count, id
179
271
  elif query_type == QueryTypes.UPDATE:
180
272
  return cursor.rowcount > 0
273
+ elif query_type == QueryTypes.UPDATEBATCH:
274
+ return cursor.rowcount
181
275
  elif query_type == QueryTypes.DELETE:
182
276
  return cursor.rowcount > 0
183
277
 
@@ -195,7 +289,7 @@ class Mysql(MeteBase):
195
289
  charset=kw.get("charset", "utf8"),
196
290
  autocommit=False, # kw.get("autocommit", False),
197
291
  maxsize=kw.get("maxsize", 10),
198
- minsize=kw.get("pool_size", 5),
292
+ minsize=kw.get("minsize", 5),
199
293
  cursorclass=AttrDictCursor,
200
294
  loop=loop,
201
295
  )
@@ -9,6 +9,8 @@ class QueryTypes(Enum):
9
9
  CREATE = 5
10
10
  UPDATE = 6
11
11
  DELETE = 7
12
+ CREATEBATCH = 8
13
+ UPDATEBATCH = 9
12
14
 
13
15
 
14
16
  class MeteBase:
@@ -60,9 +62,15 @@ class MeteBase:
60
62
 
61
63
  async def update(self, sql, params):
62
64
  return await self.query(sql, params, QueryTypes.UPDATE)
65
+
66
+ async def update_batch(self, sql, params):
67
+ return await self.query(sql, params, QueryTypes.UPDATEBATCH)
63
68
 
64
69
  async def delete(self, sql, params):
65
70
  return await self.query(sql, params, QueryTypes.DELETE)
66
71
 
67
72
  async def create(self, sql, params):
68
73
  return await self.query(sql, params, QueryTypes.CREATE)
74
+
75
+ async def create_batch(self, sql, params):
76
+ return await self.query(sql, params, QueryTypes.CREATEBATCH)
@@ -354,6 +354,7 @@ class Field(FieldBase):
354
354
  primary_key=False, # 主键
355
355
  charset=None, # 编码
356
356
  max_length=None, # 长度
357
+ scale_length=None, # 精度
357
358
  auto_increment=False, # 自增
358
359
  NOT_NULL=False, # 非空
359
360
  created_generated=False, # 创建时for datetime
@@ -371,6 +372,7 @@ class Field(FieldBase):
371
372
  self.charset = charset
372
373
  self.default = default
373
374
  self.max_length = max_length
375
+ self.scale_length = scale_length
374
376
  self.auto_increment = auto_increment
375
377
  self.NOT_NULL = NOT_NULL
376
378
  self.created_generated = created_generated
@@ -3,9 +3,12 @@ from ..logging import print_warn
3
3
  from functools import reduce
4
4
  import operator
5
5
  import copy
6
+ import re
6
7
  import datetime
7
8
  from ..utils.common import Object
8
9
 
10
+ __ALL__ = ("models", "Model")
11
+
9
12
 
10
13
  class ModelMetaclass(type):
11
14
  # def __init__(self, **kw):
@@ -127,6 +130,8 @@ class Model(metaclass=ModelMetaclass):
127
130
 
128
131
  def _get_primary(self):
129
132
  pk = self.__primary_key__
133
+ if pk is None:
134
+ return None, None
130
135
  pkf = getattr(self, pk)
131
136
  return pk, pkf.value
132
137
 
@@ -144,14 +149,14 @@ class Model(metaclass=ModelMetaclass):
144
149
  @classmethod
145
150
  def use(cls, pool=None):
146
151
  cls.__pool__ = pool
152
+ cls.__ispg = cls.__pool__.driver == "postgres"
147
153
  return cls()
148
154
 
149
155
  def select(cls, *args):
150
156
  cols = []
151
- is_pg = cls.__pool__.driver == "postgres"
152
157
  for col in args:
153
158
  if isinstance(col, Field):
154
- cols.append(col.name if is_pg else col.full_name)
159
+ cols.append(col.name if cls.__ispg else col.full_name)
155
160
  elif isinstance(col, Function):
156
161
  q, p = col.sql()
157
162
  cols.append(q)
@@ -190,12 +195,11 @@ class Model(metaclass=ModelMetaclass):
190
195
 
191
196
  def order_by(self, *args):
192
197
  by = []
193
- is_pg = self.__pool__.driver == "postgres"
194
198
  for f in args:
195
199
  if isinstance(f, str):
196
200
  by.append(f)
197
201
  else:
198
- by.append(f"{f.lhs.name if is_pg else f.lhs.full_name} {f.op}")
202
+ by.append(f"{f.lhs.name if self.__ispg else f.lhs.full_name} {f.op}")
199
203
  if self.__order_by__ is not None:
200
204
  by = self.__order_by__ + by
201
205
  self.__order_by__ = by
@@ -203,17 +207,15 @@ class Model(metaclass=ModelMetaclass):
203
207
 
204
208
  def group_by(self, *args):
205
209
  by = []
206
- is_pg = self.__pool__.driver == "postgres"
207
210
  for f in args:
208
- by.append(f.name if is_pg else f.full_name)
211
+ by.append(f.name if self.__ispg else f.full_name)
209
212
  if self.__group_by__ is not None:
210
213
  by = self.__group_by__ + by
211
214
  self.__group_by__ = by
212
215
  return self
213
216
 
214
- def _get_key_args(self, args):
217
+ def _format_data(self, action, args):
215
218
  data = dict()
216
- md = None
217
219
  if args is None or not args:
218
220
  for k in self.__fields__:
219
221
  f = getattr(self, k)
@@ -223,13 +225,19 @@ class Model(metaclass=ModelMetaclass):
223
225
  data = args
224
226
  else:
225
227
  for item in args:
226
- md = item
227
228
  if isinstance(item, Model):
228
229
  for k in item.__dict__:
229
- data[k] = item[k]
230
+ if (action == "u" and item[k].value) or action == "i":
231
+ data[k] = item[k]
232
+
230
233
  else: # for object
231
234
  for k, v in item.items():
232
235
  data[k] = v
236
+
237
+ return data
238
+
239
+ def _get_update_key_args(self, action, args):
240
+ data = self._format_data(action, args)
233
241
  keys = []
234
242
  params = []
235
243
  for k, v in data.items():
@@ -246,7 +254,48 @@ class Model(metaclass=ModelMetaclass):
246
254
  elif v is not None: # fix sql format %s
247
255
  keys.append("`%s`=?" % k)
248
256
  params.append(v)
249
- return ",".join(keys), params, md
257
+ return ",".join(keys), params
258
+
259
+ def _get_insert_key_args(self, action, args):
260
+ data = self._format_data(action, args)
261
+ keys = []
262
+ params = []
263
+ for k, v in data.items():
264
+ if isinstance(v, Field):
265
+ value = v.value
266
+ if value is None:
267
+ if v.created_generated == True or v.update_generated == True:
268
+ value = datetime.datetime.now()
269
+ else:
270
+ value = v.default
271
+ if value is not None:
272
+ keys.append(k)
273
+ params.append(value)
274
+ elif v is not None: # fix sql format %s
275
+ keys.append(k)
276
+ params.append(v)
277
+ return keys, params
278
+
279
+ def _get_batch_keys_values(self, items: list):
280
+ keys = []
281
+ values = []
282
+ item = items[0]
283
+ if isinstance(item, Model):
284
+ keys = item.__dict__.keys()
285
+ elif isinstance(item, dict):
286
+ keys = item.keys()
287
+
288
+ for item in items:
289
+ value = []
290
+ if isinstance(item, Model):
291
+ for k in item.__dict__:
292
+ value.append(item[k].value)
293
+ elif isinstance(item, dict): # for object
294
+ for k, v in item.items():
295
+ value.append(v)
296
+ value = tuple(key for key in value)
297
+ values.append(value)
298
+ return keys, values
250
299
 
251
300
  def _merge_params(self, p):
252
301
  if p is not None:
@@ -292,10 +341,11 @@ class Model(metaclass=ModelMetaclass):
292
341
  async def one(self):
293
342
  self.limit(1)
294
343
  sql = self._sql()
344
+ sql = self._exchange_sql(sql)
295
345
  args = self.__params__
296
346
  rs = await self.__pool__.one(sql, args)
297
347
  if rs:
298
- ## join 时返回dict
348
+ # join 时返回dict
299
349
  result = Object(rs) if self.__join__ is not None else self(**rs)
300
350
  self._reset()
301
351
  return result
@@ -306,10 +356,29 @@ class Model(metaclass=ModelMetaclass):
306
356
 
307
357
  async def all(self):
308
358
  sql = self._sql()
359
+ sql = self._exchange_sql(sql)
309
360
  args = self.__params__
310
361
  self._reset()
311
362
  return await self.__pool__.all(sql, args)
312
363
 
364
+ def _exchange_sql(self, sql: str):
365
+ if self.__ispg:
366
+ sql = sql.replace("CURDATE()", "CURRENT_DATE")
367
+ sql = sql.replace("NOW()", "CURRENT_TIMESTAMP")
368
+
369
+ pattern = r"INTERVAL\s+(\d+)\s+(DAY|MONTH|YEAR|HOUR|MINUTE|SECOND)"
370
+ matches = re.findall(pattern, sql)
371
+
372
+ def replacement(match):
373
+ number = match.group(1)
374
+ unit = match.group(2).lower() # 转换为小写
375
+ return f"INTERVAL '{number} {unit}'"
376
+
377
+ if matches:
378
+ sql = re.sub(pattern, replacement, sql)
379
+
380
+ return sql
381
+
313
382
  async def update(self, *args, **kw) -> bool:
314
383
  """
315
384
  Update data
@@ -318,7 +387,7 @@ class Model(metaclass=ModelMetaclass):
318
387
  # where = self.__where__
319
388
  where = self._literal("WHERE", self.__where__)
320
389
 
321
- keys, params, md = self._get_key_args(args or kw)
390
+ keys, params = self._get_update_key_args("u", args or kw)
322
391
 
323
392
  sql = f"update `{table}` set {keys} {where}"
324
393
 
@@ -339,6 +408,7 @@ class Model(metaclass=ModelMetaclass):
339
408
  else:
340
409
  raise "Need where or primary key"
341
410
  self._reset()
411
+ sql = self._exchange_sql(sql)
342
412
  return await self.__pool__.update(sql, params)
343
413
 
344
414
  async def delete(self) -> bool:
@@ -358,15 +428,26 @@ class Model(metaclass=ModelMetaclass):
358
428
  else:
359
429
  raise "need where or primary key"
360
430
  self._reset()
431
+ sql = self._exchange_sql(sql)
361
432
  return await self.__pool__.delete(sql, args)
362
433
 
363
434
  async def insert(self, *args, **kw):
364
435
  table = self.__table__
365
- keys, params, md = self._get_key_args(args or kw)
366
- sql = f"insert into `{table}` set {keys}"
436
+ keys, params = self._get_insert_key_args("i", args or kw)
437
+ sql = f"insert into `{table}` ({','.join(keys)}) values ({','.join(['?' for k in keys])})"
367
438
  self._reset()
439
+ params = tuple(key for key in params)
368
440
  return await self.__pool__.create(sql, params)
369
441
 
442
+ async def insert_batch(self, items: list):
443
+ table = self.__table__
444
+ if len(items) == 0:
445
+ return 0
446
+ keys, params = self._get_batch_keys_values(items)
447
+ sql = f"insert into `{table}` ({','.join(keys)}) values ({','.join(['?' for k in keys])})"
448
+ self._reset()
449
+ return await self.__pool__.create_batch(sql, params)
450
+
370
451
  async def count(self):
371
452
  __where__ = copy.copy(self.__where__)
372
453
  __join__ = copy.copy(self.__join__)
@@ -376,6 +457,7 @@ class Model(metaclass=ModelMetaclass):
376
457
  aft = " ".join([JOIN, WHERE])
377
458
  sql = f"SELECT COUNT(*) FROM {cls.__table__} {aft}"
378
459
  args = cls.__params__
460
+ sql = self._exchange_sql(sql)
379
461
  return await cls.__pool__.count(sql, args)
380
462
 
381
463
 
@@ -512,6 +594,7 @@ class Models(object):
512
594
  default=None,
513
595
  not_null=False,
514
596
  max_length=None,
597
+ scale_length=None,
515
598
  unsigned=False,
516
599
  comment=None,
517
600
  ):
@@ -521,10 +604,33 @@ class Models(object):
521
604
  default,
522
605
  NOT_NULL=not_null,
523
606
  max_length=max_length,
607
+ scale_length=scale_length,
524
608
  unsigned=unsigned,
525
609
  comment=comment,
526
610
  )
527
611
 
612
+ class NumericField(Field):
613
+ def __init__(
614
+ self,
615
+ name=None,
616
+ default=0.0,
617
+ not_null=False,
618
+ max_length=None,
619
+ scale_length=None,
620
+ unsigned=False,
621
+ comment=None,
622
+ ):
623
+ super().__init__(
624
+ name,
625
+ "numeric",
626
+ default,
627
+ NOT_NULL=not_null,
628
+ unsigned=unsigned,
629
+ max_length=max_length,
630
+ scale_length=scale_length,
631
+ comment=comment,
632
+ )
633
+
528
634
  class DecimalField(Field):
529
635
  def __init__(
530
636
  self,
@@ -532,6 +638,7 @@ class Models(object):
532
638
  default=0.0,
533
639
  not_null=False,
534
640
  max_length=None,
641
+ scale_length=None,
535
642
  unsigned=False,
536
643
  comment=None,
537
644
  ):
@@ -542,6 +649,7 @@ class Models(object):
542
649
  NOT_NULL=not_null,
543
650
  unsigned=unsigned,
544
651
  max_length=max_length,
652
+ scale_length=scale_length,
545
653
  comment=comment,
546
654
  )
547
655
 
@@ -5,7 +5,8 @@ __author__ = "chuchur/chuchur.com"
5
5
 
6
6
  import hashlib, hmac, time, base64
7
7
  from curl_cffi import requests as http
8
- from ..logging import print_error,print_info
8
+ from ..logging import print_error, print_info
9
+
9
10
 
10
11
  class Client(object):
11
12
  def __init__(self, **kw):
@@ -6,12 +6,12 @@ import importlib
6
6
 
7
7
 
8
8
  def snake_to_camel(snake_str):
9
- components = snake_str.split('_')
9
+ components = snake_str.split("_")
10
10
  # camel_str = components[0] + ''.join(x.title() for x in components[1:])
11
11
  # x.capitalize() //first
12
12
  # x.title() //all
13
13
  # return camel_str[0].upper() + camel_str[1:]
14
- return ''.join(x.title() for x in components[0:])
14
+ return "".join(x.title() for x in components[0:])
15
15
 
16
16
 
17
17
  async def create_model(pool, table_name) -> str:
@@ -27,6 +27,7 @@ async def create_model(pool, table_name) -> str:
27
27
  tb += f"\t__table__ = '{table_name}'\n\n"
28
28
  for f in rows:
29
29
  fields = get_col(f, pool.driver)
30
+ # print("fields",fields)
30
31
  name = fields["name"]
31
32
  column_type = fields["column_type"]
32
33
  values = []
@@ -40,6 +41,8 @@ async def create_model(pool, table_name) -> str:
40
41
  if isinstance(fields["max_length"], str) and "," in fields["max_length"]
41
42
  else f"max_length={fields['max_length']}"
42
43
  )
44
+ if fields["scale_length"]:
45
+ values.append(f"scale_length={fields['scale_length']}")
43
46
  if fields["default"]:
44
47
  values.append(f"default='{fields['default']}'")
45
48
  if fields["auto_increment"]:
@@ -65,16 +68,28 @@ async def get_table_cols(pool, table_name):
65
68
  if pool.driver == "mysql":
66
69
  return await pool.all(f"show full COLUMNS from `{table_name}`", None)
67
70
  elif pool.driver == "postgres":
71
+ pri_row = await pool.one(
72
+ f"SELECT column_name from information_schema.key_column_usage WHERE table_name = '{table_name}'",
73
+ None,
74
+ )
68
75
  sql = f"""SELECT column_name Field,
69
76
  column_default Default,
70
77
  data_type column_type,
71
78
  is_nullable Null,
72
79
  numeric_precision num_length,
80
+ numeric_scale scale_length,
73
81
  character_maximum_length str_length,
74
82
  datetime_precision date_length,
75
83
  col_description('{table_name}'::regclass, ordinal_position) Comment
76
84
  FROM information_schema.columns WHERE table_name ='{table_name}'"""
77
- return await pool.all(sql, None)
85
+
86
+ rows = await pool.all(sql, None)
87
+ for row in rows:
88
+ row["Key"] = "PRI" if pri_row and pri_row["column_name"] == row["field"] else None
89
+
90
+ # print("rows", rows)
91
+
92
+ return rows
78
93
  else:
79
94
  raise ValueError("Database not support.")
80
95
 
@@ -91,7 +106,6 @@ async def get_all_tables(pool):
91
106
  raise ValueError("Database not support.")
92
107
 
93
108
 
94
-
95
109
  async def create_models(pool, save_path: str = None, tables: list = None):
96
110
  """
97
111
  Create models
@@ -201,16 +215,17 @@ def get_col(field, driver="mysql"):
201
215
  fields["column_type"] = t.groups()[0]
202
216
  fields["max_length"] = t.groups()[1]
203
217
  return fields
204
- elif driver == "postgres":
218
+ elif driver == "postgres" or driver == "postgresql":
205
219
  fields = {
206
220
  "name": field["field"],
207
221
  "column_type": field["column_type"].replace(" ", "_"),
208
- "primary_key": False,
222
+ "primary_key": field["Key"] == "PRI",
209
223
  "default": field["default"],
210
224
  "charset": None,
211
225
  "max_length": field["num_length"]
212
226
  or field["str_length"]
213
227
  or field["date_length"],
228
+ "scale_length": field["scale_length"],
214
229
  "auto_increment": None,
215
230
  "NOT_NULL": field["null"] == "NO",
216
231
  "created_generated": None,
@@ -224,11 +239,12 @@ def get_col_sql(field):
224
239
  sql = f"`{field.name}` {field.column_type}"
225
240
 
226
241
  if field.max_length:
227
- sql += (
228
- f"{field.max_length}"
229
- if isinstance(field.max_length, tuple)
230
- else f"({field.max_length})"
231
- )
242
+ if isinstance(field.max_length, tuple):
243
+ sql += f"{field.max_length}"
244
+ elif field.scale_length:
245
+ sql += f"({field.max_length},{field.scale_length})"
246
+ else:
247
+ sql += f"({field.max_length})"
232
248
 
233
249
  if field.charset:
234
250
  # _ci 不区分大小写 _cs Yes
@@ -237,7 +253,8 @@ def get_col_sql(field):
237
253
  if field.primary_key:
238
254
  sql += " PRIMARY KEY"
239
255
  if field.auto_increment:
240
- sql += " AUTO_INCREMENT"
256
+ # todo: postgres SERIAL
257
+ sql += " AUTO_INCREMENT"
241
258
  if field.NOT_NULL:
242
259
  sql += " NOT NULL"
243
260
  if field.default:
@@ -279,14 +296,15 @@ class ColTypes(enum.Enum):
279
296
  float = "Float"
280
297
  double = "Double"
281
298
  decimal = "Decimal"
299
+ numeric = "Numeric"
282
300
  date = "Date"
283
301
  datetime = "Datetime"
284
302
  timestamp = "Timestamp"
285
303
  json = "Json"
286
304
  # pg
287
305
  timestamp_with_time_zone = "Timestamp"
306
+ timestamp_without_time_zone = "Timestamp"
288
307
  character_varying = "VarChar"
308
+ character = "Char"
289
309
  integer = "Integer"
290
310
  boolean = "Boolean"
291
-
292
-
@@ -0,0 +1,86 @@
1
+
2
+ import time
3
+ import threading
4
+
5
+
6
+ """
7
+ 雪花算法(全局唯一id)
8
+
9
+ # 最早是Twitter公司在其内部用于分布式环境下生成唯一ID
10
+
11
+ # Twitter's Snowflake algorithm implementation which is used to generate distributed IDs.
12
+
13
+ # https://github.com/twitter-archive/snowflake/blob/snowflake-2010/src/main/scala/com/twitter/service/snowflake/IdWorker.scala
14
+ """
15
+
16
+
17
+ class IdWorker:
18
+ def __init__(self, worker_id, datacenter_id, sequence=0):
19
+ # 位移值
20
+ self.worker_id_bits = 5
21
+ self.datacenter_id_bits = 5
22
+ self.sequence_bits = 12
23
+
24
+ self.max_worker_id = -1 ^ (-1 << self.worker_id_bits)
25
+ self.max_datacenter_id = -1 ^ (-1 << self.datacenter_id_bits)
26
+
27
+ # 参数校验
28
+ if worker_id > self.max_worker_id or worker_id < 0:
29
+ raise ValueError(f'worker_id must be between 0 and {self.max_worker_id}')
30
+ if datacenter_id > self.max_datacenter_id or datacenter_id < 0:
31
+ raise ValueError(f'datacenter_id must be between 0 and {self.max_datacenter_id}')
32
+
33
+ self.worker_id = worker_id
34
+ self.datacenter_id = datacenter_id
35
+ self.sequence = sequence
36
+ self.lock = threading.Lock()
37
+
38
+ # Twitter Epoch (2010-11-04 01:42:54 UTC)
39
+ self.twepoch = 1288834974657
40
+
41
+
42
+
43
+
44
+ self.worker_id_shift = self.sequence_bits
45
+ self.datacenter_id_shift = self.sequence_bits + self.worker_id_bits
46
+ self.timestamp_left_shift = self.sequence_bits + self.worker_id_bits + self.datacenter_id_bits
47
+ self.sequence_mask = -1 ^ (-1 << self.sequence_bits)
48
+
49
+ self.last_timestamp = -1
50
+
51
+ def _time_gen(self):
52
+ return int(time.time() * 1000)
53
+
54
+ def _til_next_millis(self, last_timestamp):
55
+ timestamp = self._time_gen()
56
+ while timestamp <= last_timestamp:
57
+ timestamp = self._time_gen()
58
+ return timestamp
59
+
60
+ def next_id(self):
61
+ with self.lock:
62
+ timestamp = self._time_gen()
63
+
64
+ if timestamp < self.last_timestamp:
65
+ raise Exception(f'Clock moved backwards. Refusing to generate id for {self.last_timestamp - timestamp} milliseconds')
66
+
67
+ if self.last_timestamp == timestamp:
68
+ self.sequence = (self.sequence + 1) & self.sequence_mask
69
+ if self.sequence == 0:
70
+ timestamp = self._til_next_millis(self.last_timestamp)
71
+ else:
72
+ self.sequence = 0
73
+
74
+ self.last_timestamp = timestamp
75
+
76
+ id = ((timestamp - self.twepoch) << self.timestamp_left_shift) | \
77
+ (self.datacenter_id << self.datacenter_id_shift) | \
78
+ (self.worker_id << self.worker_id_shift) | \
79
+ self.sequence
80
+
81
+ return id
82
+
83
+
84
+ # 示例使用
85
+ worker = IdWorker(worker_id=1, datacenter_id=1)
86
+ print(worker.next_id())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudoll
3
- Version: 2.2.20
3
+ Version: 2.2.24
4
4
  Summary: 辅助快速创建可分布的微服务。
5
5
  Home-page: https://gitee.com/chuchur/cloudoll-py
6
6
  Author: chuchur
@@ -31,6 +31,7 @@ cloudoll/utils/__init__.py
31
31
  cloudoll/utils/common.py
32
32
  cloudoll/utils/m2d.py
33
33
  cloudoll/utils/patch_loop.py
34
+ cloudoll/utils/snowflake.py
34
35
  cloudoll/utils/watch.py
35
36
  cloudoll/web/__init__.py
36
37
  cloudoll/web/exception.py
@@ -1 +0,0 @@
1
- __version__ = "2.2.20"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes