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.
Files changed (43) hide show
  1. {cloudoll-2.1.0 → cloudoll-2.2.0}/PKG-INFO +26 -10
  2. {cloudoll-2.1.0 → cloudoll-2.2.0}/README.md +25 -9
  3. cloudoll-2.2.0/cloudoll/__init__.py +1 -0
  4. {cloudoll-2.1.0 → 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.1.0 → cloudoll-2.2.0}/cloudoll/orm/field.py +17 -7
  8. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/orm/model.py +106 -91
  9. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/orm/mysql.py +6 -0
  10. cloudoll-2.2.0/cloudoll/orm/parse.py +139 -0
  11. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/orm/postgres.py +15 -9
  12. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/utils/m2d.py +123 -51
  13. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/__init__.py +91 -48
  14. cloudoll-2.2.0/cloudoll/web/exception.py +63 -0
  15. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/PKG-INFO +26 -10
  16. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/SOURCES.txt +2 -0
  17. cloudoll-2.1.0/cloudoll/__init__.py +0 -1
  18. cloudoll-2.1.0/cloudoll/orm/base.py +0 -130
  19. cloudoll-2.1.0/cloudoll/utils/__init__.py +0 -0
  20. {cloudoll-2.1.0 → cloudoll-2.2.0}/LICENSE +0 -0
  21. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/__main__.py +0 -0
  22. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/error/__init__.py +0 -0
  23. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/error/errors.py +0 -0
  24. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/logging/__init__.py +0 -0
  25. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/mail/__init__.py +0 -0
  26. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/mail/smtp.py +0 -0
  27. {cloudoll-2.1.0/cloudoll/orm → cloudoll-2.2.0/cloudoll/robot}/__init__.py +0 -0
  28. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/robot/dingtalk.py +0 -0
  29. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/robot/feishu.py +0 -0
  30. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/robot/slack.py +0 -0
  31. {cloudoll-2.1.0/cloudoll/robot → cloudoll-2.2.0/cloudoll/utils}/__init__.py +0 -0
  32. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/utils/common.py +0 -0
  33. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/utils/watch.py +0 -0
  34. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/html.py +0 -0
  35. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/http.py +0 -0
  36. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/jwt.py +0 -0
  37. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll/web/settings.py +0 -0
  38. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/dependency_links.txt +0 -0
  39. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/entry_points.txt +0 -0
  40. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/requires.txt +0 -0
  41. {cloudoll-2.1.0 → cloudoll-2.2.0}/cloudoll.egg-info/top_level.txt +0 -0
  42. {cloudoll-2.1.0 → cloudoll-2.2.0}/setup.cfg +0 -0
  43. {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.1.0
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)
@@ -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.get_value()}{self.op}"
276
+ cal = f"{self.lhs.value}{self.op}"
271
277
  if isinstance(self.rhs, FieldBase):
272
- return str(eval(f"{cal}{self.rhs.get_value()}"))
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 set_value(self, value):
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