From 2159b53baa4c86d9c1b739260824b7f89f935759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=80=9A=E9=97=BB?= Date: Fri, 18 Mar 2022 16:11:03 +0800 Subject: [PATCH 1/3] fix: tox error 1. fix only 1 line between functions in model.py 2. fix more than 1 line at end of file in model.py 3. fix imported but not used in model.py 4. fix imported but not used in api.py 5. fix line too long in api.py 6. add local file to gitignore --- examples/industrial-ai-apiserver/.gitignore | 3 +++ examples/industrial-ai-apiserver/industrialai/db/api.py | 4 ++-- examples/industrial-ai-apiserver/industrialai/db/model.py | 4 +--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/industrial-ai-apiserver/.gitignore b/examples/industrial-ai-apiserver/.gitignore index a101926..4049948 100644 --- a/examples/industrial-ai-apiserver/.gitignore +++ b/examples/industrial-ai-apiserver/.gitignore @@ -2,6 +2,9 @@ __pycache__/ *.py[cod] +# Build +E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db + # C extensions *.so diff --git a/examples/industrial-ai-apiserver/industrialai/db/api.py b/examples/industrial-ai-apiserver/industrialai/db/api.py index 2d5825d..3e45075 100644 --- a/examples/industrial-ai-apiserver/industrialai/db/api.py +++ b/examples/industrial-ai-apiserver/industrialai/db/api.py @@ -5,7 +5,6 @@ import os from pecan import abort from sqlalchemy import create_engine, func from sqlalchemy.orm import exc -from sqlalchemy.orm import joinedload from sqlalchemy.orm import sessionmaker logger = logging.getLogger(__name__) @@ -22,7 +21,8 @@ def get_engine(): return _ENGINE DB_URL = ( os.getenv("db_url") or - "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False" + "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\\ + industrial-ai-apiserver\\industrialai.db?check_same_thread=False" ) _ENGINE = create_engine(DB_URL, echo=True) return _ENGINE diff --git a/examples/industrial-ai-apiserver/industrialai/db/model.py b/examples/industrial-ai-apiserver/industrialai/db/model.py index 6c5c981..3c7492c 100644 --- a/examples/industrial-ai-apiserver/industrialai/db/model.py +++ b/examples/industrial-ai-apiserver/industrialai/db/model.py @@ -4,9 +4,7 @@ from sqlalchemy import DateTime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import ForeignKey from sqlalchemy import Integer -from sqlalchemy.orm import relationship from sqlalchemy import String -from sqlalchemy import Table Base = declarative_base() @@ -14,6 +12,7 @@ Base = declarative_base() def init_db(_ENGINE): Base.metadata.create_all(_ENGINE) + class User(Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) @@ -32,4 +31,3 @@ class Role(Base): __tablename__ = 'role' id = Column(Integer, primary_key=True) title = Column(String(32)) - -- Gitee From 2a80c525758dbe11be021f03a5136ec1f5583d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=80=9A=E9=97=BB?= Date: Tue, 24 May 2022 14:48:45 +0800 Subject: [PATCH 2/3] fixed: 1. yaml output added 2. methods edited --- .gitignore | 2 + examples/industrial-ai-apiserver/.gitignore | 3 - examples/industrial-ai-apiserver/README.md | 26 +- examples/industrial-ai-apiserver/datainit.py | 2 +- .../industrialai/api/controllers/root.py | 83 +++ .../industrialai/api/controllers/v1/role.py | 42 ++ .../industrialai/api/controllers/v1/user.py | 142 +++++ .../industrialai/db/api.py | 5 +- pecan_swagger/__init__.py | 0 pecan_swagger/decorators.py | 38 -- pecan_swagger/g.py | 487 ------------------ pecan_swagger/utils.py | 64 --- 12 files changed, 290 insertions(+), 604 deletions(-) delete mode 100644 pecan_swagger/__init__.py delete mode 100644 pecan_swagger/decorators.py delete mode 100644 pecan_swagger/g.py delete mode 100644 pecan_swagger/utils.py diff --git a/.gitignore b/.gitignore index 6d7b9c1..4a23ab3 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ docs/_build/ # PyBuilder target/ +/examples/industrial-ai-apiserver/test.json +/examples/industrial-ai-apiserver/test.yaml diff --git a/examples/industrial-ai-apiserver/.gitignore b/examples/industrial-ai-apiserver/.gitignore index 4049948..a101926 100644 --- a/examples/industrial-ai-apiserver/.gitignore +++ b/examples/industrial-ai-apiserver/.gitignore @@ -2,9 +2,6 @@ __pycache__/ *.py[cod] -# Build -E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db - # C extensions *.so diff --git a/examples/industrial-ai-apiserver/README.md b/examples/industrial-ai-apiserver/README.md index a9a8026..fd4789c 100644 --- a/examples/industrial-ai-apiserver/README.md +++ b/examples/industrial-ai-apiserver/README.md @@ -17,7 +17,7 @@ python -m virtualenv .venv pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt # 清理原数据库 -rm -rf /tmp/industrialai.db +#rm -rf /tmp/industrialai.db # 初始化数据库 python datainit.py @@ -27,7 +27,7 @@ python run.py # python run.py 0.0.0.0 8080 # 尝试访问 -curl http://localhost:8080/users/ +curl http://localhost:8080/ ``` ## 测试 @@ -40,14 +40,22 @@ stestr run tox ``` -## 容器镜像制作和部署 +## API-doc -```bash -docker build -t industrialai . +### 安装 api-doc +1. 安装npm: + - 前往 https://nodejs.org/en/ 下载node.js(集成npm)的最新版本 + - 在终端输入 npm -v ,输出npm版本号 +2. npm install apidoc –g +3. 参考: + - https://segmentfault.com/a/1190000011636199 + - https://apidocjs.com/#getting-started + - https://github.com/apidoc/apidoc -docker stop industrialai; docker rm industrialai +### 生成接口文档 -docker run -d --name industrialai -p 8888:8080 industrialai +```bash +apidoc -i industrialai/api/controllers -o api-doc +``` -curl http://localhost:8888/users/ -``` \ No newline at end of file +点击 api-doc/index.html 访问 \ No newline at end of file diff --git a/examples/industrial-ai-apiserver/datainit.py b/examples/industrial-ai-apiserver/datainit.py index bf11856..64814c8 100644 --- a/examples/industrial-ai-apiserver/datainit.py +++ b/examples/industrial-ai-apiserver/datainit.py @@ -5,7 +5,7 @@ from sqlalchemy import create_engine def init_db(): DB_URL = os.getenv('db_url') or \ - "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False" + "sqlite:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False" engine = create_engine(DB_URL, echo=True) Base.metadata.drop_all(engine) Base.metadata.create_all(engine) diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py index fd82b3a..2462069 100644 --- a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py +++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py @@ -7,7 +7,9 @@ import logging from pecan import abort from pecan import expose from pecan import request +from pecan_swagger import decorators as swagger from wsme import types as wtypes + logger = logging.getLogger(__name__) @@ -21,6 +23,7 @@ class checkForm: pwd = wtypes.text +@swagger.definitions('object') class registryForm: account = wtypes.text pwd = wtypes.text @@ -28,14 +31,60 @@ class registryForm: mail = wtypes.text name = wtypes.text number = wtypes.text + check = checkForm() +@swagger.response('myself', '401', 'fail') +@swagger.response('index', '401', 'fail') +@swagger.response('test', '200', 'test description') +@swagger.method('test', 'index', 'signin', 'pwdcheck', 'myself') +@swagger.path("/", "Root") class RootController(object): + _custom_actions = { + 'test': ['GET'], + 'signin': ['POST'], + 'pwdcheck': ['POST'], + 'myself': ['POST'] + } + + @expose('json') + def test(self): + print("123") + return "test" @expose('json') def index(self): return None + """ + @api {post} /signin signin + @apiName SignIn + @apiGroup User + + @apiBody {String} account 账号 + @apiBody {String} pwd 密码 + + @apiSuccess {String} token + @apiSuccessExample Success-Response: + { + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. + eyJleHAiOjE2NDYzNzc2MjksInVzZXJJZCI6MSwiZ3JvdXBJZCI6MSwicm9sZUlkIjozfQ. + fEqaaS8c2A_i-2W9D_L76mJac2vcyoS51g6trXr34lM" + } + + @apiError Unauthorized + @apiErrorExample {json} Error-Response: + { + "faultcode": "Client", + "faultstring": "This server could not verify that you are + authorized to access the document you requested. Either + you supplied the wrong credentials (e.g., bad password), + or your browser does not understand how to supply the + credentials required.", + "debuginfo": null + } + """ + @wsexpose(wtypes.text, body=logForm) def signin(self, logForm): account = logForm.account @@ -49,6 +98,22 @@ class RootController(object): "token": token } + """ + @api {post} /pwdcheck check + @apiName PwdCheck + @apiGroup User + + @apiBody {Integer} id 用户id + @apiBody {String} pwd 密码 + + @apiSuccess {Bool} result + @apiSuccessExample Success-Response: + HTTP/1.1 OK 200 + { + "result": True True:校验通过/False:校验失败 + } + """ + @wsexpose(wtypes.text, body=checkForm) def pwdcheck(self, checkForm): try: @@ -58,6 +123,24 @@ class RootController(object): return "Failure" return "Success" + """@api {get} /myself get_myself + @apiName GetMyself + @apiGroup User + + @apiSuccess (200) {Object} user + @apiSuccessExample Success-Response: + { + "id": 6, 用户id + "number": null, 身份证号 + "roleId": 3, 角色 + "status": 1, 状态:0/1 + "create_at": "2022-03-09 11:24:18.856936", 创建时间 + "name": "test", 名称 + "account": "testsss1-put", 账号 + "phone": "12345", 手机号 + } + """ + @expose('json') def myself(self): token = request.cookies.get("token") diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py index 34bb33b..9faace8 100644 --- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py +++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py @@ -1,10 +1,44 @@ +from industrialai.api.expose import expose as wsexpose from industrialai.pkg.page import get_page from pecan import expose from pecan import request from pecan.rest import RestController +from pecan_swagger import decorators as swagger +from wsme import types as wstypes +class Roletest(wstypes.Base): + message = wstypes.text + roleId = int + + +@swagger.response('get_all', '401', 'fail') +@swagger.method('test', 'get_all') +@swagger.path("roles", "Role", "Root") class RoleController(RestController): + _custom_actions = { + 'test': ['POST'] + } + + """ + @api {get} /v1/roles get_all_roles + @apiName GetAllRoles + @apiGroup Role + + @apiSuccess {Object[]} roles + @apiSuccessExample {type} Success-Response: + HTTP/1.1 200 OK + [ + “data":[ 角色列表 + { + "title": "expert", 角色名称 + "id": 1 角色id + }, + ] + "pages": 12 总页数 + “total": 10 总数 + ] + """ @expose('json') def get_all(self): @@ -15,3 +49,11 @@ class RoleController(RestController): "data": roles, "pages": num } + + @wsexpose(Roletest, body=Roletest) + def test(self, role): + print(role) + role.message = "out" + role.roleId = 1 + print(role) + return role diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py index 3c91327..5a83749 100644 --- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py +++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py @@ -4,9 +4,64 @@ from pecan import abort from pecan import expose from pecan import request from pecan.rest import RestController +from pecan_swagger import decorators as swagger +@swagger.response('put', '401', 'fail') +@swagger.response('post', '401', 'fail') +@swagger.response('get_one', '401', 'fail') +@swagger.response('get', '401', 'fail') +@swagger.response('get', '200', 'test description') +@swagger.parameter('get', {'name': "status", + 'in': "query", + 'description': 'Status values that need to be considered for filter', + 'required': True, + 'type': 'array', + 'items': {'type': 'string', + 'enum': ["available", "pending", "sold"], + 'default': "available"}, + 'collectionFormat': "multi"}) +@swagger.operationId('get', 'get users') +@swagger.description('get', 'get users information') +@swagger.summary('get', 'get users information') +@swagger.tags('put', 'user') +@swagger.tags('post', 'user') +@swagger.tags('get', 'user') +@swagger.tags('get_one', 'user') +@swagger.method('get', 'get_one', 'post', 'put') +@swagger.path("users", "User", "Root") class UserController(RestController): + """@api {get} /v1/users/?name= get_all_users + + @apiName GetAllUsers + @apiGroup User + + @apiParam {String} name name of user 根据名称进行查询(使用like进行匹配) + @apiParam {Integer} status status of user 根据用户状态过滤 + @apiParam {String} account account of user 根据账号进行查询(使用like进行匹配) + @apiParam {Integer} roleId role of user 根据角色进行过滤 + + @apiSuccess (200) {Object[]} users the list of user + @apiSuccessExample Success-Response: + { + "data": [ + { + "id": 6, 用户id + "number": null, 身份证号 + "roleId": 3, 角色 + "status": 1, 状态:0/1 + "create_at": "2022-03-09 11:24:18.856936", 创建时间 + "name": "test", 名称 + "account": "testsss1-put", 账号 + "phone": "12345", 手机号 + "email": null, 邮箱 + }, + ], + "pages": 10 总页数 + “total": 10 总数 + } + """ + @expose("json") def get(self, **kwargs): @@ -29,6 +84,27 @@ class UserController(RestController): "pages": pages } + """@api {get} /v1/users/ get_one_user + @apiName GetOneUser + @apiGroup User + + @apiParam {Integer} userId id of user + + @apiSuccess (200) {Object} user + @apiSuccessExample Success-Response: + { + "id": 6, 用户id + "number": null, 身份证号 + "roleId": 3, 角色 + "status": 1, 状态:0/1 + "create_at": "2022-03-09 11:24:18.856936", 创建时间 + "name": "test", 名称 + "account": "testsss1-put", 账号 + "phone": "12345", 手机号 + "email": null, 邮箱 + } + """ + @expose("json") def get_one(self, user_id): db_conn = request.db_conn @@ -36,12 +112,78 @@ class UserController(RestController): user.pwd = None return user + """@api {post} /v1/users create_user + @apiName CreateUser + @apiGroup User + + @apiBody {Object} user + @apiParamExample {json} Request-Example: + { + "user": { + "account": "testsss1-put", + "phone": "12345", + "email": xxx@xxxx.com, + "name": "test", + "pwd": "123456", + "roleId": 3 + } + } + + @apiSuccess {String} result + @apiSuccessExample Success-Response: + HTTP/1.1 200 OK + + @apiError Unauthorized + @apiErrorExample {json} Error-Response: + { + "faultcode": "Client", + "faultstring": "This server could not verify that you are + authorized to access the document you requested. Either you + supplied the wrong credentials (e.g., bad password), or your + browser does not understand how to supply the credentials + required.", + "debuginfo": null + } + """ + @expose("json") def post(self, user): db_conn = request.db_conn db_conn.add_user(user) return "Success" + """@api {put} /v1/users update_user + @apiName UpdateUser + @apiGroup User + + @apiBody {Object} user + @apiParamExample {json} Request-Example: + { + "user": { + "id": 6, + "enterpriseId": 1, + "enterpriseGroupId": 1, + "name": "test" + } + } + + @apiSuccess {String} result + @apiSuccessExample Success-Response: + "Success" + + @apiError Unauthorized + @apiErrorExample {json} Error-Response: + { + "faultcode": "Client", + "faultstring": "This server could not verify that you are + authorized to access the document you requested. Either you + supplied the wrong credentials (e.g., bad password), or your + browser does not understand how to supply the credentials + required.", + "debuginfo": null + } + """ + @expose("json") def put(self, user): token = request.cookies.get("token") diff --git a/examples/industrial-ai-apiserver/industrialai/db/api.py b/examples/industrial-ai-apiserver/industrialai/db/api.py index 3e45075..3a6ecd3 100644 --- a/examples/industrial-ai-apiserver/industrialai/db/api.py +++ b/examples/industrial-ai-apiserver/industrialai/db/api.py @@ -21,8 +21,9 @@ def get_engine(): return _ENGINE DB_URL = ( os.getenv("db_url") or - "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\\ - industrial-ai-apiserver\\industrialai.db?check_same_thread=False" + "sqlite:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\" + "examples\\industrial-ai-apiserver\\" + "industrialai.db?check_same_thread=False " ) _ENGINE = create_engine(DB_URL, echo=True) return _ENGINE diff --git a/pecan_swagger/__init__.py b/pecan_swagger/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pecan_swagger/decorators.py b/pecan_swagger/decorators.py deleted file mode 100644 index 5b8ef4a..0000000 --- a/pecan_swagger/decorators.py +++ /dev/null @@ -1,38 +0,0 @@ -import pecan_swagger.g as g - -""" -decorators module - -these decorators are meant to be used by developers who wish to markup -their pecan applications to produce swagger. -""" - - -def path(endpoint, name, parent=None): - """ - path decorator - - this decorator should be used on pecan controllers to instruct how - they map to routes. - - :param endpoint: the root uri for this controller. - :param name: the name of this path. - :param parent: an optional path name to indicate a parent/child - relationship between this path and another. - """ - def decorator(c): - if hasattr(c, '__swag'): - raise Exception('{} already has swag'.format(c.__name__)) - c.__swag = dict(endpoint=endpoint, name=name, parent=parent) - g.add_path(c) - return c - return decorator - - -def method(method): - def decorator(m): - if hasattr(m, '__swag'): - raise Exception('{} already has swag'.format(m.__name__)) - m.__swag = dict(method=method) - return m - return decorator diff --git a/pecan_swagger/g.py b/pecan_swagger/g.py deleted file mode 100644 index 383c2c6..0000000 --- a/pecan_swagger/g.py +++ /dev/null @@ -1,487 +0,0 @@ -import copy -import inspect - -from pecan import util as p_u -import wsme.types as wtypes -import six -import weakref - - -""" -global hierarchy module - -this module is at the heart of the swagger conversion utility. it -contains a global "hierarchy" object which will provide a single point -to assemble an application's swagger output. - -there are also several helper functions to assist in building the -hierarchy of controllers, routes, and methods. -""" - -_hierarchy = {} - -_http_methods = {'get': '', 'post': '', 'put': '', - 'patch': '', 'delete': '', - 'head': '', 'trace': ''} - -_all_wsme_types = wtypes.native_types + ( - wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType, - wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType, - wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType) - -_definitions = {} - - -def add_path(c): - """adds a named controller to the hierarchy.""" - if _hierarchy.get(c.__swag['name']): - raise Exception( - 'name {} already exists in hierarchy'.format(c.__swag['name'])) - _hierarchy[c.__swag['name']] = c - - -def build_path(swaginfo): - """return the route path for a swag metadata item.""" - if swaginfo.get('parent') is not None: - return path_join(build_path(get_swag(swaginfo.get('parent'))), - swaginfo.get('endpoint')) - return swaginfo.get('endpoint') - - -def get_controller_paths(controllers, wsme_defs): - """ - get a list of paths with methods - - returns a list of tuples (controller path, methods). - """ - def get_methods_for_generic(name): - methods = [] - generic_handlers = lc.get(name).get('generic_handlers', {}) - for method, func in generic_handlers.items(): - if method == 'DEFAULT': - methods.append('get') - else: - methods.append(method.lower()) - # TODO drill down through decorators to get true function name - truename = getrealname(func) - del lc[truename] - return methods - - def append_methods_for_specific(name, tpl): - paths.append(tpl) - del lc[name] - - lc = copy.deepcopy(controllers) - paths = [] - # TODO incorporate method decorator, removing functions marked - if lc.get('index'): - for method in get_methods_for_generic('index'): - paths.append(('', (method, {}))) - - # for REST controller - for method, path in _http_methods.items(): - if lc.get(method): - spec = wsme_defs.get(method, {}) # add wsme definitions - append_methods_for_specific(method, (path, (method, spec))) - - if lc.get('get_all'): - # add wsme definitions - spec = wsme_defs.get('get_all') # add wsme definitions - append_methods_for_specific('get_all', ('', ('get', spec))) - if lc.get('get_one'): - # add wsme definitions - spec = wsme_defs.get('get_one') # add wsme definitions - append_methods_for_specific('get_one', ('', ('get', spec))) - - if lc.get('_default'): - append_methods_for_specific('_default', ('', ('*', {}))) - if lc.get('_route'): - append_methods_for_specific('_route', ('', ('*', {}))) - if lc.get('_lookup'): - del lc['_lookup'] - - if lc.get('_custom_actions'): - for ca, ca_method in lc['_custom_actions'].items(): - spec = wsme_defs.get(ca) # add wsme definitions - for m in ca_method: - paths.append((ca, (m.lower(), spec))) - del lc['_custom_actions'] - - generic_controllers = [c for c in lc if lc[c].get('generic')] - for controller in generic_controllers: - for method in get_methods_for_generic(controller): - paths.append((controller, (method, {}))) - for controller in lc: - if controller not in [path[0] for path in paths]: - paths.append((controller, ('get', {}))) - return paths - - -def get_wsme_defs(name): - - def datatype_to_type_and_format(datatype): - tf = {} - if datatype == 'uuid' or datatype == 'ipv4address' \ - or datatype == 'ipv6address' or datatype == 'date' \ - or datatype == 'time': - tf['type'] = 'string' - tf['format'] = datatype - elif datatype == 'datetime': - tf['type'] = 'string' - tf['format'] = 'date-time' - elif datatype == 'binary' or datatype == 'bytes': - # base64 encoded characters - tf['type'] = 'string' - tf['format'] = 'byte' - elif datatype == 'array' or datatype == 'boolean' \ - or datatype == 'integer' or datatype == 'string': - # no format - tf['type'] = datatype - elif datatype == 'float': - # number - tf['type'] = 'number' - tf['format'] = datatype - elif datatype == 'unicode' or datatype == 'str' \ - or datatype == 'text': - # primitive, no format - tf['type'] = 'string' - elif datatype == 'int' or datatype == 'decimal': - # primitive, no format - tf['type'] = 'integer' - elif datatype == 'bool': - # primitive, no format - tf['type'] = 'boolean' - elif datatype == 'enum': - tf['type'] = 'enum' - elif datatype == 'unset' or datatype == 'none': - tf['type'] = None - elif datatype == 'dict': - tf['type'] = 'object' - else: - tf['type'] = 'object' - return tf - - def class_to_name_str(c): - return (('%s' % c).replace('<', '').replace('>', '') - .replace('class ', '').replace('\'', '') - .split(' ', 1)[0].rsplit('.', 1)[-1]) - - def conv_class_name_to_type_str(cn): - type_str = '' - if cn.endswith('Type'): - type_str = cn[:-4] - elif cn == 'str': - type_str = 'string' - else: - type_str = cn - type_str = type_str.lower() - return type_str - - def get_type_str(obj, src_dict=None): - type_str = '' - if hasattr(obj, '__name__'): - type_str = obj.__name__ - else: - type_str = obj.__class__.__name__ - type_str = conv_class_name_to_type_str(type_str) - - tf = datatype_to_type_and_format(type_str) - - if hasattr(obj, 'basetype') and \ - (obj.__class__ not in _all_wsme_types or type_str == 'enum'): - # UserType or Enum - tf['type'] = get_type_str(obj.basetype) - - if isinstance(src_dict, dict): - # if dict is in args, set 'type' and 'format' into the dict and - # return - src_dict.update({'type': tf['type']}) - if 'format' in tf: - src_dict.update({'format': tf['format']}) - - # get datatype options. ex.) min_length, minimum, .. - for k, v in inspect.getmembers(obj): - if ((k == 'minimum' or k == 'maxmum' - or k == 'min_length' or k == 'max_length') - and v is not None): - src_dict[to_swag_key(k)] = v - elif k == 'pattern' and v is not None: - src_dict[to_swag_key(k)] = v.pattern - # TODO(shu-mutou): this should be removed for production. - # else: - # src_dict[to_swag_key(k)] = v - - if type_str == 'enum': - # EnumType use 'set' that doesn't have sequence. - # So use 'sorted' for keeping static output. - src_dict['enum'] = sorted([v for v in obj.values]) - - # return 'type' only - return tf['type'] - - def to_swag_key(key): - keys = { - 'doc': 'description', - 'arguments': 'parameters', - 'return_type': 'schema', - 'datatype': 'type', - 'mandatory': 'required', - 'sample': 'examples', - 'readonly': 'readOnly', - 'min_length': 'minLength', - 'max_length': 'maxLength', - } - if key in keys: - return keys[key] - else: - return key - - def get_wm_item_prop(item, isparams=False): - # Add prop into 'properties' and 'required' in 'Items Object' - # 'Items Object' can be part of 'Schema Object' or 'Items Object', - # and can use recursively - prop_dict = {} - # TODO(shu-mutou): this should be removed for production. - # prop_dict['obj'] = inspect.getmembers(item) - - _item = item - if wtypes.iscomplex(item): - _item = weakref.ref(item) - - for a, i in inspect.getmembers(_item): - if a == 'datatype': - datatype = get_type_str(i, prop_dict) - if datatype == 'array': - # if array, do recursively - prop_dict['items'] = inspect_wm_schema(i.item_type) - elif datatype == 'object': - # if obuject, do recursively - prop_dict['items'] = inspect_wm_schema(i) - elif a == 'default' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'name' and isparams: - prop_dict[to_swag_key(a)] = i - elif a == 'mandatory' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'readonly' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'doc' and i is not None: - prop_dict[to_swag_key(a)] = i - - if isparams and prop_dict['type'] in ['object', 'array']: - prop_dict['schema'] = {'items': prop_dict['items'], - 'type': prop_dict['type']} - del prop_dict['type'] - del prop_dict['items'] - return prop_dict - - def get_wsattr_and_wsproperty(obj): - ws_dict = {} - for key, value in inspect.getmembers(obj): - if (key[0] != '_' and - (value.__class__.__name__ == 'wsattr' - or value.__class__.__name__ == 'wsproperty')): - ws_dict[key] = value - return ws_dict - - def inspect_wm_schema(schema_obj, isparams=False): - schema_dict = {} - # TODO(shu-mutou): this should be removed for production. - # schema_dict['obj'] = get_wsattr_and_wsproperty(schema_obj) - ws_len = len(get_wsattr_and_wsproperty(schema_obj)) - model_name = class_to_name_str(schema_obj) - - for key, obj in inspect.getmembers(schema_obj): - if (key[0] != '_' and - (obj.__class__.__name__ == 'wsattr' - or obj.__class__.__name__ == 'wsproperty')): - # TODO(shu-mutou): this should be removed for production. - # _definitions[model_name][to_swag_key(key)] = \ - # {'obj': inspect.getmembers(obj)} - - if ws_len == 1: - # single schema - schema_dict = get_wm_item_prop(obj, isparams) - else: - # multi property schema - schema_dict.update({'type': 'object'}) - # properties - if 'items' not in schema_dict: - schema_dict['items'] = {'properties': {}} - prop = {key: get_wm_item_prop(obj, isparams)} - # required as array of string - if 'required' in prop[key] and prop[key]['required'] \ - and isinstance(prop[key]['required'], bool): - if 'required' not in schema_dict: - schema_dict['required'] = [] - schema_dict['required'].append(key) - del prop[key]['required'] - schema_dict['items']['properties'].update(prop) - - if schema_obj not in _all_wsme_types: - if model_name not in _definitions: - _definitions[model_name] = schema_dict - schema_dict = {'$ref': "#/definitions/%s" % model_name} - - return schema_dict - - def get_wm_def(o): - wsme = {'description': ''} - for w, d in inspect.getmembers(o): - if w == 'arguments': - wsme[to_swag_key(w)] = [] - for arg in d: - # set each 'Parameter Object', it can include - # 'Items Object' recursively - item_dict = get_wm_item_prop(arg, True) - # TODO: MUST be set one of - # 'body|query|path|header|formData' - item_dict['in'] = 'query' - wsme[to_swag_key(w)].append(item_dict) - # 'body' should be set due to existing 'body' option - # in WSME expose - if o.body_type is not None: - # if body is set, last arg is it. - wsme[to_swag_key(w)][-1]['in'] = 'body' - elif w == 'doc' and d: - wsme[to_swag_key(w)] = d - elif w == 'return_type': - wsme['responses'] = {'status': {'description': ''}} - if d: - if d in _all_wsme_types: - # d is set single WSME type class or implicit type - wsme['responses']['status'][to_swag_key(w)] = ( - datatype_to_type_and_format( - conv_class_name_to_type_str( - class_to_name_str(d)))) - else: - # d is model class - wsme['responses']['status'][to_swag_key(w)] = ( - inspect_wm_schema(d, False)) - doc = inspect.getdoc(d) - if doc is not None: - wsme['responses']['status']['description'] = doc - elif w == 'status_code': - wsme['responses'][d] = wsme['responses']['status'] - del wsme['responses']['status'] - # TODO(shu-mutou): this should be removed for production. - # elif w == 'body_type': - # wsme[to_swag_key(w)] = get_type_str(d) - # elif w == 'extra_options' or w == 'ignore_extra_args' \ - # or w == 'name' or w == 'pass_request': - # wsme[to_swag_key(w)] = d - # else: - # wsme[to_swag_key(w)] = d - return wsme - - c = _hierarchy[name] - wsme_defs = {} - for k, v in inspect.getmembers(c): - if p_u.iscontroller(v): - for m, o in inspect.getmembers(v): - if m == "_wsme_definition": - wsme_defs[k] = get_wm_def(o) - - # TODO(shu-mutou): this should be removed for production. - # output wsme info into files by each controller for dev - # import pprint - # with open(name + '.txt', 'w') as fout: - # pprint.pprint(wsme_defs, stream=fout, indent=2) - - return wsme_defs - - -def get_controllers(name): - """ - get all the controllers associated with a path - - returns a dictionary of controllers indexed by their names. - """ - c = _hierarchy[name] - controllers = {k: p_u._cfg(v) - for k, v in c.__dict__.items() if p_u.iscontroller(v)} - cacts = {k: v for k, v in c.__dict__.items() if k == '_custom_actions'} - if len(cacts): - controllers.update(cacts) - return controllers - - -def get_paths(): - """ - return all the registered paths - - loops through the hierarchy and retuns a list of tuples containing the - paths and their methods. - - :returns: [(path, methods), ...] - """ - pathlist = [] - for name in _hierarchy: - fullpath = build_path(get_swag(name)) - controllers = get_controllers(name) - wsme_defs = get_wsme_defs(name) - paths = get_controller_paths(controllers, wsme_defs) - for path in paths: - ptuple = (path_join(fullpath, path[0]), path[1]) - pathlist.append(ptuple) - return pathlist - - -def get_swag(name): - """return the swag metadata from an named controller.""" - return _hierarchy.get(name).__swag - - -def getrealname(method): - """attempt to get a method's real name.""" - argspec = inspect.getargspec(method) - args = argspec[0] - if args and args[0] == 'self': - return method.__name__ - if hasattr(method, '__func__'): - method = method.__func__ - - func_closure = six.get_function_closure(method) - - # NOTE(sileht): if the closure is None we cannot look deeper, - # so return actual argspec, this occurs when the method - # is static for example. - if func_closure is None: - return method.__name__ - - closure = next( - ( - c for c in func_closure if six.callable(c.cell_contents) - ), - None - ) - method = closure.cell_contents - return getrealname(method) - - -def methods_get(name): - """get all the methods for a named controller.""" - c = _hierarchy[name] - mlist = [] - if hasattr(c, 'index') and p_u.iscontroller(c.index): - cfg = p_u._cfg(c.index) - if cfg.get('generic_handlers').get('DEFAULT'): - mlist.append('get') - mlist += cfg.get('allowed_methods') - for i in c.__dict__: - ii = getattr(c, i) - if hasattr(ii, '__swag'): - m = ii.__swag.get('method') - if m is not None: - mlist.append(m) - return mlist - - -def path_join(part1, part2): - """join two url paths.""" - if len(part2) == 0: - return part1 - sep = '/' - if part1[-1] == sep: - sep = '' - return part1 + sep + part2 diff --git a/pecan_swagger/utils.py b/pecan_swagger/utils.py deleted file mode 100644 index aa3d3b6..0000000 --- a/pecan_swagger/utils.py +++ /dev/null @@ -1,64 +0,0 @@ -from pecan_swagger import g as g - - -""" -utility function module - -this module contains the utility functions to assemble the swagger -dictionary object. they can be consumed by end-user applications to -build up swagger objects for applications. - -functions: -swagger_build -- build a full swagger dictionary -""" - - -def swagger_build(title, version): - swag = dict() - swag['swagger'] = '2.0' - swag['info'] = dict(title=title, version=version) - swag['consumes'] = [] - swag['produces'] = [] - swag['paths'] = {} - for p in g.get_paths(): - if p[0] not in swag['paths']: - swag['paths'][p[0]] = _tuple_to_dict(p[1]) - elif len(p[1]) > 0: - swag['paths'][p[0]].update(_tuple_to_dict(p[1])) - swag['definitions'] = g._definitions - return swag - - -def _tuple_to_dict(tpl): - """Convert tuple to dictionary - - each tuple must have key and value. - This function arrows taple including lists. - - ex.) acceptable taple - OK: ('/', ('get',(('desc',{}),('res',{}),('params',['id','name'])))) - NG: ('/', ('get',('desc','res',('params',['id','name'])))) - """ - if isinstance(tpl, list): - d = [] - for e in tpl: - d.append(_tuple_to_dict(e)) - elif isinstance(tpl, tuple): - d = {} - if isinstance(tpl[0], tuple): - # tuple has some child tuple - for e in tpl: - d[e[0]] = _tuple_to_dict(e[1]) - elif isinstance(tpl[0], list): - # list member should be processed recursively - d = _tuple_to_dict(tpl[0]) - else: - if len(tpl) == 2: - # single tuple node - d[tpl[0]] = _tuple_to_dict(tpl[1]) - else: - raise Exception(tpl) - else: - # value or dict - d = tpl - return d -- Gitee From 115479a513b7b3d44c7beca73c494975278a8f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=80=9A=E9=97=BB?= Date: Tue, 24 May 2022 14:59:15 +0800 Subject: [PATCH 3/3] fixed: 1. yaml output added 2. functions edited --- .DS_Store | Bin 0 -> 6148 bytes .idea/.gitignore | 0 .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/pecan-swagger.iml | 8 + .../shelved.patch | 1136 +++++++++++++++++ ...Checkout_at_2022_5_24__14_46__Changes_.xml | 4 + .idea/vcs.xml | 6 + .idea/workspace.xml | 141 ++ API Reference.md | 142 +++ examples/.DS_Store | Bin 0 -> 8196 bytes examples/industrial-ai-apiserver/myapp-doc.py | 24 + .../pecan_swagger/__init__.py | 0 .../pecan_swagger/decorators.py | 161 +++ .../pecan_swagger/g.py | 617 +++++++++ .../pecan_swagger/utils.py | 108 ++ pecan_swagger/.DS_Store | Bin 0 -> 6148 bytes 18 files changed, 2365 insertions(+) create mode 100644 .DS_Store create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/pecan-swagger.iml create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24,_14_46_[Changes]/shelved.patch create mode 100644 .idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24__14_46__Changes_.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 API Reference.md create mode 100644 examples/.DS_Store create mode 100755 examples/industrial-ai-apiserver/myapp-doc.py create mode 100755 examples/industrial-ai-apiserver/pecan_swagger/__init__.py create mode 100755 examples/industrial-ai-apiserver/pecan_swagger/decorators.py create mode 100755 examples/industrial-ai-apiserver/pecan_swagger/g.py create mode 100755 examples/industrial-ai-apiserver/pecan_swagger/utils.py create mode 100644 pecan_swagger/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1b097e279931e088c564c80dd5de395d8411f9d2 GIT binary patch literal 6148 zcmeHKQA@)x5WcwST8FR)g*^s*9XPkCh%aT%KVU^4RBG#r7Hc!s%?e}CXZ=I|5`T|( zNd{_F1VKe}8p)wP{{?<&4d#GN9N;khGaN#hLe(J9n*;|`Np zBo`Q?yd|tRgmbJs0S{8h(7V7Vgdv?^KgmFQz1`sLjm{St_@Kg9T+Dbg_ + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2d85f80 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c5b50f1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pecan-swagger.iml b/.idea/pecan-swagger.iml new file mode 100644 index 0000000..c03885c --- /dev/null +++ b/.idea/pecan-swagger.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24,_14_46_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24,_14_46_[Changes]/shelved.patch new file mode 100644 index 0000000..fdc7d9e --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24,_14_46_[Changes]/shelved.patch @@ -0,0 +1,1136 @@ +Index: pecan_swagger/utils.py +=================================================================== +diff --git a/pecan_swagger/utils.py b/pecan_swagger/utils.py +deleted file mode 100644 +--- a/pecan_swagger/utils.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ /dev/null (revision 2159b53baa4c86d9c1b739260824b7f89f935759) +@@ -1,64 +0,0 @@ +-from pecan_swagger import g as g +- +- +-""" +-utility function module +- +-this module contains the utility functions to assemble the swagger +-dictionary object. they can be consumed by end-user applications to +-build up swagger objects for applications. +- +-functions: +-swagger_build -- build a full swagger dictionary +-""" +- +- +-def swagger_build(title, version): +- swag = dict() +- swag['swagger'] = '2.0' +- swag['info'] = dict(title=title, version=version) +- swag['consumes'] = [] +- swag['produces'] = [] +- swag['paths'] = {} +- for p in g.get_paths(): +- if p[0] not in swag['paths']: +- swag['paths'][p[0]] = _tuple_to_dict(p[1]) +- elif len(p[1]) > 0: +- swag['paths'][p[0]].update(_tuple_to_dict(p[1])) +- swag['definitions'] = g._definitions +- return swag +- +- +-def _tuple_to_dict(tpl): +- """Convert tuple to dictionary +- +- each tuple must have key and value. +- This function arrows taple including lists. +- +- ex.) acceptable taple +- OK: ('/', ('get',(('desc',{}),('res',{}),('params',['id','name'])))) +- NG: ('/', ('get',('desc','res',('params',['id','name'])))) +- """ +- if isinstance(tpl, list): +- d = [] +- for e in tpl: +- d.append(_tuple_to_dict(e)) +- elif isinstance(tpl, tuple): +- d = {} +- if isinstance(tpl[0], tuple): +- # tuple has some child tuple +- for e in tpl: +- d[e[0]] = _tuple_to_dict(e[1]) +- elif isinstance(tpl[0], list): +- # list member should be processed recursively +- d = _tuple_to_dict(tpl[0]) +- else: +- if len(tpl) == 2: +- # single tuple node +- d[tpl[0]] = _tuple_to_dict(tpl[1]) +- else: +- raise Exception(tpl) +- else: +- # value or dict +- d = tpl +- return d +Index: pecan_swagger/decorators.py +=================================================================== +diff --git a/pecan_swagger/decorators.py b/pecan_swagger/decorators.py +deleted file mode 100644 +--- a/pecan_swagger/decorators.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ /dev/null (revision 2159b53baa4c86d9c1b739260824b7f89f935759) +@@ -1,38 +0,0 @@ +-import pecan_swagger.g as g +- +-""" +-decorators module +- +-these decorators are meant to be used by developers who wish to markup +-their pecan applications to produce swagger. +-""" +- +- +-def path(endpoint, name, parent=None): +- """ +- path decorator +- +- this decorator should be used on pecan controllers to instruct how +- they map to routes. +- +- :param endpoint: the root uri for this controller. +- :param name: the name of this path. +- :param parent: an optional path name to indicate a parent/child +- relationship between this path and another. +- """ +- def decorator(c): +- if hasattr(c, '__swag'): +- raise Exception('{} already has swag'.format(c.__name__)) +- c.__swag = dict(endpoint=endpoint, name=name, parent=parent) +- g.add_path(c) +- return c +- return decorator +- +- +-def method(method): +- def decorator(m): +- if hasattr(m, '__swag'): +- raise Exception('{} already has swag'.format(m.__name__)) +- m.__swag = dict(method=method) +- return m +- return decorator +Index: pecan_swagger/g.py +=================================================================== +diff --git a/pecan_swagger/g.py b/pecan_swagger/g.py +deleted file mode 100644 +--- a/pecan_swagger/g.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ /dev/null (revision 2159b53baa4c86d9c1b739260824b7f89f935759) +@@ -1,487 +0,0 @@ +-import copy +-import inspect +- +-from pecan import util as p_u +-import wsme.types as wtypes +-import six +-import weakref +- +- +-""" +-global hierarchy module +- +-this module is at the heart of the swagger conversion utility. it +-contains a global "hierarchy" object which will provide a single point +-to assemble an application's swagger output. +- +-there are also several helper functions to assist in building the +-hierarchy of controllers, routes, and methods. +-""" +- +-_hierarchy = {} +- +-_http_methods = {'get': '', 'post': '', 'put': '', +- 'patch': '', 'delete': '', +- 'head': '', 'trace': ''} +- +-_all_wsme_types = wtypes.native_types + ( +- wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType, +- wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType, +- wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType) +- +-_definitions = {} +- +- +-def add_path(c): +- """adds a named controller to the hierarchy.""" +- if _hierarchy.get(c.__swag['name']): +- raise Exception( +- 'name {} already exists in hierarchy'.format(c.__swag['name'])) +- _hierarchy[c.__swag['name']] = c +- +- +-def build_path(swaginfo): +- """return the route path for a swag metadata item.""" +- if swaginfo.get('parent') is not None: +- return path_join(build_path(get_swag(swaginfo.get('parent'))), +- swaginfo.get('endpoint')) +- return swaginfo.get('endpoint') +- +- +-def get_controller_paths(controllers, wsme_defs): +- """ +- get a list of paths with methods +- +- returns a list of tuples (controller path, methods). +- """ +- def get_methods_for_generic(name): +- methods = [] +- generic_handlers = lc.get(name).get('generic_handlers', {}) +- for method, func in generic_handlers.items(): +- if method == 'DEFAULT': +- methods.append('get') +- else: +- methods.append(method.lower()) +- # TODO drill down through decorators to get true function name +- truename = getrealname(func) +- del lc[truename] +- return methods +- +- def append_methods_for_specific(name, tpl): +- paths.append(tpl) +- del lc[name] +- +- lc = copy.deepcopy(controllers) +- paths = [] +- # TODO incorporate method decorator, removing functions marked +- if lc.get('index'): +- for method in get_methods_for_generic('index'): +- paths.append(('', (method, {}))) +- +- # for REST controller +- for method, path in _http_methods.items(): +- if lc.get(method): +- spec = wsme_defs.get(method, {}) # add wsme definitions +- append_methods_for_specific(method, (path, (method, spec))) +- +- if lc.get('get_all'): +- # add wsme definitions +- spec = wsme_defs.get('get_all') # add wsme definitions +- append_methods_for_specific('get_all', ('', ('get', spec))) +- if lc.get('get_one'): +- # add wsme definitions +- spec = wsme_defs.get('get_one') # add wsme definitions +- append_methods_for_specific('get_one', ('', ('get', spec))) +- +- if lc.get('_default'): +- append_methods_for_specific('_default', ('', ('*', {}))) +- if lc.get('_route'): +- append_methods_for_specific('_route', ('', ('*', {}))) +- if lc.get('_lookup'): +- del lc['_lookup'] +- +- if lc.get('_custom_actions'): +- for ca, ca_method in lc['_custom_actions'].items(): +- spec = wsme_defs.get(ca) # add wsme definitions +- for m in ca_method: +- paths.append((ca, (m.lower(), spec))) +- del lc['_custom_actions'] +- +- generic_controllers = [c for c in lc if lc[c].get('generic')] +- for controller in generic_controllers: +- for method in get_methods_for_generic(controller): +- paths.append((controller, (method, {}))) +- for controller in lc: +- if controller not in [path[0] for path in paths]: +- paths.append((controller, ('get', {}))) +- return paths +- +- +-def get_wsme_defs(name): +- +- def datatype_to_type_and_format(datatype): +- tf = {} +- if datatype == 'uuid' or datatype == 'ipv4address' \ +- or datatype == 'ipv6address' or datatype == 'date' \ +- or datatype == 'time': +- tf['type'] = 'string' +- tf['format'] = datatype +- elif datatype == 'datetime': +- tf['type'] = 'string' +- tf['format'] = 'date-time' +- elif datatype == 'binary' or datatype == 'bytes': +- # base64 encoded characters +- tf['type'] = 'string' +- tf['format'] = 'byte' +- elif datatype == 'array' or datatype == 'boolean' \ +- or datatype == 'integer' or datatype == 'string': +- # no format +- tf['type'] = datatype +- elif datatype == 'float': +- # number +- tf['type'] = 'number' +- tf['format'] = datatype +- elif datatype == 'unicode' or datatype == 'str' \ +- or datatype == 'text': +- # primitive, no format +- tf['type'] = 'string' +- elif datatype == 'int' or datatype == 'decimal': +- # primitive, no format +- tf['type'] = 'integer' +- elif datatype == 'bool': +- # primitive, no format +- tf['type'] = 'boolean' +- elif datatype == 'enum': +- tf['type'] = 'enum' +- elif datatype == 'unset' or datatype == 'none': +- tf['type'] = None +- elif datatype == 'dict': +- tf['type'] = 'object' +- else: +- tf['type'] = 'object' +- return tf +- +- def class_to_name_str(c): +- return (('%s' % c).replace('<', '').replace('>', '') +- .replace('class ', '').replace('\'', '') +- .split(' ', 1)[0].rsplit('.', 1)[-1]) +- +- def conv_class_name_to_type_str(cn): +- type_str = '' +- if cn.endswith('Type'): +- type_str = cn[:-4] +- elif cn == 'str': +- type_str = 'string' +- else: +- type_str = cn +- type_str = type_str.lower() +- return type_str +- +- def get_type_str(obj, src_dict=None): +- type_str = '' +- if hasattr(obj, '__name__'): +- type_str = obj.__name__ +- else: +- type_str = obj.__class__.__name__ +- type_str = conv_class_name_to_type_str(type_str) +- +- tf = datatype_to_type_and_format(type_str) +- +- if hasattr(obj, 'basetype') and \ +- (obj.__class__ not in _all_wsme_types or type_str == 'enum'): +- # UserType or Enum +- tf['type'] = get_type_str(obj.basetype) +- +- if isinstance(src_dict, dict): +- # if dict is in args, set 'type' and 'format' into the dict and +- # return +- src_dict.update({'type': tf['type']}) +- if 'format' in tf: +- src_dict.update({'format': tf['format']}) +- +- # get datatype options. ex.) min_length, minimum, .. +- for k, v in inspect.getmembers(obj): +- if ((k == 'minimum' or k == 'maxmum' +- or k == 'min_length' or k == 'max_length') +- and v is not None): +- src_dict[to_swag_key(k)] = v +- elif k == 'pattern' and v is not None: +- src_dict[to_swag_key(k)] = v.pattern +- # TODO(shu-mutou): this should be removed for production. +- # else: +- # src_dict[to_swag_key(k)] = v +- +- if type_str == 'enum': +- # EnumType use 'set' that doesn't have sequence. +- # So use 'sorted' for keeping static output. +- src_dict['enum'] = sorted([v for v in obj.values]) +- +- # return 'type' only +- return tf['type'] +- +- def to_swag_key(key): +- keys = { +- 'doc': 'description', +- 'arguments': 'parameters', +- 'return_type': 'schema', +- 'datatype': 'type', +- 'mandatory': 'required', +- 'sample': 'examples', +- 'readonly': 'readOnly', +- 'min_length': 'minLength', +- 'max_length': 'maxLength', +- } +- if key in keys: +- return keys[key] +- else: +- return key +- +- def get_wm_item_prop(item, isparams=False): +- # Add prop into 'properties' and 'required' in 'Items Object' +- # 'Items Object' can be part of 'Schema Object' or 'Items Object', +- # and can use recursively +- prop_dict = {} +- # TODO(shu-mutou): this should be removed for production. +- # prop_dict['obj'] = inspect.getmembers(item) +- +- _item = item +- if wtypes.iscomplex(item): +- _item = weakref.ref(item) +- +- for a, i in inspect.getmembers(_item): +- if a == 'datatype': +- datatype = get_type_str(i, prop_dict) +- if datatype == 'array': +- # if array, do recursively +- prop_dict['items'] = inspect_wm_schema(i.item_type) +- elif datatype == 'object': +- # if obuject, do recursively +- prop_dict['items'] = inspect_wm_schema(i) +- elif a == 'default' and i: +- prop_dict[to_swag_key(a)] = i +- elif a == 'name' and isparams: +- prop_dict[to_swag_key(a)] = i +- elif a == 'mandatory' and i: +- prop_dict[to_swag_key(a)] = i +- elif a == 'readonly' and i: +- prop_dict[to_swag_key(a)] = i +- elif a == 'doc' and i is not None: +- prop_dict[to_swag_key(a)] = i +- +- if isparams and prop_dict['type'] in ['object', 'array']: +- prop_dict['schema'] = {'items': prop_dict['items'], +- 'type': prop_dict['type']} +- del prop_dict['type'] +- del prop_dict['items'] +- return prop_dict +- +- def get_wsattr_and_wsproperty(obj): +- ws_dict = {} +- for key, value in inspect.getmembers(obj): +- if (key[0] != '_' and +- (value.__class__.__name__ == 'wsattr' +- or value.__class__.__name__ == 'wsproperty')): +- ws_dict[key] = value +- return ws_dict +- +- def inspect_wm_schema(schema_obj, isparams=False): +- schema_dict = {} +- # TODO(shu-mutou): this should be removed for production. +- # schema_dict['obj'] = get_wsattr_and_wsproperty(schema_obj) +- ws_len = len(get_wsattr_and_wsproperty(schema_obj)) +- model_name = class_to_name_str(schema_obj) +- +- for key, obj in inspect.getmembers(schema_obj): +- if (key[0] != '_' and +- (obj.__class__.__name__ == 'wsattr' +- or obj.__class__.__name__ == 'wsproperty')): +- # TODO(shu-mutou): this should be removed for production. +- # _definitions[model_name][to_swag_key(key)] = \ +- # {'obj': inspect.getmembers(obj)} +- +- if ws_len == 1: +- # single schema +- schema_dict = get_wm_item_prop(obj, isparams) +- else: +- # multi property schema +- schema_dict.update({'type': 'object'}) +- # properties +- if 'items' not in schema_dict: +- schema_dict['items'] = {'properties': {}} +- prop = {key: get_wm_item_prop(obj, isparams)} +- # required as array of string +- if 'required' in prop[key] and prop[key]['required'] \ +- and isinstance(prop[key]['required'], bool): +- if 'required' not in schema_dict: +- schema_dict['required'] = [] +- schema_dict['required'].append(key) +- del prop[key]['required'] +- schema_dict['items']['properties'].update(prop) +- +- if schema_obj not in _all_wsme_types: +- if model_name not in _definitions: +- _definitions[model_name] = schema_dict +- schema_dict = {'$ref': "#/definitions/%s" % model_name} +- +- return schema_dict +- +- def get_wm_def(o): +- wsme = {'description': ''} +- for w, d in inspect.getmembers(o): +- if w == 'arguments': +- wsme[to_swag_key(w)] = [] +- for arg in d: +- # set each 'Parameter Object', it can include +- # 'Items Object' recursively +- item_dict = get_wm_item_prop(arg, True) +- # TODO: MUST be set one of +- # 'body|query|path|header|formData' +- item_dict['in'] = 'query' +- wsme[to_swag_key(w)].append(item_dict) +- # 'body' should be set due to existing 'body' option +- # in WSME expose +- if o.body_type is not None: +- # if body is set, last arg is it. +- wsme[to_swag_key(w)][-1]['in'] = 'body' +- elif w == 'doc' and d: +- wsme[to_swag_key(w)] = d +- elif w == 'return_type': +- wsme['responses'] = {'status': {'description': ''}} +- if d: +- if d in _all_wsme_types: +- # d is set single WSME type class or implicit type +- wsme['responses']['status'][to_swag_key(w)] = ( +- datatype_to_type_and_format( +- conv_class_name_to_type_str( +- class_to_name_str(d)))) +- else: +- # d is model class +- wsme['responses']['status'][to_swag_key(w)] = ( +- inspect_wm_schema(d, False)) +- doc = inspect.getdoc(d) +- if doc is not None: +- wsme['responses']['status']['description'] = doc +- elif w == 'status_code': +- wsme['responses'][d] = wsme['responses']['status'] +- del wsme['responses']['status'] +- # TODO(shu-mutou): this should be removed for production. +- # elif w == 'body_type': +- # wsme[to_swag_key(w)] = get_type_str(d) +- # elif w == 'extra_options' or w == 'ignore_extra_args' \ +- # or w == 'name' or w == 'pass_request': +- # wsme[to_swag_key(w)] = d +- # else: +- # wsme[to_swag_key(w)] = d +- return wsme +- +- c = _hierarchy[name] +- wsme_defs = {} +- for k, v in inspect.getmembers(c): +- if p_u.iscontroller(v): +- for m, o in inspect.getmembers(v): +- if m == "_wsme_definition": +- wsme_defs[k] = get_wm_def(o) +- +- # TODO(shu-mutou): this should be removed for production. +- # output wsme info into files by each controller for dev +- # import pprint +- # with open(name + '.txt', 'w') as fout: +- # pprint.pprint(wsme_defs, stream=fout, indent=2) +- +- return wsme_defs +- +- +-def get_controllers(name): +- """ +- get all the controllers associated with a path +- +- returns a dictionary of controllers indexed by their names. +- """ +- c = _hierarchy[name] +- controllers = {k: p_u._cfg(v) +- for k, v in c.__dict__.items() if p_u.iscontroller(v)} +- cacts = {k: v for k, v in c.__dict__.items() if k == '_custom_actions'} +- if len(cacts): +- controllers.update(cacts) +- return controllers +- +- +-def get_paths(): +- """ +- return all the registered paths +- +- loops through the hierarchy and retuns a list of tuples containing the +- paths and their methods. +- +- :returns: [(path, methods), ...] +- """ +- pathlist = [] +- for name in _hierarchy: +- fullpath = build_path(get_swag(name)) +- controllers = get_controllers(name) +- wsme_defs = get_wsme_defs(name) +- paths = get_controller_paths(controllers, wsme_defs) +- for path in paths: +- ptuple = (path_join(fullpath, path[0]), path[1]) +- pathlist.append(ptuple) +- return pathlist +- +- +-def get_swag(name): +- """return the swag metadata from an named controller.""" +- return _hierarchy.get(name).__swag +- +- +-def getrealname(method): +- """attempt to get a method's real name.""" +- argspec = inspect.getargspec(method) +- args = argspec[0] +- if args and args[0] == 'self': +- return method.__name__ +- if hasattr(method, '__func__'): +- method = method.__func__ +- +- func_closure = six.get_function_closure(method) +- +- # NOTE(sileht): if the closure is None we cannot look deeper, +- # so return actual argspec, this occurs when the method +- # is static for example. +- if func_closure is None: +- return method.__name__ +- +- closure = next( +- ( +- c for c in func_closure if six.callable(c.cell_contents) +- ), +- None +- ) +- method = closure.cell_contents +- return getrealname(method) +- +- +-def methods_get(name): +- """get all the methods for a named controller.""" +- c = _hierarchy[name] +- mlist = [] +- if hasattr(c, 'index') and p_u.iscontroller(c.index): +- cfg = p_u._cfg(c.index) +- if cfg.get('generic_handlers').get('DEFAULT'): +- mlist.append('get') +- mlist += cfg.get('allowed_methods') +- for i in c.__dict__: +- ii = getattr(c, i) +- if hasattr(ii, '__swag'): +- m = ii.__swag.get('method') +- if m is not None: +- mlist.append(m) +- return mlist +- +- +-def path_join(part1, part2): +- """join two url paths.""" +- if len(part2) == 0: +- return part1 +- sep = '/' +- if part1[-1] == sep: +- sep = '' +- return part1 + sep + part2 +Index: examples/industrial-ai-apiserver/README.md +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+># industrial-ai-apiserver\n\n数据字典 & API:\n\n## 本地调试\n\n```bash\ncd industrial-ai-apiserver\n\n# 创建 python 虚拟环境\npython -m virtualenv .venv\n\n# 进入 python 虚拟环境\n.venv\\Scripts\\activate \n\n# 安装依赖\npip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt\n\n# 清理原数据库\nrm -rf /tmp/industrialai.db\n\n# 初始化数据库\npython datainit.py\n\n# 启动 web 服务\npython run.py\n# python run.py 0.0.0.0 8080\n\n# 尝试访问\ncurl http://localhost:8080/users/\n```\n\n## 测试\n\n```bash\npython setup.py test -q\n\nstestr run\n\ntox\n```\n\n## 容器镜像制作和部署\n\n```bash\ndocker build -t industrialai .\n\ndocker stop industrialai; docker rm industrialai\n\ndocker run -d --name industrialai -p 8888:8080 industrialai\n\ncurl http://localhost:8888/users/\n``` +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/examples/industrial-ai-apiserver/README.md b/examples/industrial-ai-apiserver/README.md +--- a/examples/industrial-ai-apiserver/README.md (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ b/examples/industrial-ai-apiserver/README.md (date 1651652456000) +@@ -17,7 +17,7 @@ + pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt + + # 清理原数据库 +-rm -rf /tmp/industrialai.db ++#rm -rf /tmp/industrialai.db + + # 初始化数据库 + python datainit.py +@@ -27,7 +27,7 @@ + # python run.py 0.0.0.0 8080 + + # 尝试访问 +-curl http://localhost:8080/users/ ++curl http://localhost:8080/ + ``` + + ## 测试 +@@ -40,14 +40,22 @@ + tox + ``` + +-## 容器镜像制作和部署 ++## API-doc ++ ++### 安装 api-doc ++1. 安装npm: ++ - 前往 https://nodejs.org/en/ 下载node.js(集成npm)的最新版本 ++ - 在终端输入 npm -v ,输出npm版本号 ++2. npm install apidoc –g ++3. 参考: ++ - https://segmentfault.com/a/1190000011636199 ++ - https://apidocjs.com/#getting-started ++ - https://github.com/apidoc/apidoc ++ ++### 生成接口文档 + + ```bash +-docker build -t industrialai . +- +-docker stop industrialai; docker rm industrialai ++apidoc -i industrialai/api/controllers -o api-doc ++``` + +-docker run -d --name industrialai -p 8888:8080 industrialai +- +-curl http://localhost:8888/users/ +-``` +\ No newline at end of file ++点击 api-doc/index.html 访问 +\ No newline at end of file +Index: examples/industrial-ai-apiserver/.gitignore +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+># Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# Build\nE:\\\\dhuProject\\\\pecan-swagger\\\\examples\\\\industrial-ai-apiserver\\\\industrialai.db\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\ncloudkitty.egg-info\n.idea/\n.python-version\n\n# Configuration file\netc/cloudkitty/cloudkitty.conf.sample\netc/cloudkitty/policy.yaml.sample\n\n# vscode\n.vscode\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.stestr/\n.coverage\n.cache\ncoverage.xml\ncover\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndoc/build/\n\n# Rope\n.ropeproject/\n\n# Others\n*.sqlite\n*.swp\n*~\n*.swn\n*.swo\n*.DS_Store\n\n# tox -e docs\nAUTHORS\nChangeLog\n\nreleasenotes/build\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/examples/industrial-ai-apiserver/.gitignore b/examples/industrial-ai-apiserver/.gitignore +--- a/examples/industrial-ai-apiserver/.gitignore (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ b/examples/industrial-ai-apiserver/.gitignore (date 1651652456000) +@@ -2,9 +2,6 @@ + __pycache__/ + *.py[cod] + +-# Build +-E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db +- + # C extensions + *.so + +Index: examples/industrial-ai-apiserver/datainit.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from industrialai.db.model import Base, Role, User\nfrom sqlalchemy.orm import sessionmaker\nimport os\nfrom sqlalchemy import create_engine\n\ndef init_db():\n DB_URL = os.getenv('db_url') or \\\n \"sqlite:///E:\\\\dhuProject\\\\pecan-swagger\\\\examples\\\\industrial-ai-apiserver\\\\industrialai.db?check_same_thread=False\"\n engine = create_engine(DB_URL, echo=True)\n Base.metadata.drop_all(engine)\n Base.metadata.create_all(engine)\n Session = sessionmaker(bind=engine)\n session = Session()\n\n session.add_all([\n Role(title=\"expert\"),\n Role(title=\"government\"),\n Role(title=\"enterpriseAdmin\"),\n Role(title=\"member\"),\n Role(title=\"admin\"),\n User(account=\"admin\", pwd=\"123456\", roleId=3),\n User(account=\"member\", pwd=\"123456\", roleId=4),\n User(account=\"root\", pwd=\"123456\", roleId=5)\n ])\n session.commit()\n\n\ninit_db()\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/examples/industrial-ai-apiserver/datainit.py b/examples/industrial-ai-apiserver/datainit.py +--- a/examples/industrial-ai-apiserver/datainit.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ b/examples/industrial-ai-apiserver/datainit.py (date 1651652456000) +@@ -5,7 +5,7 @@ + + def init_db(): + DB_URL = os.getenv('db_url') or \ +- "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False" ++ "sqlite:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False" + engine = create_engine(DB_URL, echo=True) + Base.metadata.drop_all(engine) + Base.metadata.create_all(engine) +Index: examples/industrial-ai-apiserver/industrialai/db/api.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from industrialai.db import model as db_model\nimport logging\nimport math\nimport os\nfrom pecan import abort\nfrom sqlalchemy import create_engine, func\nfrom sqlalchemy.orm import exc\nfrom sqlalchemy.orm import sessionmaker\n\nlogger = logging.getLogger(__name__)\nDomain = \"sqlalchemy\"\n\n\n_ENGINE = None\n_SESSION_MAKER = None\n\n\ndef get_engine():\n global _ENGINE\n if _ENGINE is not None:\n return _ENGINE\n DB_URL = (\n os.getenv(\"db_url\") or\n \"sqlite:///E:\\\\dhuProject\\\\pecan-swagger\\\\examples\\\\\\\n industrial-ai-apiserver\\\\industrialai.db?check_same_thread=False\"\n )\n _ENGINE = create_engine(DB_URL, echo=True)\n return _ENGINE\n\n\ndef get_session_maker(engine):\n global _SESSION_MAKER\n if _SESSION_MAKER is not None:\n return _SESSION_MAKER\n _SESSION_MAKER = sessionmaker(bind=engine)\n return _SESSION_MAKER\n\n\ndef get_session():\n engine = get_engine()\n maker = get_session_maker(engine)\n session = maker()\n return session\n\n\nclass Connection(object):\n def __init__(self):\n pass\n\n def get_user(self, user_id):\n user = None\n query = get_session().query(db_model.User).filter_by(id=user_id)\n try:\n user = query.one()\n except exc.NoResultFound:\n logger.error(\"query by enterpriseId not found ...\")\n abort(404)\n return user\n\n def list_users(self, page_num, page_size, **kwargs):\n users = []\n # query = get_session().query(db_model.User.account,\n # db_model.User.name, db_model.User.roleId,\n # db_model.User.status, db_model.User.create_at, db_model.User.source)\n query = get_session().query(db_model.User)\n for k, v in kwargs.items():\n if v:\n if k == \"name\":\n query = query.filter(\n db_model.User.name.like(\"%{keyword}%\".\n format(keyword=v))\n )\n elif k == \"account\":\n query = query.filter(\n db_model.User.account.like(\"%{keyword}%\".\n format(keyword=v))\n )\n else:\n query = query.filter_by(**{k: v})\n try:\n users = query.slice((page_num-1)*page_size, page_size).all()\n num = query.count()\n pages = math.ceil(num/page_size)\n for item in users:\n item.pwd = None\n except exc.NoResultFound:\n logger.error(\"query all user occur error ...\")\n abort(404)\n return users, pages\n\n def update_user(self, user):\n id = user.get(\"id\")\n logger.info(\"user.id: %s\" % (id))\n try:\n session = get_session()\n session.query(db_model.User).filter_by(id=id).one()\n for k, v in user.items():\n if v is not None:\n session.query(db_model.User).filter_by(id=id). \\\n update({k: v})\n session.flush()\n session.commit()\n except exc.NoResultFound:\n logger.error(\"update user occur error ...\")\n abort(404)\n\n return user\n\n def delete_user(self, user_id):\n logger.info(\"user.id: %s\" % (user_id))\n try:\n session = get_session()\n user = session.query(db_model.User).filter_by(id=user_id).first()\n session.delete(user)\n session.flush()\n session.commit()\n except exc.NoResultFound:\n logger.error(\"delete user occur error ...\")\n abort(404)\n\n def add_user(self, user):\n db_user = db_model.User(\n name=user.get(\"name\"),\n account=user.get(\"account\"),\n pwd=user.get(\"pwd\"),\n phone=user.get(\"phone\"),\n roleId=user.get(\"roleId\"),\n enterpriseId=user.get(\"enterpriseId\"),\n status=user.get(\"status\"),\n )\n try:\n session = get_session()\n if not user.get(\"groupId\") is None:\n group = (\n session.query(db_model.Group)\n .filter_by(id=user.get(\"groupId\"))\n .one()\n )\n db_user.group = group\n session.add(db_user)\n session.flush()\n session.commit()\n except exc.NoResultFound:\n logger.error(\"add user occour error ...\")\n abort(404)\n # return session.query(db_model.Group).options(\n # joinedload(db_model.Group.users)).all()\n\n def list_roles(self, page_num, page_size):\n roles = []\n query = get_session().query(db_model.Role)\n try:\n roles = query.slice((page_num-1)*page_size, page_size).all()\n num = get_session().query(func.count(db_model.Role.id)).scalar()\n pages = math.ceil(num/page_size)\n except exc.NoResultFound:\n logger.error(\"query all roles occur error ...\")\n abort(404)\n return roles, pages\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/examples/industrial-ai-apiserver/industrialai/db/api.py b/examples/industrial-ai-apiserver/industrialai/db/api.py +--- a/examples/industrial-ai-apiserver/industrialai/db/api.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ b/examples/industrial-ai-apiserver/industrialai/db/api.py (date 1651652456000) +@@ -21,8 +21,9 @@ + return _ENGINE + DB_URL = ( + os.getenv("db_url") or +- "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\\ +- industrial-ai-apiserver\\industrialai.db?check_same_thread=False" ++ "sqlite:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\" ++ "examples\\industrial-ai-apiserver\\" ++ "industrialai.db?check_same_thread=False " + ) + _ENGINE = create_engine(DB_URL, echo=True) + return _ENGINE +Index: examples/industrial-ai-apiserver/industrialai/api/controllers/root.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from industrialai.api.controllers.v1 import controller as v1_controller\nfrom industrialai.api.expose import expose as wsexpose\nfrom industrialai.db import api\nfrom industrialai.db.model import User\nfrom industrialai.pkg.authentication import JWT\nimport logging\nfrom pecan import abort\nfrom pecan import expose\nfrom pecan import request\nfrom wsme import types as wtypes\nlogger = logging.getLogger(__name__)\n\n\nclass logForm:\n account = wtypes.text\n pwd = wtypes.text\n\n\nclass checkForm:\n id = int\n pwd = wtypes.text\n\n\nclass registryForm:\n account = wtypes.text\n pwd = wtypes.text\n phone = wtypes.text\n mail = wtypes.text\n name = wtypes.text\n number = wtypes.text\n\n\nclass RootController(object):\n\n @expose('json')\n def index(self):\n return None\n\n @wsexpose(wtypes.text, body=logForm)\n def signin(self, logForm):\n account = logForm.account\n pwd = logForm.pwd\n user = api.get_session().query(User). \\\n filter_by(account=account, pwd=pwd).first()\n if user is None:\n abort(401)\n token = JWT.createToken(user.id, user.roleId)\n return {\n \"token\": token\n }\n\n @wsexpose(wtypes.text, body=checkForm)\n def pwdcheck(self, checkForm):\n try:\n api.get_session().query(User). \\\n filter_by(id=checkForm.id, pwd=checkForm.pwd).first()\n except Exception:\n return \"Failure\"\n return \"Success\"\n\n @expose('json')\n def myself(self):\n token = request.cookies.get(\"token\")\n message = JWT.deToken(token)\n if message is None:\n abort(401)\n else:\n uId = message.get(\"userId\")\n user = api.get_session().query(User). \\\n filter_by(id=uId).first()\n user.pwd = None\n return user\n\n v1 = v1_controller.v1Controller()\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py +--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py (date 1652796268000) +@@ -7,7 +7,9 @@ + from pecan import abort + from pecan import expose + from pecan import request ++from pecan_swagger import decorators as swagger + from wsme import types as wtypes ++ + logger = logging.getLogger(__name__) + + +@@ -21,6 +23,7 @@ + pwd = wtypes.text + + ++@swagger.definitions('object') + class registryForm: + account = wtypes.text + pwd = wtypes.text +@@ -28,14 +31,60 @@ + mail = wtypes.text + name = wtypes.text + number = wtypes.text ++ check = checkForm() + + ++@swagger.response('myself', '401', 'fail') ++@swagger.response('index', '401', 'fail') ++@swagger.response('test', '200', 'test description') ++@swagger.method('test', 'index', 'signin', 'pwdcheck', 'myself') ++@swagger.path("/", "Root") + class RootController(object): ++ _custom_actions = { ++ 'test': ['GET'], ++ 'signin': ['POST'], ++ 'pwdcheck': ['POST'], ++ 'myself': ['POST'] ++ } ++ ++ @expose('json') ++ def test(self): ++ print("123") ++ return "test" + + @expose('json') + def index(self): + return None + ++ """ ++ @api {post} /signin signin ++ @apiName SignIn ++ @apiGroup User ++ ++ @apiBody {String} account 账号 ++ @apiBody {String} pwd 密码 ++ ++ @apiSuccess {String} token ++ @apiSuccessExample Success-Response: ++ { ++ "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. ++ eyJleHAiOjE2NDYzNzc2MjksInVzZXJJZCI6MSwiZ3JvdXBJZCI6MSwicm9sZUlkIjozfQ. ++ fEqaaS8c2A_i-2W9D_L76mJac2vcyoS51g6trXr34lM" ++ } ++ ++ @apiError Unauthorized ++ @apiErrorExample {json} Error-Response: ++ { ++ "faultcode": "Client", ++ "faultstring": "This server could not verify that you are ++ authorized to access the document you requested. Either ++ you supplied the wrong credentials (e.g., bad password), ++ or your browser does not understand how to supply the ++ credentials required.", ++ "debuginfo": null ++ } ++ """ ++ + @wsexpose(wtypes.text, body=logForm) + def signin(self, logForm): + account = logForm.account +@@ -49,6 +98,22 @@ + "token": token + } + ++ """ ++ @api {post} /pwdcheck check ++ @apiName PwdCheck ++ @apiGroup User ++ ++ @apiBody {Integer} id 用户id ++ @apiBody {String} pwd 密码 ++ ++ @apiSuccess {Bool} result ++ @apiSuccessExample Success-Response: ++ HTTP/1.1 OK 200 ++ { ++ "result": True True:校验通过/False:校验失败 ++ } ++ """ ++ + @wsexpose(wtypes.text, body=checkForm) + def pwdcheck(self, checkForm): + try: +@@ -58,6 +123,24 @@ + return "Failure" + return "Success" + ++ """@api {get} /myself get_myself ++ @apiName GetMyself ++ @apiGroup User ++ ++ @apiSuccess (200) {Object} user ++ @apiSuccessExample Success-Response: ++ { ++ "id": 6, 用户id ++ "number": null, 身份证号 ++ "roleId": 3, 角色 ++ "status": 1, 状态:0/1 ++ "create_at": "2022-03-09 11:24:18.856936", 创建时间 ++ "name": "test", 名称 ++ "account": "testsss1-put", 账号 ++ "phone": "12345", 手机号 ++ } ++ """ ++ + @expose('json') + def myself(self): + token = request.cookies.get("token") +Index: examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from industrialai.pkg.authentication import JWT\nfrom industrialai.pkg.page import get_page\nfrom pecan import abort\nfrom pecan import expose\nfrom pecan import request\nfrom pecan.rest import RestController\n\n\nclass UserController(RestController):\n\n @expose(\"json\")\n def get(self, **kwargs):\n page_num, page_size = get_page(request.body)\n validKeys = [\"enterpriseId\", \"name\", \"account\", \"roleId\", \"status\"]\n kwargs = {k: v for k, v in kwargs.items() if k in validKeys}\n token = request.cookies.get(\"token\")\n message = JWT.deToken(token)\n if message is None:\n abort(401)\n else:\n eId = message.get(\"enterpriseId\")\n rId = message.get(\"roleId\")\n if rId != 5:\n kwargs[\"enterpriseId\"] = eId\n db_conn = request.db_conn\n users, pages = db_conn.list_users(page_num, page_size, **kwargs)\n return {\n \"data\": users,\n \"pages\": pages\n }\n\n @expose(\"json\")\n def get_one(self, user_id):\n db_conn = request.db_conn\n user = db_conn.get_user(user_id)\n user.pwd = None\n return user\n\n @expose(\"json\")\n def post(self, user):\n db_conn = request.db_conn\n db_conn.add_user(user)\n return \"Success\"\n\n @expose(\"json\")\n def put(self, user):\n token = request.cookies.get(\"token\")\n message = JWT.deToken(token)\n if message is None:\n abort(401)\n else:\n uId = message.get(\"userId\")\n rId = message.get(\"roleId\")\n if uId != int(user.get(\"id\")) and rId != 5:\n abort(401)\n elif uId == int(user.get(\"id\")):\n user[\"roleId\"] = None\n user[\"phone\"] = None\n db_conn = request.db_conn\n db_conn.update_user(user)\n return \"Success\"\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py +--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py (date 1652794836000) +@@ -4,9 +4,64 @@ + from pecan import expose + from pecan import request + from pecan.rest import RestController ++from pecan_swagger import decorators as swagger + + ++@swagger.response('put', '401', 'fail') ++@swagger.response('post', '401', 'fail') ++@swagger.response('get_one', '401', 'fail') ++@swagger.response('get', '401', 'fail') ++@swagger.response('get', '200', 'test description') ++@swagger.parameter('get', {'name': "status", ++ 'in': "query", ++ 'description': 'Status values that need to be considered for filter', ++ 'required': True, ++ 'type': 'array', ++ 'items': {'type': 'string', ++ 'enum': ["available", "pending", "sold"], ++ 'default': "available"}, ++ 'collectionFormat': "multi"}) ++@swagger.operationId('get', 'get users') ++@swagger.description('get', 'get users information') ++@swagger.summary('get', 'get users information') ++@swagger.tags('put', 'user') ++@swagger.tags('post', 'user') ++@swagger.tags('get', 'user') ++@swagger.tags('get_one', 'user') ++@swagger.method('get', 'get_one', 'post', 'put') ++@swagger.path("users", "User", "Root") + class UserController(RestController): ++ """@api {get} /v1/users/?name= get_all_users ++ ++ @apiName GetAllUsers ++ @apiGroup User ++ ++ @apiParam {String} name name of user 根据名称进行查询(使用like进行匹配) ++ @apiParam {Integer} status status of user 根据用户状态过滤 ++ @apiParam {String} account account of user 根据账号进行查询(使用like进行匹配) ++ @apiParam {Integer} roleId role of user 根据角色进行过滤 ++ ++ @apiSuccess (200) {Object[]} users the list of user ++ @apiSuccessExample Success-Response: ++ { ++ "data": [ ++ { ++ "id": 6, 用户id ++ "number": null, 身份证号 ++ "roleId": 3, 角色 ++ "status": 1, 状态:0/1 ++ "create_at": "2022-03-09 11:24:18.856936", 创建时间 ++ "name": "test", 名称 ++ "account": "testsss1-put", 账号 ++ "phone": "12345", 手机号 ++ "email": null, 邮箱 ++ }, ++ ], ++ "pages": 10 总页数 ++ “total": 10 总数 ++ } ++ """ ++ + + @expose("json") + def get(self, **kwargs): +@@ -29,6 +84,27 @@ + "pages": pages + } + ++ """@api {get} /v1/users/ get_one_user ++ @apiName GetOneUser ++ @apiGroup User ++ ++ @apiParam {Integer} userId id of user ++ ++ @apiSuccess (200) {Object} user ++ @apiSuccessExample Success-Response: ++ { ++ "id": 6, 用户id ++ "number": null, 身份证号 ++ "roleId": 3, 角色 ++ "status": 1, 状态:0/1 ++ "create_at": "2022-03-09 11:24:18.856936", 创建时间 ++ "name": "test", 名称 ++ "account": "testsss1-put", 账号 ++ "phone": "12345", 手机号 ++ "email": null, 邮箱 ++ } ++ """ ++ + @expose("json") + def get_one(self, user_id): + db_conn = request.db_conn +@@ -36,12 +112,78 @@ + user.pwd = None + return user + ++ """@api {post} /v1/users create_user ++ @apiName CreateUser ++ @apiGroup User ++ ++ @apiBody {Object} user ++ @apiParamExample {json} Request-Example: ++ { ++ "user": { ++ "account": "testsss1-put", ++ "phone": "12345", ++ "email": xxx@xxxx.com, ++ "name": "test", ++ "pwd": "123456", ++ "roleId": 3 ++ } ++ } ++ ++ @apiSuccess {String} result ++ @apiSuccessExample Success-Response: ++ HTTP/1.1 200 OK ++ ++ @apiError Unauthorized ++ @apiErrorExample {json} Error-Response: ++ { ++ "faultcode": "Client", ++ "faultstring": "This server could not verify that you are ++ authorized to access the document you requested. Either you ++ supplied the wrong credentials (e.g., bad password), or your ++ browser does not understand how to supply the credentials ++ required.", ++ "debuginfo": null ++ } ++ """ ++ + @expose("json") + def post(self, user): + db_conn = request.db_conn + db_conn.add_user(user) + return "Success" + ++ """@api {put} /v1/users update_user ++ @apiName UpdateUser ++ @apiGroup User ++ ++ @apiBody {Object} user ++ @apiParamExample {json} Request-Example: ++ { ++ "user": { ++ "id": 6, ++ "enterpriseId": 1, ++ "enterpriseGroupId": 1, ++ "name": "test" ++ } ++ } ++ ++ @apiSuccess {String} result ++ @apiSuccessExample Success-Response: ++ "Success" ++ ++ @apiError Unauthorized ++ @apiErrorExample {json} Error-Response: ++ { ++ "faultcode": "Client", ++ "faultstring": "This server could not verify that you are ++ authorized to access the document you requested. Either you ++ supplied the wrong credentials (e.g., bad password), or your ++ browser does not understand how to supply the credentials ++ required.", ++ "debuginfo": null ++ } ++ """ ++ + @expose("json") + def put(self, user): + token = request.cookies.get("token") +Index: examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>from industrialai.pkg.page import get_page\nfrom pecan import expose\nfrom pecan import request\nfrom pecan.rest import RestController\n\n\nclass RoleController(RestController):\n\n @expose('json')\n def get_all(self):\n page_num, page_size = get_page(request.body)\n db_conn = request.db_conn\n roles, num = db_conn.list_roles(page_num, page_size)\n return {\n \"data\": roles,\n \"pages\": num\n }\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py +--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py (date 1652794554000) +@@ -1,10 +1,44 @@ ++from industrialai.api.expose import expose as wsexpose + from industrialai.pkg.page import get_page + from pecan import expose + from pecan import request + from pecan.rest import RestController ++from pecan_swagger import decorators as swagger ++from wsme import types as wstypes ++ + ++class Roletest(wstypes.Base): ++ message = wstypes.text ++ roleId = int + ++ ++@swagger.response('get_all', '401', 'fail') ++@swagger.method('test', 'get_all') ++@swagger.path("roles", "Role", "Root") + class RoleController(RestController): ++ _custom_actions = { ++ 'test': ['POST'] ++ } ++ ++ """ ++ @api {get} /v1/roles get_all_roles ++ @apiName GetAllRoles ++ @apiGroup Role ++ ++ @apiSuccess {Object[]} roles ++ @apiSuccessExample {type} Success-Response: ++ HTTP/1.1 200 OK ++ [ ++ “data":[ 角色列表 ++ { ++ "title": "expert", 角色名称 ++ "id": 1 角色id ++ }, ++ ] ++ "pages": 12 总页数 ++ “total": 10 总数 ++ ] ++ """ + + @expose('json') + def get_all(self): +@@ -15,3 +49,11 @@ + "data": roles, + "pages": num + } ++ ++ @wsexpose(Roletest, body=Roletest) ++ def test(self, role): ++ print(role) ++ role.message = "out" ++ role.roleId = 1 ++ print(role) ++ return role +Index: .gitignore +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+># Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# Build\nexample_wsme_app.json\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n# Usually these files are written by a python script from a template\n# before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/.gitignore b/.gitignore +--- a/.gitignore (revision 2159b53baa4c86d9c1b739260824b7f89f935759) ++++ b/.gitignore (date 1653373170493) +@@ -55,3 +55,5 @@ + + # PyBuilder + target/ ++/examples/industrial-ai-apiserver/test.json ++/examples/industrial-ai-apiserver/test.yaml +diff --git a/pecan_swagger/__init__.py b/pecan_swagger/__init__.py +deleted file mode 100644 diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24__14_46__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24__14_46__Changes_.xml new file mode 100644 index 0000000..436185f --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24__14_46__Changes_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..11edc86 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1651814739960 + + + + + + + \ No newline at end of file diff --git a/API Reference.md b/API Reference.md new file mode 100644 index 0000000..2169d98 --- /dev/null +++ b/API Reference.md @@ -0,0 +1,142 @@ +# Pecan Swagger API Reference + +```python +pecan_swagger.utils.swagger_build(title, version) +``` +Parameters: +- **title** (String): The title of the application. +- **version** (String): Provides the version of the application API. + +Returns: +- **Swagger Dict** + +```python +pecan_swagger.utils.add_info(s, description=None, terms_of_service=None, contact=None, license=None) +``` +Parameters: +- **s** (Swagger Dict): Swagger Dict create by swagger_build. +- **description** (String): A short description of the application. +- **terms_of_service** (String): The Terms of Service for the API. +- **contact** (Dict): The contact information for the exposed API. Key should be in "name", "url", "email" or patterned string started with "x-". Values should be string for contact. +- **license** (String): Provides the version of the application API. Key should be in "name", "url" or patterned string started with "x-". Values should be string for license. + +Returns: +- **Swagger Dict** + +```python +pecan_swagger.utils.add_external_docs(s, external_docs) +``` +Parameters: +- **s** (Swagger Dict): Swagger Dict create by swagger_build. +- **external_docs** (Dict): Allows referencing an external resource for extended documentation. Key must include "url" and should be in "description", or patterned string started with "x-". Values should be string for license. + +Returns: +- **Swagger Dict** + +```python +pecan_swagger.utils.add_host(s, host) +``` +Parameters: +- **s** (Swagger Dict): Swagger Dict create by swagger_build. +- **host** (String): The host (name or ip) serving the API. +This MUST be the host only and does not include the scheme nor sub-paths. + +Returns: +- **Swagger Dict** + +```python +pecan_swagger.utils.add_base_path(s, base_path) +``` +Parameters: +- **s** (Swagger Dict): Swagger Dict create by swagger_build. +- **base_path** (String): The base path on which the API is served, which is relative to the host. If it is not included, the API is served directly under the host. + +Returns: +- **Swagger Dict** + +```python +pecan_swagger.utils.print_json(s, output_path) +``` +Parameters: +- **s** (Swagger Dict): Swagger Dict create by swagger_build. +- **base_path** (String): Path to output the json file. + +```python +pecan_swagger.utils.print_yaml(s, output_path) +``` +Parameters: +- **s** (Swagger Dict): Swagger Dict create by swagger_build. +- **base_path** (String): Path to output the json file. + + +```python +@pecan_swagger.decorators.path(endpoint, name, parent=None) +``` +Parameters: +- **endpoint** (String): The root uri for this controller. +- **base_path** (String): The name of this path. +- **parent** (String): An optional path name to indicate a parent/child relationship between this path and another. + +```python +@pecan_swagger.decorators.response(method_name, res_code, des, schema=None) +``` +Parameters: +- **method_name** (String): The name of the method. +- **res_code** (String): The HTTP response code of this response. +- **des** (String): The description of this response. +- **schema** (String): The schema of this response. + +```python +@pecan_swagger.decorators.tags(method_name, *tags) +``` +Parameters: +- **method_name** (String): The name of the method. +- ***tags** (String): The tags of this response. + +```python +@pecan_swagger.decorators.summary(method_name, summary) +``` +Parameters: +- **method_name** (String): The name of the method. +- **summary** (String): The summary of the method. + +```python +@pecan_swagger.decorators.description(method_name, des) +``` +Parameters: +- **method_name** (String): The name of the method. +- **des** (String): The description of the method. + +```python +@pecan_swagger.decorators.operationId(method_name, oi) +``` +Parameters: +- **method_name** (String): The name of the method. +- **oi** (String): The operationId of the method. + +```python +@pecan_swagger.decorators.method(*args) +``` +Parameters: +- ***args** (String): The names of the method. + +```python +@pecan_swagger.decorators.definitions(t, *args) +``` +Parameters: +- **t** (String): The type of the definition. +- ***args** (String): The properties of the definition. + +```python +@pecan_swagger.decorators.definitions(t, *args) +``` +Parameters: +- **t** (String): The type of the definition. +- ***args** (String): The properties of the definition. + +```python +@pecan_swagger.decorators.parameter(method_name, *args) +``` +Parameters: +- **method_name** (String): The name of the method. +- ***args** (String): The parameters of the definition. \ No newline at end of file diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..11b20ae6c07fee8a9188fce9c11763f834b55ea0 GIT binary patch literal 8196 zcmeHMU2GIp6u#fI&>1_}TLfE%Ev!@lwbB-9`4M#6@?W9IZht}RZfBq))0w(6yG2N2 ziAE#(LNwx#dASXqLFfvAbZAq7O*_CS(Z5$%#H_Q z1Y!hY1Y!hY1Y!iP1qA5M7Dc_py)U(K9U~AU@V`VroDWHQgiJ+pLdx*ypejrONQx97 zEYzkvAhbzFG8M@QDMM3QQ)Ul{OcA9RkmjUMis>X%k(`iHnlngqhG=I*84AMP$u1dA zXGlpI*D(Sy0v946=u<-(yUR>gn3BKWL0P=bL_JE!>a6_pkDr-!|ME4$lp;wrk}> z1KYG{%I;pT6WP|Pb<<=v&C**qhcAq$IAO8>r+h<1nubc zHHn*UxvjBf^}6QgRW=xQ6O+X+N!BpH@cl{&T_b^$$jo@)s5bw^p1B%gKOsK zM&FP(GD5ES(b(Ph7{&u)+k3Nq&^2ng^h#evdw0+)^$T5_X@)N}n^#vEn~kk1|6U#N zhqHOBYm6dvG8*5ms^RBK<6^Z9rX6_al{^=4|JI20Y$Jr@%n*GYovU30wltIT_)Swpgun3K4#&WE{ zT5QG^Y(*M<7{D&7G7*YO74#Jl(mpW_RBiEnWN zC-DP*#xFRHGx!aE;7^=WxH3;Ml=;dsrBPX>v?<$_F6oC-B3OUP#c2FKl9zd`5W%8j zMS1Ds1WRn%ylIQR@|q{uiR&e$=H6J76g*qCuI+qC$`~$%?cd1|GGIzzgbWBNH!XT6 ziFor9b^5{*FQ@aho2X;$R|Qjfg`B-ezgZ>f@ENkZM88!f((swG+mKkM5>2@hcJ=1O z3XRCf%Vf79u~JhxWM_@ORwG*Sa@noZQ<}zE*hzFGHmF1`uKsJx|6{~|$4;^z*%|gb zJByj91jj-wMH5<)!bWVO81KY3^q?306zL+)_h1+{_8~xlVtqdj;4wUoC-5YmqIiFf zB3{J%%XkH^;x!z_TX-Aq;6r?bWB3H05(B=*Bu-6>>dvWAjh`+*s*CBo>v-;7vMh0B z>Y`V%S0?wa75V>$OaK0V72h!4Iz}Ky;7TKa^7ZNU?X=6KPAG+RNqQcnM-(w{LdwvD uDltWjhv_)U^M4ppI~nOxk(`i{G*tfc9|ErY7wq`{kMI9dvV6_n|9=6cn3^yE literal 0 HcmV?d00001 diff --git a/examples/industrial-ai-apiserver/myapp-doc.py b/examples/industrial-ai-apiserver/myapp-doc.py new file mode 100755 index 0000000..88c51af --- /dev/null +++ b/examples/industrial-ai-apiserver/myapp-doc.py @@ -0,0 +1,24 @@ +# import pprint +import json +from pecan_swagger import utils +import industrialai.api.controllers.root + +# pp = pprint.PrettyPrinter(indent=2) +# pp.pprint(utils.swagger_build('industrialai', '1.0')) +# Create JSON file +s = utils.swagger_build('industrialai', '1.0') +description = 'This is a This is a test project' +termsOfService = 'http://swagger.io/terms/' +contact = {'email': "apiteam@swagger.io"} +license = {'name': "Apache 2.0", + 'url': "http://www.apache.org/licenses/LICENSE-2.0.html"} +s = utils.add_info(s, description, termsOfService, contact, license) +externalDocs = {'description': "Find out more about Swagger", + 'url': "http://swagger.io"} +s = utils.add_external_docs(s, externalDocs) +host = "127.0.0.1" +s = utils.add_host(s, host) +basePath = "/v1" +s = utils.add_base_path(s, basePath) +output_path = "test.json" +utils.print_json(s, output_path) diff --git a/examples/industrial-ai-apiserver/pecan_swagger/__init__.py b/examples/industrial-ai-apiserver/pecan_swagger/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/examples/industrial-ai-apiserver/pecan_swagger/decorators.py b/examples/industrial-ai-apiserver/pecan_swagger/decorators.py new file mode 100755 index 0000000..08de158 --- /dev/null +++ b/examples/industrial-ai-apiserver/pecan_swagger/decorators.py @@ -0,0 +1,161 @@ +import pecan_swagger.g as g + +""" +decorators module + +these decorators are meant to be used by developers who wish to markup +their pecan applications to produce swagger. +""" + + +def path(endpoint, name, parent=None): + """ + path decorator + + this decorator should be used on pecan controllers to instruct how + they map to routes. + + :param endpoint: the root uri for this controller. + :param name: the name of this path. + :param parent: an optional path name to indicate a parent/child + relationship between this path and another. + """ + + def decorator(c): + if hasattr(c, '__swag'): + raise Exception('{} already has swag'.format(c.__name__)) + c.__swag = dict(endpoint=endpoint, name=name, parent=parent) + g.add_path(c) + return c + + return decorator + + +def response(method_name, res_code, des, schema=None): + """ + 获取方法的response + 生成字典{response:res_code:{description:des}} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + + if not m.__swag['method'][method_name].get('responses'): + m.__swag['method'][method_name]['responses'] = {} + if not m.__swag['method'][method_name]['responses'].get(res_code): + m.__swag['method'][method_name]['responses'][res_code] = {} + m.__swag['method'][method_name]['responses'][res_code]['description'] = des + if schema: + m.__swag['method'][method_name]['responses'][res_code]['schema'] = schema + return m + + return decorator + + +def tags(method_name, *tags): + """ + 获取方法的tags + 生成字典{tags:tag} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + if not m.__swag['method'][method_name].get('tags'): + m.__swag['method'][method_name]['tags'] = [] + for i in tags: + m.__swag['method'][method_name]['tags'].append(i) + return m + + return decorator + + +def summary(method_name, summary): + """ + 获取方法的summary + 生成字典{summary:summary} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + m.__swag['method'][method_name]['summary'] = summary + return m + + return decorator + + +def description(method_name, des): + """ + 获取方法的description + 生成字典{description:des} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + m.__swag['method'][method_name]['description'] = des + return m + + return decorator + + +def operationId(method_name, oi): + """ + 获取方法的operationId + 生成字典{operationId:oi} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + m.__swag['method'][method_name]['operationId'] = oi + return m + + return decorator + + +def method(*args): + """ + 获取被装饰类包含的方法 + 生成method字典 + 并加入controller.__swag + """ + + def decorator(m): + d = {} + for i in args: + if hasattr(m, i): + d[i] = {} + m.__swag['method'] = d + return m + + return decorator + + +def definitions(t, *args): + """ + 添加definitions到g.py中 + """ + + def decorator(m): + g.add_definitions(t, m) + return m + + return decorator + + +def parameter(method_name, *args): + """ + 获取方法的parameter + 生成字典{parameter:[]} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + if not m.__swag['method'][method_name].get('parameters'): + m.__swag['method'][method_name]['parameters'] = [] + for i in args: + if i.get('schema'): + i['schema'] = {'$ref': '#/definitions/' + i['schema']} + m.__swag['method'][method_name]['parameters'].append(i) + return m + + return decorator + diff --git a/examples/industrial-ai-apiserver/pecan_swagger/g.py b/examples/industrial-ai-apiserver/pecan_swagger/g.py new file mode 100755 index 0000000..15f6104 --- /dev/null +++ b/examples/industrial-ai-apiserver/pecan_swagger/g.py @@ -0,0 +1,617 @@ +import copy +import inspect + +from pecan import util as p_u +import wsme.types as wtypes +import six +import weakref + +""" +global hierarchy module + +this module is at the heart of the swagger conversion utility. it +contains a global "hierarchy" object which will provide a single point +to assemble an application's swagger output. + +there are also several helper functions to assist in building the +hierarchy of controllers, routes, and methods. +""" + +_hierarchy = {} + +_http_methods = {'get': '', 'post': '', 'put': '', + 'patch': '', 'delete': '', + 'head': '', 'trace': ''} + +_all_wsme_types = wtypes.native_types + ( + wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType, + wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType, + wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType) + +_definitions = {} + +all_definitions = {} + + +def add_path(c): + """adds a named controller to the hierarchy.""" + if _hierarchy.get(c.__swag['name']): + raise Exception( + 'name {} already exists in hierarchy'.format(c.__swag['name'])) + _hierarchy[c.__swag['name']] = c + + +def build_path(swaginfo): + """return the route path for a swag metadata item.""" + if swaginfo.get('parent') is not None: + return path_join(build_path(get_swag(swaginfo.get('parent'))), + swaginfo.get('endpoint')) + return swaginfo.get('endpoint') + + +def get_controller_paths(controllers, wsme_defs): + """ + get a list of paths with methods + + returns a list of tuples (controller path, methods). + """ + + def get_methods_for_generic(name): + methods = [] + generic_handlers = lc.get(name).get('generic_handlers', {}) + for method, func in generic_handlers.items(): + if method == 'DEFAULT': + methods.append('get') + else: + methods.append(method.lower()) + # TODO drill down through decorators to get true function name + truename = getrealname(func) + del lc[truename] + return methods + + def append_methods_for_specific(name, tpl): + paths.append(tpl) + del lc[name] + + lc = copy.deepcopy(controllers) + paths = [] + # TODO incorporate method decorator, removing functions marked + if lc.get('index'): + for method in get_methods_for_generic('index'): + paths.append(('', (method, {}))) + spec = wsme_defs.get('index') # add wsme definitions + append_methods_for_specific('index', ('', ('get', spec))) + + # for REST controller + for method, path in _http_methods.items(): + if lc.get(method): + spec = wsme_defs.get(method, {}) # add wsme definitions + append_methods_for_specific(method, (path, (method, spec))) + + if lc.get('get_all'): + # add wsme definitions + spec = wsme_defs.get('get_all') # add wsme definitions + append_methods_for_specific('get_all', ('get_all', ('get', spec))) + if lc.get('get_one'): + # add wsme definitions + spec = wsme_defs.get('get_one') # add wsme definitions + append_methods_for_specific('get_one', ('get_one', ('get', spec))) + + if lc.get('_default'): + append_methods_for_specific('_default', ('', ('*', {}))) + if lc.get('_route'): + append_methods_for_specific('_route', ('', ('*', {}))) + if lc.get('_lookup'): + del lc['_lookup'] + + if lc.get('_custom_actions'): + for ca, ca_method in lc['_custom_actions'].items(): + spec = wsme_defs.get(ca) # add wsme definitions + for m in ca_method: + paths.append((ca, (m.lower(), spec))) + del lc['_custom_actions'] + + generic_controllers = [c for c in lc if lc[c].get('generic')] + for controller in generic_controllers: + for method in get_methods_for_generic(controller): + paths.append((controller, (method, {}))) + for controller in lc: + if controller not in [path[0] for path in paths]: + paths.append((controller, ('get', {}))) + return paths + + +def get_wsme_defs(name): + """获得wsexpose装饰方法的definitions.""" + + def datatype_to_type_and_format(datatype): + tf = {} + if datatype == 'uuid' or datatype == 'ipv4address' \ + or datatype == 'ipv6address' or datatype == 'date' \ + or datatype == 'time': + tf['type'] = 'string' + tf['format'] = datatype + elif datatype == 'datetime': + tf['type'] = 'string' + tf['format'] = 'date-time' + elif datatype == 'binary' or datatype == 'bytes': + # base64 encoded characters + tf['type'] = 'string' + tf['format'] = 'byte' + elif datatype == 'array' or datatype == 'boolean' \ + or datatype == 'integer' or datatype == 'string': + # no format + tf['type'] = datatype + elif datatype == 'float': + # number + tf['type'] = 'number' + tf['format'] = datatype + elif datatype == 'unicode' or datatype == 'str' \ + or datatype == 'text': + # primitive, no format + tf['type'] = 'string' + elif datatype == 'int' or datatype == 'decimal': + # primitive, no format + tf['type'] = 'integer' + elif datatype == 'bool': + # primitive, no format + tf['type'] = 'boolean' + elif datatype == 'enum': + tf['type'] = 'enum' + elif datatype == 'unset' or datatype == 'none': + tf['type'] = None + elif datatype == 'dict': + tf['type'] = 'object' + else: + tf['type'] = 'object' + return tf + + def class_to_name_str(c): + return (('%s' % c).replace('<', '').replace('>', '') + .replace('class ', '').replace('\'', '') + .split(' ', 1)[0].rsplit('.', 1)[-1]) + + def conv_class_name_to_type_str(cn): + type_str = '' + if cn.endswith('Type'): + type_str = cn[:-4] + elif cn == 'str': + type_str = 'string' + else: + type_str = cn + type_str = type_str.lower() + return type_str + + def get_type_str(obj, src_dict=None): + type_str = '' + if hasattr(obj, '__name__'): + type_str = obj.__name__ + else: + type_str = obj.__class__.__name__ + type_str = conv_class_name_to_type_str(type_str) + + tf = datatype_to_type_and_format(type_str) + + if hasattr(obj, 'basetype') and \ + (obj.__class__ not in _all_wsme_types or type_str == 'enum'): + # UserType or Enum + tf['type'] = get_type_str(obj.basetype) + + if isinstance(src_dict, dict): + # if dict is in args, set 'type' and 'format' into the dict and + # return + src_dict.update({'type': tf['type']}) + if 'format' in tf: + src_dict.update({'format': tf['format']}) + + # get datatype options. ex.) min_length, minimum, .. + for k, v in inspect.getmembers(obj): + if ((k == 'minimum' or k == 'maxmum' + or k == 'min_length' or k == 'max_length') + and v is not None): + src_dict[to_swag_key(k)] = v + elif k == 'pattern' and v is not None: + src_dict[to_swag_key(k)] = v.pattern + # TODO(shu-mutou): this should be removed for production. + # else: + # src_dict[to_swag_key(k)] = v + + if type_str == 'enum': + # EnumType use 'set' that doesn't have sequence. + # So use 'sorted' for keeping static output. + src_dict['enum'] = sorted([v for v in obj.values]) + + # return 'type' only + return tf['type'] + + def to_swag_key(key): + keys = { + 'doc': 'description', + 'arguments': 'parameters', + 'return_type': 'schema', + 'datatype': 'type', + 'mandatory': 'required', + 'sample': 'examples', + 'readonly': 'readOnly', + 'min_length': 'minLength', + 'max_length': 'maxLength', + } + if key in keys: + return keys[key] + else: + return key + + def get_wm_item_prop(item, isparams=False): + # Add prop into 'properties' and 'required' in 'Items Object' + # 'Items Object' can be part of 'Schema Object' or 'Items Object', + # and can use recursively + prop_dict = {} + # TODO(shu-mutou): this should be removed for production. + # prop_dict['obj'] = inspect.getmembers(item) + + _item = item + if wtypes.iscomplex(item): + _item = weakref.ref(item) + + for a, i in inspect.getmembers(_item): + if a == 'datatype': + datatype = get_type_str(i, prop_dict) + if datatype == 'array': + # if array, do recursively + prop_dict['items'] = inspect_wm_schema(i.item_type) + elif datatype == 'object': + # if obuject, do recursively + prop_dict['items'] = inspect_wm_schema(i) + elif a == 'default' and i: + prop_dict[to_swag_key(a)] = i + elif a == 'name' and isparams: + prop_dict[to_swag_key(a)] = i + elif a == 'mandatory' and i: + prop_dict[to_swag_key(a)] = i + elif a == 'readonly' and i: + prop_dict[to_swag_key(a)] = i + elif a == 'doc' and i is not None: + prop_dict[to_swag_key(a)] = i + + if isparams and prop_dict['type'] in ['object', 'array']: + prop_dict['schema'] = {'items': prop_dict['items'], + 'type': prop_dict['type']} + if prop_dict['type'] == 'object': + prop_dict['schema'] = prop_dict['items'] + del prop_dict['type'] + del prop_dict['items'] + return prop_dict + + def get_wsattr_and_wsproperty(obj): + ws_dict = {} + for key, value in inspect.getmembers(obj): + if (key[0] != '_' and + (value.__class__.__name__ == 'wsattr' + or value.__class__.__name__ == 'wsproperty')): + ws_dict[key] = value + return ws_dict + + def inspect_wm_schema(schema_obj, isparams=False): + schema_dict = {} + # TODO(shu-mutou): this should be removed for production. + # schema_dict['obj'] = get_wsattr_and_wsproperty(schema_obj) + ws_len = len(get_wsattr_and_wsproperty(schema_obj)) + model_name = class_to_name_str(schema_obj) + + for key, obj in inspect.getmembers(schema_obj): + if (key[0] != '_' and + (obj.__class__.__name__ == 'wsattr' + or obj.__class__.__name__ == 'wsproperty')): + # TODO(shu-mutou): this should be removed for production. + # _definitions[model_name][to_swag_key(key)] = \ + # {'obj': inspect.getmembers(obj)} + + if ws_len == 1: + # single schema + schema_dict = get_wm_item_prop(obj, isparams) + else: + # multi property schema + schema_dict.update({'type': 'object'}) + # properties + if 'properties' not in schema_dict: + schema_dict['properties'] = {} + prop = {key: get_wm_item_prop(obj, isparams)} + # required as array of string + if 'required' in prop[key] and prop[key]['required'] \ + and isinstance(prop[key]['required'], bool): + if 'required' not in schema_dict: + schema_dict['required'] = [] + schema_dict['required'].append(key) + del prop[key]['required'] + schema_dict['properties'].update(prop) + + if schema_obj not in _all_wsme_types: + if model_name not in _definitions: + _definitions[model_name] = schema_dict + schema_dict = {'$ref': "#/definitions/%s" % model_name} + + return schema_dict + + def get_wm_def(o): + wsme = {'description': ''} + for w, d in inspect.getmembers(o): + if w == 'arguments': + wsme[to_swag_key(w)] = [] + for arg in d: + # set each 'Parameter Object', it can include + # 'Items Object' recursively + item_dict = get_wm_item_prop(arg, True) + # TODO: MUST be set one of + # 'body|query|path|header|formData' + item_dict['in'] = 'query' + wsme[to_swag_key(w)].append(item_dict) + # 'body' should be set due to existing 'body' option + # in WSME expose + if o.body_type is not None: + # if body is set, last arg is it. + wsme[to_swag_key(w)][-1]['in'] = 'body' + elif w == 'doc' and d: + wsme[to_swag_key(w)] = d + elif w == 'return_type': + wsme['responses'] = {'status': {'description': ''}} + if d: + if d in _all_wsme_types: + # d is set single WSME type class or implicit type + wsme['responses']['status'][to_swag_key(w)] = ( + datatype_to_type_and_format( + conv_class_name_to_type_str( + class_to_name_str(d)))) + else: + # d is model class + wsme['responses']['status'][to_swag_key(w)] = ( + inspect_wm_schema(d, False)) + doc = inspect.getdoc(d) + if doc is not None: + wsme['responses']['status']['description'] = doc + elif w == 'status_code': + wsme['responses'][d] = wsme['responses']['status'] + del wsme['responses']['status'] + # TODO(shu-mutou): this should be removed for production. + # elif w == 'body_type': + # wsme[to_swag_key(w)] = get_type_str(d) + # elif w == 'extra_options' or w == 'ignore_extra_args' \ + # or w == 'name' or w == 'pass_request': + # wsme[to_swag_key(w)] = d + # else: + # wsme[to_swag_key(w)] = d + return wsme + + c = _hierarchy[name] + wsme_defs = {} + for k, v in inspect.getmembers(c): + if p_u.iscontroller(v): + for m, o in inspect.getmembers(v): + if m == "_wsme_definition": + wsme_defs[k] = get_wm_def(o) + + # TODO(shu-mutou): this should be removed for production. + # output wsme info into files by each controller for dev + # import pprint + # with open(name + '.txt', 'w') as fout: + # pprint.pprint(wsme_defs, stream=fout, indent=2) + + return wsme_defs + + +def get_controllers(name): + """ + get all the controllers associated with a path + + returns a dictionary of controllers indexed by their names. + """ + c = _hierarchy[name] + controllers = {k: p_u._cfg(v) + for k, v in c.__dict__.items() if p_u.iscontroller(v)} + cacts = {k: v for k, v in c.__dict__.items() if k == '_custom_actions'} + if len(cacts): + controllers.update(cacts) + return controllers + + +def get_paths(): + """ + return all the registered paths + + loops through the hierarchy and retuns a list of tuples containing the + paths and their methods. + + :returns: [(path, methods), ...] + """ + pathlist = [] + for name in _hierarchy: + fullpath = build_path(get_swag(name)) + controllers = get_controllers(name) + paths = get_controller_paths(controllers, combine_method_defs(name)) + for path in paths: + ptuple = (path_join(fullpath, path[0]), path[1]) + pathlist.append(ptuple) + combine_all_definitions() + return pathlist + + +def get_swag(name): + """return the swag metadata from an named controller.""" + return _hierarchy.get(name).__swag + + +def getrealname(method): + """attempt to get a method's real name.""" + argspec = inspect.getargspec(method) + args = argspec[0] + if args and args[0] == 'self': + return method.__name__ + if hasattr(method, '__func__'): + method = method.__func__ + + func_closure = six.get_function_closure(method) + + # NOTE(sileht): if the closure is None we cannot look deeper, + # so return actual argspec, this occurs when the method + # is static for example. + if func_closure is None: + return method.__name__ + + closure = next( + ( + c for c in func_closure if six.callable(c.cell_contents) + ), + None + ) + method = closure.cell_contents + return getrealname(method) + + +def methods_get(name): + """get all the methods for a named controller.""" + c = _hierarchy[name] + mlist = [] + if hasattr(c, 'index') and p_u.iscontroller(c.index): + cfg = p_u._cfg(c.index) + if cfg.get('generic_handlers') and cfg.get('generic_handlers').get('DEFAULT'): + mlist.append('get') + if cfg.get('allowed_methods'): + mlist += cfg.get('allowed_methods') + if hasattr(c, '__swag') and c.__swag.get('method'): + for i in c.__swag['method']: + mlist.append(i) + # for i in c.__dict__: + # ii = getattr(c, i) + # if hasattr(ii, '__swag'): + # m = ii.__swag.get('method') + # if m is not None: + # mlist.append(m) + return mlist + + +def path_join(part1, part2): + """join two url paths.""" + if len(part2) == 0: + return part1 + sep = '/' + if part1[-1] == sep: + sep = '' + return part1 + sep + part2 + + +def get_def(name): + """获取所有expose装饰方法的definitions.""" + c = _hierarchy[name] + if hasattr(c, '__swag') and c.__swag.get('method'): + return c.__swag['method'] + return {} + + +def combine_method_defs(name): + """合并expose和wsexpose装饰方法的definitions.""" + + c = _hierarchy[name] + lc = copy.deepcopy(c) + w_d = get_wsme_defs(name) + e_d = get_def(name) + if w_d and e_d: + for i in w_d: + if e_d.get(i): + for j in w_d[i]: + e_d[i][j] = w_d[i][j] + else: + e_d[i] = w_d[i] + elif w_d: + return w_d + else: + return e_d + return e_d + + +def format_type(t): + """ + 判断数据类型是否为自定义的数据类型 + """ + + def datatype_to_type_and_format(datatype): + + tf = {} + if datatype == 'uuid' or datatype == 'ipv4address' \ + or datatype == 'ipv6address' or datatype == 'date' \ + or datatype == 'time': + tf['type'] = 'string' + tf['format'] = datatype + elif datatype == 'datetime': + tf['type'] = 'string' + tf['format'] = 'date-time' + elif datatype == 'binary' or datatype == 'bytes': + # base64 encoded characters + tf['type'] = 'string' + tf['format'] = 'byte' + elif datatype == 'array' or datatype == 'boolean' \ + or datatype == 'integer' or datatype == 'string': + # no format + tf['type'] = datatype + elif datatype == 'float': + # number + tf['type'] = 'number' + tf['format'] = datatype + elif datatype == 'unicode' or datatype == 'str' \ + or datatype == 'text': + # primitive, no format + tf['type'] = 'string' + elif datatype == 'int' or datatype == 'decimal': + # primitive, no format + tf['type'] = 'integer' + elif datatype == 'bool': + # primitive, no format + tf['type'] = 'boolean' + elif datatype == 'enum': + tf['type'] = 'enum' + elif datatype == 'unset' or datatype == 'none': + tf['type'] = None + elif datatype == 'dict': + tf['type'] = 'object' + else: + tf['$ref'] = '#/definitions/' + datatype + return tf + + def conv_class_name_to_type_str(cn): + type_str = '' + if cn.endswith('Type'): + type_str = cn[:-4] + elif cn == 'str': + type_str = 'string' + else: + type_str = cn + type_str = type_str + return type_str + + def class_to_name_str(c): + return (('%s' % c).replace('<', '').replace('>', '') + .replace('class ', '').replace('\'', '') + .split(' ', 1)[0].rsplit('.', 1)[-1]) + + return datatype_to_type_and_format(conv_class_name_to_type_str(class_to_name_str(t))) + + +def add_definitions(t, m): + """将定义的类添加到all_definitions.""" + + if not all_definitions.get(m.__name__): + d = {} + d['type'] = t + d['properties'] = {} + for i, j in inspect.getmembers(m): + if not i.endswith('__'): + d['properties'][i] = format_type(j) + all_definitions[m.__name__] = d + + +def combine_all_definitions(): + """合并自定义和wsexpose解析出的definitions.""" + + for i in all_definitions: + if not _definitions.get(i): + _definitions[i] = all_definitions[i] + + return _definitions diff --git a/examples/industrial-ai-apiserver/pecan_swagger/utils.py b/examples/industrial-ai-apiserver/pecan_swagger/utils.py new file mode 100755 index 0000000..889fbf4 --- /dev/null +++ b/examples/industrial-ai-apiserver/pecan_swagger/utils.py @@ -0,0 +1,108 @@ +from pecan_swagger import g as g +import json +import yaml + +""" +utility function module + +this module contains the utility functions to assemble the swagger +dictionary object. they can be consumed by end-user applications to +build up swagger objects for applications. + +functions: +swagger_build -- build a full swagger dictionary +""" + + +def swagger_build(title, version): + swag = dict() + swag['swagger'] = '2.0' + swag['info'] = dict(title=title, version=version) + swag['consumes'] = [] + swag['produces'] = [] + swag['paths'] = {} + swag['tags'] = [] + swag['schemes'] = [] + for p in g.get_paths(): + if p[0] not in swag['paths']: + swag['paths'][p[0]] = _tuple_to_dict(p[1]) + elif len(p[1]) > 0: + swag['paths'][p[0]].update(_tuple_to_dict(p[1])) + swag['definitions'] = g._definitions + return swag + + +def print_json(s, output_path): + json_str = json.dumps(s, indent=4) + f = open(output_path, "w") + f.write(json_str) + f.close() + + +def print_yaml(s, output_path): + yaml_str = yaml.dump(s, indent=4) + f = open(output_path, "w") + f.write(yaml_str) + f.close() + + +def add_info(s, description=None, terms_of_service=None, contact=None, license=None): + if description is not None: + s['info']['description'] = description + if terms_of_service is not None: + s['info']['termsOfService'] = terms_of_service + if contact is not None: + s['info']['contact'] = contact + if license is not None: + s['info']['license'] = license + return s + + +def add_external_docs(s, external_docs): + s["externalDocs"] = external_docs + return s + + +def add_host(s, host): + s["host"] = host + return s + + +def add_base_path(s, basepath): + s["basePath"] = basepath + return s + + +def _tuple_to_dict(tpl): + """Convert tuple to dictionary + + each tuple must have key and value. + This function arrows taple including lists. + + ex.) acceptable taple + OK: ('/', ('get',(('desc',{}),('res',{}),('params',['id','name'])))) + NG: ('/', ('get',('desc','res',('params',['id','name'])))) + """ + if isinstance(tpl, list): + d = [] + for e in tpl: + d.append(_tuple_to_dict(e)) + elif isinstance(tpl, tuple): + d = {} + if isinstance(tpl[0], tuple): + # tuple has some child tuple + for e in tpl: + d[e[0]] = _tuple_to_dict(e[1]) + elif isinstance(tpl[0], list): + # list member should be processed recursively + d = _tuple_to_dict(tpl[0]) + else: + if len(tpl) == 2: + # single tuple node + d[tpl[0]] = _tuple_to_dict(tpl[1]) + else: + raise Exception(tpl) + else: + # value or dict + d = tpl + return d diff --git a/pecan_swagger/.DS_Store b/pecan_swagger/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0