cloudoll 2.2.23__tar.gz → 2.2.26__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.23 → cloudoll-2.2.26}/PKG-INFO +14 -1
  2. cloudoll-2.2.26/cloudoll/__init__.py +1 -0
  3. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/orm/__init__.py +108 -18
  4. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/orm/base.py +8 -0
  5. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/orm/field.py +62 -35
  6. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/orm/model.py +69 -12
  7. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/robot/feishu.py +2 -1
  8. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/utils/m2d.py +2 -1
  9. cloudoll-2.2.26/cloudoll/utils/snowflake.py +86 -0
  10. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll.egg-info/PKG-INFO +14 -1
  11. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll.egg-info/SOURCES.txt +1 -0
  12. cloudoll-2.2.23/cloudoll/__init__.py +0 -1
  13. {cloudoll-2.2.23 → cloudoll-2.2.26}/LICENSE +0 -0
  14. {cloudoll-2.2.23 → cloudoll-2.2.26}/README.md +0 -0
  15. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/__main__.py +0 -0
  16. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/cli.py +0 -0
  17. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/error/__init__.py +0 -0
  18. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/error/errors.py +0 -0
  19. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/logging/__init__.py +0 -0
  20. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/mail/__init__.py +0 -0
  21. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/mail/smtp.py +0 -0
  22. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/orm/err.py +0 -0
  23. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/orm/mysql.py +0 -0
  24. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/orm/parse.py +0 -0
  25. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/orm/postgres.py +0 -0
  26. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/robot/__init__.py +0 -0
  27. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/robot/dingtalk.py +0 -0
  28. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/robot/slack.py +0 -0
  29. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/utils/__init__.py +0 -0
  30. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/utils/common.py +0 -0
  31. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/utils/patch_loop.py +0 -0
  32. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/utils/watch.py +0 -0
  33. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/web/__init__.py +0 -0
  34. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/web/exception.py +0 -0
  35. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/web/html.py +0 -0
  36. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/web/http.py +0 -0
  37. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/web/jwt.py +0 -0
  38. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll/web/settings.py +0 -0
  39. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll.egg-info/dependency_links.txt +0 -0
  40. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll.egg-info/entry_points.txt +0 -0
  41. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll.egg-info/requires.txt +0 -0
  42. {cloudoll-2.2.23 → cloudoll-2.2.26}/cloudoll.egg-info/top_level.txt +0 -0
  43. {cloudoll-2.2.23 → cloudoll-2.2.26}/setup.cfg +0 -0
  44. {cloudoll-2.2.23 → cloudoll-2.2.26}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudoll
3
- Version: 2.2.23
3
+ Version: 2.2.26
4
4
  Summary: 辅助快速创建可分布的微服务。
5
5
  Home-page: https://gitee.com/chuchur/cloudoll-py
6
6
  Author: chuchur
@@ -15,6 +15,19 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
15
15
  Requires-Python: >=3.6.0
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
+ Requires-Dist: watchfiles
19
+ Requires-Dist: curl_cffi
20
+ Requires-Dist: colorlog
21
+ Requires-Dist: aiomysql
22
+ Requires-Dist: aiopg
23
+ Requires-Dist: aiohttp
24
+ Requires-Dist: redis[hiredis]
25
+ Requires-Dist: aiomcache
26
+ Requires-Dist: jinja2
27
+ Requires-Dist: pyjwt
28
+ Requires-Dist: aiohttp_session[secure]
29
+ Requires-Dist: PyYAML
30
+ Requires-Dist: click
18
31
 
19
32
 
20
33
  # cloudoll 云端玩具
@@ -0,0 +1 @@
1
+ __version__ = "2.2.26"
@@ -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")
@@ -48,23 +55,85 @@ 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
- sql = sql.replace("?", "%s").replace("`", "")
118
+ sql = sql.replace("?", "%s").replace('`', '"')
53
119
  print_info("sql", sql, params)
54
120
  if not self.pool:
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
- # print(columns)
66
- # await conn.commit()
67
- # elif self.driver == "postgres":
68
137
  result = None
69
138
 
70
139
  if query_type == QueryTypes.ALL:
@@ -90,12 +159,18 @@ class Postgres(MeteBase):
90
159
  result = cursor.rowcount > 0
91
160
  id = cursor.lastrowid
92
161
  return result, id
162
+ elif query_type == QueryTypes.CREATEBATCH:
163
+ count = cursor.rowcount
164
+ id = cursor.lastrowid
165
+ return count, id
93
166
  elif query_type == QueryTypes.UPDATE:
94
167
  return cursor.rowcount > 0
168
+ elif query_type == QueryTypes.UPDATEBATCH:
169
+ return cursor.rowcount
95
170
  elif query_type == QueryTypes.DELETE:
96
171
  return cursor.rowcount > 0
97
172
 
98
- self.pool.release(conn)
173
+ self.pool.release(cursor)
99
174
 
100
175
  async def create_engine(self, **kw):
101
176
  try:
@@ -104,17 +179,20 @@ class Postgres(MeteBase):
104
179
  user = (kw.get("username"),)
105
180
  password = (str(kw.get("password", "")),)
106
181
  db = (kw.get("db"),)
107
- dsn = f"dbname={db[0]} user={user[0]} password={password[0]} host={host[0]} port={port[0]}"
108
- 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(
109
185
  dsn=dsn,
110
186
  timeout=kw.get("timeout"),
111
- echo=kw.get("echo", False),
112
- maxsize=kw.get("maxsize", 10),
113
- 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
114
192
  )
115
193
  print_info(f"Database connection successfuly for postgres/{kw.get('db')}")
116
194
  except Exception as e:
117
- # print_error(e)
195
+ print_error(e)
118
196
  # print(traceback.format_exc())
119
197
  print_error(
120
198
  f"Database connection failed,the instance : postgres/{kw.get('db')}"
@@ -148,7 +226,7 @@ class Mysql(MeteBase):
148
226
 
149
227
  async def query(self, sql, params=None, query_type: QueryTypes = 2, size: int = 10):
150
228
  sql = sql.replace("?", "%s")
151
- print_info("sql", sql, params)
229
+ # print_info("sql", sql, params)
152
230
  if not self.pool:
153
231
  raise ValueError("must be create_engine first.")
154
232
  if self.pool._closing or self.pool._closed:
@@ -156,7 +234,13 @@ class Mysql(MeteBase):
156
234
  async with self.pool.acquire() as conn:
157
235
  async with conn.cursor() as cursor:
158
236
  # current_cursor = getattr(cursor, 'lastrowid', None)
159
- 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)
160
244
 
161
245
  # if query_type.value > 4:
162
246
  # if self.driver == "mysql":
@@ -180,8 +264,14 @@ class Mysql(MeteBase):
180
264
  result = cursor.rowcount > 0
181
265
  id = cursor.lastrowid
182
266
  return result, id
267
+ elif query_type == QueryTypes.CREATEBATCH:
268
+ count = cursor.rowcount
269
+ id = cursor.lastrowid
270
+ return count, id
183
271
  elif query_type == QueryTypes.UPDATE:
184
272
  return cursor.rowcount > 0
273
+ elif query_type == QueryTypes.UPDATEBATCH:
274
+ return cursor.rowcount
185
275
  elif query_type == QueryTypes.DELETE:
186
276
  return cursor.rowcount > 0
187
277
 
@@ -199,7 +289,7 @@ class Mysql(MeteBase):
199
289
  charset=kw.get("charset", "utf8"),
200
290
  autocommit=False, # kw.get("autocommit", False),
201
291
  maxsize=kw.get("maxsize", 10),
202
- minsize=kw.get("pool_size", 5),
292
+ minsize=kw.get("minsize", 5),
203
293
  cursorclass=AttrDictCursor,
204
294
  loop=loop,
205
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)
@@ -35,13 +35,18 @@ OP = objdict(
35
35
  IS_THIS_WEEK="THIS_WEEK",
36
36
  IS_THIS_MONTH="THIS_MONTH",
37
37
  IS_THIS_YEAR="THIS_YEAR",
38
+ LASTED_HOURS="LASTED_HOURS",
39
+ LASTED_MINUTES="LASTED_MINUTES",
40
+ LASTED_SECONDS="LASTED_SECONDS",
41
+ LASTED_YEARS="LASTED_YEARS",
42
+ LASTED_MONTHS="LASTED_MONTHS",
43
+ LASTED_DAYS="LASTED_DAYS",
44
+ BEFORE_YEARS="BEFORE_YEARS",
45
+ BEFORE_MONTHS="BEFORE_MONTHS",
38
46
  BEFORE_DAYS="BEFORE_DAYS",
39
47
  BEFORE_HOURS="BEFORE_HOURS",
40
48
  BEFORE_MINUTES="BEFORE_MINUTES",
41
- BEFORE_YEARS="BEFORE_YEARS",
42
- AFTER_YEARS="AFTER_YEARS",
43
- AFTER_MONTHS="AFTER_MONTHS",
44
- BEFORE_MONTHS="BEFORE_MONTHS",
49
+ BEFORE_SECONDS="BEFORE_SECONDS",
45
50
  DATE_FORMAT="DATE_FORMAT",
46
51
  JSON_CONTAINS_OBJECT="JSON_CONTAINS_OBJECT",
47
52
  JSON_CONTAINS_ARRAY="JSON_CONTAINS_ARRAY",
@@ -138,6 +143,30 @@ class FieldBase(object):
138
143
  def is_this_year(self):
139
144
  return Function(self, OP.IS_THIS_YEAR)
140
145
 
146
+ def lasted_hours(self, args):
147
+ return Function(self, OP.LASTED_HOURS, args)
148
+
149
+ def lasted_minutes(self, args):
150
+ return Function(self, OP.LASTED_MINUTES, args)
151
+
152
+ def lasted_seconds(self, args):
153
+ return Function(self, OP.LASTED_SECONDS, args)
154
+
155
+ def lasted_years(self, args):
156
+ return Function(self, OP.LASTED_YEARS, args)
157
+
158
+ def lasted_months(self, args):
159
+ return Function(self, OP.LASTED_MONTHS, args)
160
+
161
+ def lasted_days(self, args):
162
+ return Function(self, OP.LASTED_DAYS, args)
163
+
164
+ def before_months(self, args):
165
+ return Function(self, OP.BEFORE_MONTHS, args)
166
+
167
+ def before_years(self, args):
168
+ return Function(self, OP.BEFORE_YEARS, args)
169
+
141
170
  def before_days(self, args):
142
171
  return Function(self, OP.BEFORE_DAYS, args)
143
172
 
@@ -147,17 +176,8 @@ class FieldBase(object):
147
176
  def before_minutes(self, args):
148
177
  return Function(self, OP.BEFORE_MINUTES, args)
149
178
 
150
- def before_years(self, args):
151
- return Function(self, OP.BEFORE_YEARS, args)
152
-
153
- def before_months(self, args):
154
- return Function(self, OP.BEFORE_MONTHS, args)
155
-
156
- def after_months(self, args):
157
- return Function(self, OP.AFTER_MONTHS, args)
158
-
159
- def after_years(self, args):
160
- return Function(self, OP.AFTER_YEARS, args)
179
+ def before_seconds(self, args):
180
+ return Function(self, OP.BEFORE_SECOBDS, args)
161
181
 
162
182
  def json_contains_object(self, key, value):
163
183
  return Function(self, OP.JSON_CONTAINS_OBJECT, (key, value))
@@ -238,20 +258,33 @@ class Function(FieldBase):
238
258
  return f"MONTH({col_name}) = MONTH(CURDATE())", None
239
259
  elif op == OP.IS_THIS_YEAR:
240
260
  return f"YEAR({col_name}) = YEAR(CURDATE())", None
241
- elif op == OP.BEFORE_DAYS:
242
- return f"DATE({col_name}) >= CURDATE() - INTERVAL {self.rpt} DAY", None
243
- elif op == OP.BEFORE_HOURS:
244
- return f"{col_name} >= NOW() - INTERVAL {self.rpt} HOUR", None
245
- elif op == OP.BEFORE_MINUTES:
261
+
262
+ elif op == OP.LASTED_SECONDS:
263
+ return f"{col_name} >= NOW() - INTERVAL {self.rpt} SECOND", None
264
+ elif op == OP.LASTED_MINUTES:
246
265
  return f"{col_name} >= NOW() - INTERVAL {self.rpt} MINUTE", None
266
+ elif op == OP.LASTED_HOURS:
267
+ return f"{col_name} >= NOW() - INTERVAL {self.rpt} HOUR", None
268
+ elif op == OP.LASTED_YEARS:
269
+ return f"{col_name} >= NOW() - INTERVAL {self.rpt} YEAR", None
270
+ elif op == OP.LASTED_MONTHS:
271
+ return f"{col_name} >= NOW() - INTERVAL {self.rpt} MONTH", None
272
+ elif op == OP.LASTED_DAYS:
273
+ return f"DATE({col_name}) >= CURDATE() - INTERVAL {self.rpt} DAY", None
274
+
247
275
  elif op == OP.BEFORE_YEARS:
248
- return f"{col_name} >= DATE_SUB(NOW(), INTERVAL {self.rpt} YEAR)", None
276
+ return f"{col_name} < NOW() - INTERVAL {self.rpt} YEAR", None
249
277
  elif op == OP.BEFORE_MONTHS:
250
- return f"{col_name} >= DATE_SUB(NOW(), INTERVAL {self.rpt} MONTH)", None
251
- elif op == OP.AFTER_YEARS:
252
- return f"{col_name} < DATE_SUB(NOW(), INTERVAL {self.rpt} YEAR)", None
253
- elif op == OP.AFTER_MONTHS:
254
- return f"{col_name} < DATE_SUB(NOW(), INTERVAL {self.rpt} MONTH)", None
278
+ return f"{col_name} < NOW() - INTERVAL {self.rpt} MONTH", None
279
+ elif op == OP.BEFORE_DAYS:
280
+ return f"{col_name} < NOW() - INTERVAL {self.rpt} DAY", None
281
+ elif op == OP.BEFORE_HOURS:
282
+ return f"{col_name} < NOW() - INTERVAL {self.rpt} HOUR", None
283
+ elif op == OP.BEFORE_MINUTES:
284
+ return f"{col_name} < NOW() - INTERVAL {self.rpt} MINUTE", None
285
+ elif op == OP.BEFORE_SECONDS:
286
+ return f"{col_name} < NOW() - INTERVAL {self.rpt} SECOND", None
287
+
255
288
  elif op == OP.JSON_CONTAINS_OBJECT:
256
289
  _k, _v = self.rpt
257
290
  key = _k.full_name if isinstance(_k, Field) else f"'{_k}'"
@@ -301,33 +334,28 @@ class Expression(FieldBase):
301
334
  q.append(_q)
302
335
  if _p:
303
336
  p = p + _p
304
- # return l.sql()
305
337
 
306
338
  q.append(f" {self.op} ")
307
339
 
308
340
  if isinstance(r, Field):
309
341
  if r._value is not None:
310
- # _value = r._value if r._value is not None else r.full_name
311
342
  q.append("?")
312
343
  p.append(r._value)
313
344
  else:
314
345
  q.append(r.full_name)
315
- # q.append(str(r._value) if r._value is not None else r.full_name)
316
346
  elif isinstance(r, FieldBase):
317
347
  _q, _p = r.sql()
318
348
  q.append(_q)
319
- p = p + _p
349
+ if _p:
350
+ p = p + _p
320
351
  elif self.op == OP.AS:
321
- # is_fun or (isinstance(r, str) and (self.op == OP.AND or self.op == OP.OR)):
322
352
  q.append(r)
323
353
  elif r is not None:
324
354
  q.append("?")
325
355
  p.append(r)
326
356
  if not is_fun:
327
357
  q.append(")")
328
- # q.append(")")
329
358
  q = [str(num) for num in q]
330
- # print("".join(q))
331
359
  return "".join(q), p
332
360
 
333
361
  # def __str__(self):
@@ -338,7 +366,6 @@ class Expression(FieldBase):
338
366
  # return str(eval(f"{cal}{self.rhs}"))
339
367
 
340
368
  def __repr__(self):
341
- # return self.__value
342
369
  cal = f"{self.lhs.value}{self.op}"
343
370
  if isinstance(self.rhs, FieldBase):
344
371
  return str(eval(f"{cal}{self.rhs.value}"))
@@ -354,7 +381,7 @@ class Field(FieldBase):
354
381
  primary_key=False, # 主键
355
382
  charset=None, # 编码
356
383
  max_length=None, # 长度
357
- scale_length=None, # 精度
384
+ scale_length=None, # 精度
358
385
  auto_increment=False, # 自增
359
386
  NOT_NULL=False, # 非空
360
387
  created_generated=False, # 创建时for datetime
@@ -7,6 +7,8 @@ import re
7
7
  import datetime
8
8
  from ..utils.common import Object
9
9
 
10
+ __ALL__ = ("models", "Model")
11
+
10
12
 
11
13
  class ModelMetaclass(type):
12
14
  # def __init__(self, **kw):
@@ -197,9 +199,7 @@ class Model(metaclass=ModelMetaclass):
197
199
  if isinstance(f, str):
198
200
  by.append(f)
199
201
  else:
200
- by.append(
201
- f"{f.lhs.name if self.__ispg else f.lhs.full_name} {f.op}"
202
- )
202
+ by.append(f"{f.lhs.name if self.__ispg else f.lhs.full_name} {f.op}")
203
203
  if self.__order_by__ is not None:
204
204
  by = self.__order_by__ + by
205
205
  self.__order_by__ = by
@@ -214,9 +214,8 @@ class Model(metaclass=ModelMetaclass):
214
214
  self.__group_by__ = by
215
215
  return self
216
216
 
217
- def _get_key_args(self, action, args):
217
+ def _format_data(self, action, args):
218
218
  data = dict()
219
- md = None
220
219
  if args is None or not args:
221
220
  for k in self.__fields__:
222
221
  f = getattr(self, k)
@@ -226,15 +225,19 @@ class Model(metaclass=ModelMetaclass):
226
225
  data = args
227
226
  else:
228
227
  for item in args:
229
- md = item
230
228
  if isinstance(item, Model):
231
229
  for k in item.__dict__:
232
- if (action == 'u' and item[k].value) or action == 'i':
230
+ if (action == "u" and item[k].value) or action == "i":
233
231
  data[k] = item[k]
234
232
 
235
233
  else: # for object
236
234
  for k, v in item.items():
237
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)
238
241
  keys = []
239
242
  params = []
240
243
  for k, v in data.items():
@@ -251,7 +254,48 @@ class Model(metaclass=ModelMetaclass):
251
254
  elif v is not None: # fix sql format %s
252
255
  keys.append("`%s`=?" % k)
253
256
  params.append(v)
254
- 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
255
299
 
256
300
  def _merge_params(self, p):
257
301
  if p is not None:
@@ -343,12 +387,13 @@ class Model(metaclass=ModelMetaclass):
343
387
  # where = self.__where__
344
388
  where = self._literal("WHERE", self.__where__)
345
389
 
346
- keys, params, md = self._get_key_args('u', args or kw)
390
+ keys, params = self._get_update_key_args("u", args or kw)
347
391
 
348
392
  sql = f"update `{table}` set {keys} {where}"
349
393
 
350
394
  if where is not None and where != "":
351
- params += self.__params__
395
+ if self.__params__:
396
+ params += self.__params__
352
397
  else:
353
398
  pk, pkv = self._get_primary()
354
399
  if pkv is None:
@@ -389,11 +434,23 @@ class Model(metaclass=ModelMetaclass):
389
434
 
390
435
  async def insert(self, *args, **kw):
391
436
  table = self.__table__
392
- keys, params, md = self._get_key_args('i', args or kw)
393
- sql = f"insert into `{table}` set {keys}"
437
+ keys, params = self._get_insert_key_args("i", args or kw)
438
+ escape_keys = [f'`{k}`' for k in keys]
439
+ sql = f"insert into `{table}` ({','.join(escape_keys)}) values ({','.join(['?' for k in keys])})"
394
440
  self._reset()
441
+ params = tuple(key for key in params)
395
442
  return await self.__pool__.create(sql, params)
396
443
 
444
+ async def insert_batch(self, items: list):
445
+ table = self.__table__
446
+ if len(items) == 0:
447
+ return 0
448
+ keys, params = self._get_batch_keys_values(items)
449
+ escape_keys = [f'`{k}`' for k in keys]
450
+ sql = f"insert into `{table}` ({','.join(escape_keys)}) values ({','.join(['?' for k in keys])})"
451
+ self._reset()
452
+ return await self.__pool__.create_batch(sql, params)
453
+
397
454
  async def count(self):
398
455
  __where__ = copy.copy(self.__where__)
399
456
  __join__ = copy.copy(self.__join__)
@@ -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):
@@ -253,7 +253,8 @@ def get_col_sql(field):
253
253
  if field.primary_key:
254
254
  sql += " PRIMARY KEY"
255
255
  if field.auto_increment:
256
- sql += " AUTO_INCREMENT"
256
+ # todo: postgres SERIAL
257
+ sql += " AUTO_INCREMENT"
257
258
  if field.NOT_NULL:
258
259
  sql += " NOT NULL"
259
260
  if field.default:
@@ -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.23
3
+ Version: 2.2.26
4
4
  Summary: 辅助快速创建可分布的微服务。
5
5
  Home-page: https://gitee.com/chuchur/cloudoll-py
6
6
  Author: chuchur
@@ -15,6 +15,19 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
15
15
  Requires-Python: >=3.6.0
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
+ Requires-Dist: watchfiles
19
+ Requires-Dist: curl_cffi
20
+ Requires-Dist: colorlog
21
+ Requires-Dist: aiomysql
22
+ Requires-Dist: aiopg
23
+ Requires-Dist: aiohttp
24
+ Requires-Dist: redis[hiredis]
25
+ Requires-Dist: aiomcache
26
+ Requires-Dist: jinja2
27
+ Requires-Dist: pyjwt
28
+ Requires-Dist: aiohttp_session[secure]
29
+ Requires-Dist: PyYAML
30
+ Requires-Dist: click
18
31
 
19
32
 
20
33
  # cloudoll 云端玩具
@@ -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.23"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes