From a7e12af66fbabc2fd32dfd2c90c4b8c00fa65528 Mon Sep 17 00:00:00 2001 From: fengdelu <492607694@qq.com> Date: Fri, 18 Mar 2022 16:43:32 +0800 Subject: [PATCH 1/5] fix:tox error 1. --- Desktop/pecan-swagger/README.md | 19 +++++++++ Desktop/pecan-swagger/README.rst | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 Desktop/pecan-swagger/README.md create mode 100644 Desktop/pecan-swagger/README.rst diff --git a/Desktop/pecan-swagger/README.md b/Desktop/pecan-swagger/README.md new file mode 100644 index 0000000..4409760 --- /dev/null +++ b/Desktop/pecan-swagger/README.md @@ -0,0 +1,19 @@ +# Pecan Swagger + +## 1. 项目背景 + +Pecan-Swagger 用于为 Pecan Web 服务生成 Swagger 文档。该项目源自 ,目前原 repo 已无人维护,[原示例链接](examples/README.rst)。 + +Pecan 是 OpenStack 诸多模块中比较受欢迎的一种 Restful API 框架,新兴的项目大多采用 Pecan,比如 Magnum / CloudKitty 等等。Pecan-Swagger 依然是有很大价值的,因此我们再这个 repo 继续更新 Pecan-Swagger。 + +## 2. 需要预先掌握的技能 + +1. [Python 三日课程](https://gitee.com/wu-wen-xiang/training-python/blob/master/doc/learning-python-in-3-days.md) +2. [Git 基本使用](https://gitee.com/wu-wen-xiang/training-python/blob/master/doc/learning-python-in-3-days.md#31-%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6) +3. [Markdown 基本语法](https://markdown.com.cn/basic-syntax/) +4. [Gitee 合作](https://gitee.com/help/articles/4346) +5. [Swagger](https://editor.swagger.io/) + +## 3. 预期目标 + +1. 对接 Pecan 最近的 Stable 版本,提供 Pecan-Swagger 模块,为 Pecan 项目提供在线的 Swagger 文档。 diff --git a/Desktop/pecan-swagger/README.rst b/Desktop/pecan-swagger/README.rst new file mode 100644 index 0000000..1bc6070 --- /dev/null +++ b/Desktop/pecan-swagger/README.rst @@ -0,0 +1,66 @@ +===================== +Pecan Swagger Project +===================== + +some helpers to create swagger output from a pecan app + +example usage +------------- + +given a file named ``myapp.py`` + +:: + + import pecan + from pecan_swagger import decorators as swagger + + + @swagger.path('profile', 'Profile', 'Root') + class ProfileController(object): + + @pecan.expose(generic=True, template='index.html') + def index(self): + return dict() + + @index.when(method='POST') + def index_post(self, **kw): + print(kw) + pecan.redirect('/profile') + + + @swagger.path('/', 'Root') + class RootController(object): + + profile = ProfileController() + +and another file named ``myapp-doc.py`` + +:: + + import pprint + + from pecan_swagger import utils + import myapp + + pp = pprint.PrettyPrinter(indent=2) + pp.pprint(utils.swagger_build('myapp', '1.0')) + + +the following will be produced when run + +:: + + $ python myapp-doc.py + { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": "myapp" + }, + "paths": { + "/profile": { + "POST": {}, + "GET": {} + } + } + } -- Gitee From eeba0c3f2c6be169f10c0826c94eb2b05ee23080 Mon Sep 17 00:00:00 2001 From: fengdelu <492607694@qq.com> Date: Wed, 30 Mar 2022 11:27:29 +0800 Subject: [PATCH 2/5] fix: tox error 1.remove unused function library 2.fix line too long 3.fix blank lines --- Desktop/pecan-swagger/examples/README.md | 0 Desktop/pecan-swagger/examples/README.rst | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Desktop/pecan-swagger/examples/README.md create mode 100644 Desktop/pecan-swagger/examples/README.rst diff --git a/Desktop/pecan-swagger/examples/README.md b/Desktop/pecan-swagger/examples/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/README.rst b/Desktop/pecan-swagger/examples/README.rst new file mode 100644 index 0000000..e69de29 -- Gitee From 59a9c70c1adb6ad14211c8ddd9db6af6308cb05d Mon Sep 17 00:00:00 2001 From: fengdelu <492607694@qq.com> Date: Wed, 30 Mar 2022 11:39:10 +0800 Subject: [PATCH 3/5] fix:tox error --- Desktop/pecan-swagger/.gitignore | 57 ++ Desktop/pecan-swagger/.idea/.gitignore | 8 + Desktop/pecan-swagger/.idea/.name | 1 + .../inspectionProfiles/profiles_settings.xml | 6 + Desktop/pecan-swagger/.idea/misc.xml | 4 + Desktop/pecan-swagger/.idea/modules.xml | 8 + .../.idea/pecan-swagger-master.iml | 12 + Desktop/pecan-swagger/.idea/vcs.xml | 6 + Desktop/pecan-swagger/LICENSE | 57 ++ Desktop/pecan-swagger/README.md | 19 - .../examples/{README.md => .gitignore} | 0 Desktop/pecan-swagger/examples/__init__.py | 0 .../industrial-ai-apiserver/.dockerignore | 10 + .../industrial-ai-apiserver/.drone.yml | 70 +++ .../industrial-ai-apiserver/.gitignore | 72 +++ .../industrial-ai-apiserver/.gitreview | 4 + .../industrial-ai-apiserver/.stestr.conf | 4 + .../industrial-ai-apiserver/Dockerfile | 16 + .../industrial-ai-apiserver/MANIFEST.in | 1 + .../industrial-ai-apiserver/README.md | 53 ++ .../industrial-ai-apiserver/README.rst | 0 .../industrial-ai-apiserver/__init__.py | 0 .../industrial-ai-apiserver/apidoc.json | 8 + .../industrial-ai-apiserver/config.py | 54 ++ .../industrial-ai-apiserver/datainit.py | 28 + .../deploy/default.conf | 44 ++ .../industrial-ai-apiserver/deploy/nginx.conf | 32 ++ .../industrial-ai-apiserver/industrialai.db | Bin 0 -> 12288 bytes .../industrialai/__init__.py | 0 .../industrialai/api/__init__.py | 0 .../industrialai/api/app.py | 14 + .../industrialai/api/controllers/__init__.py | 0 .../industrialai/api/controllers/root.py | 74 +++ .../api/controllers/v1/__init__.py | 0 .../api/controllers/v1/controller.py | 12 + .../industrialai/api/controllers/v1/role.py | 17 + .../industrialai/api/controllers/v1/user.py | 61 +++ .../industrialai/api/expose.py | 8 + .../industrialai/api/hooks.py | 8 + .../industrialai/db/__init__.py | 0 .../industrialai/db/api.py | 160 ++++++ .../industrialai/db/model.py | 33 ++ .../industrialai/hacking/__init__.py | 0 .../industrialai/hacking/checks.py | 354 +++++++++++++ .../industrialai/pkg/__init__.py | 0 .../industrialai/pkg/authentication.py | 40 ++ .../industrialai/pkg/const.py | 4 + .../industrialai/pkg/page.py | 12 + .../industrialai/tests/__init__.py | 17 + .../industrialai/tests/config.py | 25 + .../industrialai/tests/test_functional.py | 16 + .../industrialai/tests/test_units.py | 7 + .../lower-constraints.txt | 22 + .../industrial-ai-apiserver/requirements.txt | 7 + .../examples/industrial-ai-apiserver/run.py | 20 + .../industrial-ai-apiserver/setup.cfg | 6 + .../examples/industrial-ai-apiserver/setup.py | 22 + .../test-requirements.txt | 16 + .../examples/industrial-ai-apiserver/tox.ini | 53 ++ Desktop/pecan-swagger/examples/myapp.py | 47 ++ Desktop/pecan-swagger/myapp-doc.py | 8 + Desktop/pecan-swagger/pecan-swagger | 1 + .../pecan-swagger/pecan_swagger/__init__.py | 0 .../pecan-swagger/pecan_swagger/decorators.py | 38 ++ Desktop/pecan-swagger/pecan_swagger/g.py | 487 ++++++++++++++++++ Desktop/pecan-swagger/pecan_swagger/utils.py | 64 +++ Desktop/pecan-swagger/setup.cfg | 2 + Desktop/pecan-swagger/setup.py | 32 ++ Desktop/pecan-swagger/tests/__init__.py | 0 .../pecan-swagger/tests/resources/__init__.py | 0 .../tests/resources/example_app.py | 57 ++ .../tests/resources/example_wsme_app.py | 50 ++ Desktop/pecan-swagger/tests/test_utils.py | 216 ++++++++ Desktop/pecan-swagger/tox.ini | 12 + 74 files changed, 2577 insertions(+), 19 deletions(-) create mode 100644 Desktop/pecan-swagger/.gitignore create mode 100644 Desktop/pecan-swagger/.idea/.gitignore create mode 100644 Desktop/pecan-swagger/.idea/.name create mode 100644 Desktop/pecan-swagger/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 Desktop/pecan-swagger/.idea/misc.xml create mode 100644 Desktop/pecan-swagger/.idea/modules.xml create mode 100644 Desktop/pecan-swagger/.idea/pecan-swagger-master.iml create mode 100644 Desktop/pecan-swagger/.idea/vcs.xml create mode 100644 Desktop/pecan-swagger/LICENSE rename Desktop/pecan-swagger/examples/{README.md => .gitignore} (100%) create mode 100644 Desktop/pecan-swagger/examples/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/.dockerignore create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/.drone.yml create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/.gitignore create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/.gitreview create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/.stestr.conf create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/Dockerfile create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/MANIFEST.in create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.md create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.rst create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/apidoc.json create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/config.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/datainit.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/deploy/default.conf create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/deploy/nginx.conf create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai.db create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/app.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/controller.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/expose.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/hooks.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/api.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/model.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/hacking/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/hacking/checks.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/authentication.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/const.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/page.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/__init__.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/config.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/test_functional.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/test_units.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/lower-constraints.txt create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/requirements.txt create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/run.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/setup.cfg create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/setup.py create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/test-requirements.txt create mode 100644 Desktop/pecan-swagger/examples/industrial-ai-apiserver/tox.ini create mode 100644 Desktop/pecan-swagger/examples/myapp.py create mode 100644 Desktop/pecan-swagger/myapp-doc.py create mode 160000 Desktop/pecan-swagger/pecan-swagger create mode 100644 Desktop/pecan-swagger/pecan_swagger/__init__.py create mode 100644 Desktop/pecan-swagger/pecan_swagger/decorators.py create mode 100644 Desktop/pecan-swagger/pecan_swagger/g.py create mode 100644 Desktop/pecan-swagger/pecan_swagger/utils.py create mode 100644 Desktop/pecan-swagger/setup.cfg create mode 100644 Desktop/pecan-swagger/setup.py create mode 100644 Desktop/pecan-swagger/tests/__init__.py create mode 100644 Desktop/pecan-swagger/tests/resources/__init__.py create mode 100644 Desktop/pecan-swagger/tests/resources/example_app.py create mode 100644 Desktop/pecan-swagger/tests/resources/example_wsme_app.py create mode 100644 Desktop/pecan-swagger/tests/test_utils.py create mode 100644 Desktop/pecan-swagger/tox.ini diff --git a/Desktop/pecan-swagger/.gitignore b/Desktop/pecan-swagger/.gitignore new file mode 100644 index 0000000..6d7b9c1 --- /dev/null +++ b/Desktop/pecan-swagger/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# Build +example_wsme_app.json + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/Desktop/pecan-swagger/.idea/.gitignore b/Desktop/pecan-swagger/.idea/.gitignore new file mode 100644 index 0000000..619a014 --- /dev/null +++ b/Desktop/pecan-swagger/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/../../../../../../:\Users\fengdelu\Desktop\pecan-swagger-master\.idea/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/Desktop/pecan-swagger/.idea/.name b/Desktop/pecan-swagger/.idea/.name new file mode 100644 index 0000000..8fdfb52 --- /dev/null +++ b/Desktop/pecan-swagger/.idea/.name @@ -0,0 +1 @@ +model.py \ No newline at end of file diff --git a/Desktop/pecan-swagger/.idea/inspectionProfiles/profiles_settings.xml b/Desktop/pecan-swagger/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/Desktop/pecan-swagger/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/Desktop/pecan-swagger/.idea/misc.xml b/Desktop/pecan-swagger/.idea/misc.xml new file mode 100644 index 0000000..1f9f903 --- /dev/null +++ b/Desktop/pecan-swagger/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Desktop/pecan-swagger/.idea/modules.xml b/Desktop/pecan-swagger/.idea/modules.xml new file mode 100644 index 0000000..313b4d9 --- /dev/null +++ b/Desktop/pecan-swagger/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Desktop/pecan-swagger/.idea/pecan-swagger-master.iml b/Desktop/pecan-swagger/.idea/pecan-swagger-master.iml new file mode 100644 index 0000000..8b8c395 --- /dev/null +++ b/Desktop/pecan-swagger/.idea/pecan-swagger-master.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Desktop/pecan-swagger/.idea/vcs.xml b/Desktop/pecan-swagger/.idea/vcs.xml new file mode 100644 index 0000000..8bd613e --- /dev/null +++ b/Desktop/pecan-swagger/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Desktop/pecan-swagger/LICENSE b/Desktop/pecan-swagger/LICENSE new file mode 100644 index 0000000..c1c8608 --- /dev/null +++ b/Desktop/pecan-swagger/LICENSE @@ -0,0 +1,57 @@ +Copyright (c) 2015, michael mccune +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of pecan-swagger nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Includes some source code from the Pecan project, http://www.pecanpy.org/ + +Copyright (c) <2011>, Jonathan LaCour +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Desktop/pecan-swagger/README.md b/Desktop/pecan-swagger/README.md index 4409760..e69de29 100644 --- a/Desktop/pecan-swagger/README.md +++ b/Desktop/pecan-swagger/README.md @@ -1,19 +0,0 @@ -# Pecan Swagger - -## 1. 项目背景 - -Pecan-Swagger 用于为 Pecan Web 服务生成 Swagger 文档。该项目源自 ,目前原 repo 已无人维护,[原示例链接](examples/README.rst)。 - -Pecan 是 OpenStack 诸多模块中比较受欢迎的一种 Restful API 框架,新兴的项目大多采用 Pecan,比如 Magnum / CloudKitty 等等。Pecan-Swagger 依然是有很大价值的,因此我们再这个 repo 继续更新 Pecan-Swagger。 - -## 2. 需要预先掌握的技能 - -1. [Python 三日课程](https://gitee.com/wu-wen-xiang/training-python/blob/master/doc/learning-python-in-3-days.md) -2. [Git 基本使用](https://gitee.com/wu-wen-xiang/training-python/blob/master/doc/learning-python-in-3-days.md#31-%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6) -3. [Markdown 基本语法](https://markdown.com.cn/basic-syntax/) -4. [Gitee 合作](https://gitee.com/help/articles/4346) -5. [Swagger](https://editor.swagger.io/) - -## 3. 预期目标 - -1. 对接 Pecan 最近的 Stable 版本,提供 Pecan-Swagger 模块,为 Pecan 项目提供在线的 Swagger 文档。 diff --git a/Desktop/pecan-swagger/examples/README.md b/Desktop/pecan-swagger/examples/.gitignore similarity index 100% rename from Desktop/pecan-swagger/examples/README.md rename to Desktop/pecan-swagger/examples/.gitignore diff --git a/Desktop/pecan-swagger/examples/__init__.py b/Desktop/pecan-swagger/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.dockerignore b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.dockerignore new file mode 100644 index 0000000..ecf5595 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.dockerignore @@ -0,0 +1,10 @@ +.DS_Store +.dockerignore +.gitignore +.drone.yml +README.md +**/*.pyc +**/.venv +**/__pycache__ +**/industrialai.egg-info +**/build diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.drone.yml b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.drone.yml new file mode 100644 index 0000000..6b32928 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.drone.yml @@ -0,0 +1,70 @@ +kind: pipeline +type: exec +name: push-build + +clone: + depth: 5 + +platform: + os: linux + arch: amd64 + +node: + node: ci-buildx-2 + +steps: + - name: build-image + environment: + DOCKER_USER: + from_secret: DOCKER_USERNAME + DOCKER_PASS: + from_secret: DOCKER_PASSWORD + DOCKER_REGISTRY: docker.io + DOCKER_ORG: 99cloud + DOCKER_TAG: latest + commands: + - env + - mkdir -pv $HOMEPATH/.docker/buildx/instances + - cp /root/.docker/buildx/instances/cicd $HOMEPATH/.docker/buildx/instances/cicd + - docker buildx ls + - docker buildx use cicd + - echo -n $DOCKER_PASS | docker login $DOCKER_REGISTRY --username $DOCKER_USER --password-stdin + - docker buildx build -f Dockerfile --build-arg BRANCH=${DRONE_BRANCH} --build-arg REPO_URL=${DRONE_REPO} --build-arg COMMIT_REF=${DRONE_COMMIT_SHA} -t $DOCKER_REGISTRY/$DOCKER_ORG/industrial-ai-apiserver:$DOCKER_TAG --tag $DOCKER_REGISTRY/$DOCKER_ORG/industrial-ai-apiserver:$DOCKER_TAG-${DRONE_COMMIT_SHA:0:6} --platform=linux/arm64,linux/amd64 . --push + - name: clean + environment: + DOCKER_REGISTRY: docker.io + commands: + - docker logout $DOCKER_REGISTRY || true + when: + status: + - success + - failure + - name: predeploy + commands: + - cp -rf /root/.ssh ~/ + - name: deploy-to-aliyun + commands: + - ssh maqiao-cd "docker stop industrial-ai-apiserver || true" + - ssh maqiao-cd "docker rm industrial-ai-apiserver || true" + - ssh maqiao-cd "docker rmi 99cloud/industrial-ai-apiserver:latest || true" + - ssh maqiao-cd "docker pull 99cloud/industrial-ai-apiserver:latest" + - ssh maqiao-cd "docker run -d -p 8888:8080 --name="industrial-ai-apiserver" -v ~/data:/data -v /etc/localtime:/etc/localtime 99cloud/industrial-ai-apiserver:latest || true" + - ssh maqiao-cd "sleep 10" + - ssh maqiao-cd "docker exec industrial-ai-apiserver python datainit.py" + - name: wechat on success + commands: + - bash -x wechat-robot-ai-success.sh ${DRONE_COMMIT} ${DRONE_COMMIT_BRANCH} "${DRONE_COMMIT_AUTHOR_NAME}" ${DRONE_REPO_OWNER} ${DRONE_REPO_NAME} ${DRONE_COMMIT_AUTHOR_EMAIL} ${DRONE_BUILD_NUMBER} ${DRONE_BUILD_STARTED} + when: + status: + - success + - name: wechat on failure + commands: + - bash -x wechat-robot-ai-failure.sh ${DRONE_COMMIT} ${DRONE_COMMIT_BRANCH} "${DRONE_COMMIT_AUTHOR_NAME}" ${DRONE_REPO_OWNER} ${DRONE_REPO_NAME} ${DRONE_COMMIT_AUTHOR_EMAIL} ${DRONE_BUILD_NUMBER} ${DRONE_BUILD_STARTED} + when: + status: + - failure +trigger: + event: + - push + branch: + - master diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.gitignore b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.gitignore new file mode 100644 index 0000000..a101926 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.gitignore @@ -0,0 +1,72 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg +cloudkitty.egg-info +.idea/ +.python-version + +# Configuration file +etc/cloudkitty/cloudkitty.conf.sample +etc/cloudkitty/policy.yaml.sample + +# vscode +.vscode + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.stestr/ +.coverage +.cache +coverage.xml +cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +doc/build/ + +# Rope +.ropeproject/ + +# Others +*.sqlite +*.swp +*~ +*.swn +*.swo +*.DS_Store + +# tox -e docs +AUTHORS +ChangeLog + +releasenotes/build diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.gitreview b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.gitreview new file mode 100644 index 0000000..05d5ef7 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.sh.99cloud.net +port=29418 +project=fifth-element/industrial-ai-apiserver diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.stestr.conf b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.stestr.conf new file mode 100644 index 0000000..3c04f30 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/.stestr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_path=./industrialai/tests +top_dir=./ +group_regex=gabbi\.(suitemaker|driver)\.(test_[^_]+_[^_]+) diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/Dockerfile b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/Dockerfile new file mode 100644 index 0000000..68e04fa --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/Dockerfile @@ -0,0 +1,16 @@ +FROM docker.io/library/python:3.9.10-alpine + +LABEL purpose="Industrial AI API Server" + +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories +RUN apk update && apk add sqlite && apk add build-base +RUN mkdir -p /home/www/industrialai +WORKDIR /home/www/industrialai +COPY . /home/www/industrialai + +RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r /home/www/industrialai/requirements.txt +RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gunicorn==20.0.0 +RUN pip install /home/www/industrialai + +ENTRYPOINT [ "gunicorn_pecan" ] +CMD ["config.py"] diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/MANIFEST.in b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/MANIFEST.in new file mode 100644 index 0000000..c922f11 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/MANIFEST.in @@ -0,0 +1 @@ +recursive-include public * diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.md b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.md new file mode 100644 index 0000000..837b723 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.md @@ -0,0 +1,53 @@ +# industrial-ai-apiserver + +数据字典 & API: + +## 本地调试 + +```bash +cd industrial-ai-apiserver + +# 创建 python 虚拟环境 +python -m virtualenv .venv + +# 进入 python 虚拟环境 +.venv\Scripts\activate + +# 安装依赖 +pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt + +# 清理原数据库 +rm -rf /tmp/industrialai.db + +# 初始化数据库 +python datainit.py + +# 启动 web 服务 +python run.py +# python run.py 0.0.0.0 8080 + +# 尝试访问 +curl http://localhost:8080/v1/users/ +``` + +## 测试 + +```bash +python setup.py test -q + +stestr run + +tox +``` + +## 容器镜像制作和部署 + +```bash +docker build -t industrialai . + +docker stop industrialai; docker rm industrialai + +docker run -d --name industrialai -p 8888:8080 industrialai + +curl http://localhost:8888/users/ +``` diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.rst b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.rst new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/apidoc.json b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/apidoc.json new file mode 100644 index 0000000..37ac42c --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/apidoc.json @@ -0,0 +1,8 @@ +{ + "name": "Industrial AI Platform", + "version": "0.1.0", + "description": "apiDoc basic example", + "title": "Custom apiDoc browser title", + "url" : "http://106.14.36.119/api", + "sampleUrl" : false +} \ No newline at end of file diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/config.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/config.py new file mode 100644 index 0000000..6f9988d --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/config.py @@ -0,0 +1,54 @@ +# Server Specific Configurations +server = { + 'port': '8080', + 'host': '0.0.0.0' +} + +# Pecan Application Configurations +app = { + 'root': 'industrialai.api.controllers.root.RootController', + 'modules': ['industrialai.api'], + 'static_root': '%(confdir)s/public', + 'template_path': '%(confdir)s/industrialai/templates', + 'debug': True, + 'errors': { + 404: '/error/404', + '__force_dict__': True + } +} + +logging = { + 'root': {'level': 'INFO', 'handlers': ['console']}, + 'loggers': { + 'industrialai': {'level': 'DEBUG', 'handlers': ['console'], 'propagate': False}, + 'pecan': {'level': 'DEBUG', 'handlers': ['console'], 'propagate': False}, + 'py.warnings': {'handlers': ['console']}, + '__force_dict__': True + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'color' + } + }, + 'formatters': { + 'simple': { + 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]' + '[%(threadName)s] %(message)s') + }, + 'color': { + '()': 'pecan.log.ColorFormatter', + 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]' + '[%(threadName)s] %(message)s'), + '__force_dict__': True + } + } +} + +# Custom Configurations must be in Python dictionary format:: +# +# foo = {'bar':'baz'} +# +# All configurations are accessible at:: +# pecan.conf diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/datainit.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/datainit.py new file mode 100644 index 0000000..e866187 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/datainit.py @@ -0,0 +1,28 @@ +from industrialai.db.model import Base, Role, User +from sqlalchemy.orm import sessionmaker +import os +from sqlalchemy import create_engine + +def init_db(): + DB_URL = os.getenv('db_url') or \ + "sqlite:///C:\\Users\\fengdelu\\Desktop\\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) + Session = sessionmaker(bind=engine) + session = Session() + + session.add_all([ + Role(title="expert"), + Role(title="government"), + Role(title="enterpriseAdmin"), + Role(title="member"), + Role(title="admin"), + User(account="admin", pwd="123456", roleId=3), + User(account="member", pwd="123456", roleId=4), + User(account="root", pwd="123456", roleId=5) + ]) + session.commit() + + +init_db() diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/deploy/default.conf b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/deploy/default.conf new file mode 100644 index 0000000..a903df6 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/deploy/default.conf @@ -0,0 +1,44 @@ +server { + listen 80; + listen [::]:80; + server_name localhost; + + #access_log /var/log/nginx/host.access.log main; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} +} diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/deploy/nginx.conf b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/deploy/nginx.conf new file mode 100644 index 0000000..5e076aa --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/deploy/nginx.conf @@ -0,0 +1,32 @@ + +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai.db b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai.db new file mode 100644 index 0000000000000000000000000000000000000000..197e6ae3351c6519007cc9a56f7b5be6e4c632ac GIT binary patch literal 12288 zcmeI&!EVzq7zc1CZUrqGK}c2A1CrUP6>Msgc3UeBgyM~gu7#|CCJrcT4U5zyRq6s) z5Eov6x8Z~kcOHT_;EtWPq8*#KBJ{W9*!t|xPJTCAIeOd;q+l=OB=jY7$TeaZ2+tF`J4+SK9WtJK${K-s^IvBdnay1tZq!4fp}~Sg$`|y(iu7ZI;hP zekj;e*K0p;y|QChbj%+P<5?unh9_@Ee}qos=y!T;o*vD@6Se1Z(ONTIbQ;f+;mX`< z8r@9dvFMC8m+z*xpGsfOrmK-*B77-csI&8Z^y${A|5csmh^)aQR{^@;V>`luQd2tWV= z5P$##AOHafKmY;|fB*#kF0eF2Q634P;JN3rSWb8}*i+$Fv+V)n}*|Hm~ z)@a$aR=v4xS9j|>&6cS$ON-3;Nq#*ybAE24zUwp%m0MioruPBssr39*yS``t0!E;@ AdjJ3c literal 0 HcmV?d00001 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/app.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/app.py new file mode 100644 index 0000000..f03231a --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/app.py @@ -0,0 +1,14 @@ +from industrialai.api import hooks +from pecan import make_app + + +def setup_app(config): + app_conf = dict(config.app) + app_hooks = [hooks.DBHook()] + app = make_app( + app_conf.pop('root'), + logging=getattr(config, 'logging', {}), + hooks=app_hooks, + **app_conf + ) + return app diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py new file mode 100644 index 0000000..fd82b3a --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py @@ -0,0 +1,74 @@ +from industrialai.api.controllers.v1 import controller as v1_controller +from industrialai.api.expose import expose as wsexpose +from industrialai.db import api +from industrialai.db.model import User +from industrialai.pkg.authentication import JWT +import logging +from pecan import abort +from pecan import expose +from pecan import request +from wsme import types as wtypes +logger = logging.getLogger(__name__) + + +class logForm: + account = wtypes.text + pwd = wtypes.text + + +class checkForm: + id = int + pwd = wtypes.text + + +class registryForm: + account = wtypes.text + pwd = wtypes.text + phone = wtypes.text + mail = wtypes.text + name = wtypes.text + number = wtypes.text + + +class RootController(object): + + @expose('json') + def index(self): + return None + + @wsexpose(wtypes.text, body=logForm) + def signin(self, logForm): + account = logForm.account + pwd = logForm.pwd + user = api.get_session().query(User). \ + filter_by(account=account, pwd=pwd).first() + if user is None: + abort(401) + token = JWT.createToken(user.id, user.roleId) + return { + "token": token + } + + @wsexpose(wtypes.text, body=checkForm) + def pwdcheck(self, checkForm): + try: + api.get_session().query(User). \ + filter_by(id=checkForm.id, pwd=checkForm.pwd).first() + except Exception: + return "Failure" + return "Success" + + @expose('json') + def myself(self): + token = request.cookies.get("token") + message = JWT.deToken(token) + if message is None: + abort(401) + else: + uId = message.get("userId") + user = api.get_session().query(User). \ + filter_by(id=uId).first() + user.pwd = None + return user + + v1 = v1_controller.v1Controller() diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/controller.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/controller.py new file mode 100644 index 0000000..ad47574 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/controller.py @@ -0,0 +1,12 @@ +from industrialai.api.controllers.v1 import role as v1_role +from industrialai.api.controllers.v1 import user as v1_user +from pecan import expose + + +class v1Controller(object): + users = v1_user.UserController() + roles = v1_role.RoleController() + + @expose('json') + def index(self): + return None diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py new file mode 100644 index 0000000..34bb33b --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py @@ -0,0 +1,17 @@ +from industrialai.pkg.page import get_page +from pecan import expose +from pecan import request +from pecan.rest import RestController + + +class RoleController(RestController): + + @expose('json') + def get_all(self): + page_num, page_size = get_page(request.body) + db_conn = request.db_conn + roles, num = db_conn.list_roles(page_num, page_size) + return { + "data": roles, + "pages": num + } diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py new file mode 100644 index 0000000..3c91327 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py @@ -0,0 +1,61 @@ +from industrialai.pkg.authentication import JWT +from industrialai.pkg.page import get_page +from pecan import abort +from pecan import expose +from pecan import request +from pecan.rest import RestController + + +class UserController(RestController): + + @expose("json") + def get(self, **kwargs): + page_num, page_size = get_page(request.body) + validKeys = ["enterpriseId", "name", "account", "roleId", "status"] + kwargs = {k: v for k, v in kwargs.items() if k in validKeys} + token = request.cookies.get("token") + message = JWT.deToken(token) + if message is None: + abort(401) + else: + eId = message.get("enterpriseId") + rId = message.get("roleId") + if rId != 5: + kwargs["enterpriseId"] = eId + db_conn = request.db_conn + users, pages = db_conn.list_users(page_num, page_size, **kwargs) + return { + "data": users, + "pages": pages + } + + @expose("json") + def get_one(self, user_id): + db_conn = request.db_conn + user = db_conn.get_user(user_id) + user.pwd = None + return user + + @expose("json") + def post(self, user): + db_conn = request.db_conn + db_conn.add_user(user) + return "Success" + + @expose("json") + def put(self, user): + token = request.cookies.get("token") + message = JWT.deToken(token) + if message is None: + abort(401) + else: + uId = message.get("userId") + rId = message.get("roleId") + if uId != int(user.get("id")) and rId != 5: + abort(401) + elif uId == int(user.get("id")): + user["roleId"] = None + user["phone"] = None + db_conn = request.db_conn + db_conn.update_user(user) + return "Success" diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/expose.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/expose.py new file mode 100644 index 0000000..d847657 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/expose.py @@ -0,0 +1,8 @@ +import wsmeext.pecan as wsme_pecan + + +# 让API返回JSON格式的数据 +def expose(*args, **kwargs): + if 'rest_content_types' not in kwargs: + kwargs['rest_content_types'] = ('json',) + return wsme_pecan.wsexpose(*args, **kwargs) diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/hooks.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/hooks.py new file mode 100644 index 0000000..3395026 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/api/hooks.py @@ -0,0 +1,8 @@ +from industrialai.db import api as db_api +from pecan import hooks + + +class DBHook(hooks.PecanHook): + # Create a db connection instance. + def before(self, state): + state.request.db_conn = db_api.Connection() diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/api.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/api.py new file mode 100644 index 0000000..db8b8a2 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/api.py @@ -0,0 +1,160 @@ +from industrialai.db import model as db_model +import logging +import math +import os +from pecan import abort +from sqlalchemy import create_engine, func +from sqlalchemy.orm import exc + +from sqlalchemy.orm import sessionmaker + +logger = logging.getLogger(__name__) +Domain = "sqlalchemy" + + +_ENGINE = None +_SESSION_MAKER = None + + +def get_engine(): + global _ENGINE + if _ENGINE is not None: + return _ENGINE + DB_URL = ( + os.getenv("db_url") or + "sqlite:///D:\\Users\\fengdelu\\Desktop\\pecan-swagger\\examples\\" + "industrial-ai-apiserver\\industrialai.db?check_same_thread=False" + ) + _ENGINE = create_engine(DB_URL, echo=True) + return _ENGINE + + +def get_session_maker(engine): + global _SESSION_MAKER + if _SESSION_MAKER is not None: + return _SESSION_MAKER + _SESSION_MAKER = sessionmaker(bind=engine) + return _SESSION_MAKER + + +def get_session(): + engine = get_engine() + maker = get_session_maker(engine) + session = maker() + return session + + +class Connection(object): + def __init__(self): + pass + + def get_user(self, user_id): + user = None + query = get_session().query(db_model.User).filter_by(id=user_id) + try: + user = query.one() + except exc.NoResultFound: + logger.error("query by enterpriseId not found ...") + abort(404) + return user + + def list_users(self, page_num, page_size, **kwargs): + users = [] + # query = get_session().query(db_model.User.account, + # db_model.User.name, db_model.User.roleId, + # db_model.User.status, db_model.User.create_at, db_model.User.source) + query = get_session().query(db_model.User) + for k, v in kwargs.items(): + if v: + if k == "name": + query = query.filter( + db_model.User.name.like("%{keyword}%". + format(keyword=v)) + ) + elif k == "account": + query = query.filter( + db_model.User.account.like("%{keyword}%". + format(keyword=v)) + ) + else: + query = query.filter_by(**{k: v}) + try: + users = query.slice((page_num-1)*page_size, page_size).all() + num = query.count() + pages = math.ceil(num/page_size) + for item in users: + item.pwd = None + except exc.NoResultFound: + logger.error("query all user occur error ...") + abort(404) + return users, pages + + def update_user(self, user): + id = user.get("id") + logger.info("user.id: %s" % (id)) + try: + session = get_session() + session.query(db_model.User).filter_by(id=id).one() + for k, v in user.items(): + if v is not None: + session.query(db_model.User).filter_by(id=id). \ + update({k: v}) + session.flush() + session.commit() + except exc.NoResultFound: + logger.error("update user occur error ...") + abort(404) + + return user + + def delete_user(self, user_id): + logger.info("user.id: %s" % (user_id)) + try: + session = get_session() + user = session.query(db_model.User).filter_by(id=user_id).first() + session.delete(user) + session.flush() + session.commit() + except exc.NoResultFound: + logger.error("delete user occur error ...") + abort(404) + + def add_user(self, user): + db_user = db_model.User( + name=user.get("name"), + account=user.get("account"), + pwd=user.get("pwd"), + phone=user.get("phone"), + roleId=user.get("roleId"), + enterpriseId=user.get("enterpriseId"), + status=user.get("status"), + ) + try: + session = get_session() + if not user.get("groupId") is None: + group = ( + session.query(db_model.Group) + .filter_by(id=user.get("groupId")) + .one() + ) + db_user.group = group + session.add(db_user) + session.flush() + session.commit() + except exc.NoResultFound: + logger.error("add user occour error ...") + abort(404) + # return session.query(db_model.Group).options( + # joinedload(db_model.Group.users)).all() + + def list_roles(self, page_num, page_size): + roles = [] + query = get_session().query(db_model.Role) + try: + roles = query.slice((page_num-1)*page_size, page_size).all() + num = get_session().query(func.count(db_model.Role.id)).scalar() + pages = math.ceil(num/page_size) + except exc.NoResultFound: + logger.error("query all roles occur error ...") + abort(404) + return roles, pages diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/model.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/model.py new file mode 100644 index 0000000..3c7492c --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/model.py @@ -0,0 +1,33 @@ +from datetime import datetime +from sqlalchemy import Column +from sqlalchemy import DateTime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import ForeignKey +from sqlalchemy import Integer +from sqlalchemy import String + +Base = declarative_base() + + +def init_db(_ENGINE): + Base.metadata.create_all(_ENGINE) + + +class User(Base): + __tablename__ = 'user' + id = Column(Integer, primary_key=True) + name = Column(String(32)) + account = Column(String(32)) + pwd = Column(String(32)) + phone = Column(String(12)) + number = Column(String(19)) + source = Column(String(10), default="platform") + roleId = Column(Integer, ForeignKey("role.id")) + status = Column(Integer) + create_at = Column(DateTime, default=datetime.now) + + +class Role(Base): + __tablename__ = 'role' + id = Column(Integer, primary_key=True) + title = Column(String(32)) diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/hacking/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/hacking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/hacking/checks.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/hacking/checks.py new file mode 100644 index 0000000..84207e1 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/hacking/checks.py @@ -0,0 +1,354 @@ +# Copyright (c) 2016, GohighSec +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ast +import re + +from hacking import core + + +""" +Guidelines for writing new hacking checks + + - Use only for Cloudkitty specific tests. OpenStack general tests + should be submitted to the common 'hacking' module. + - Pick numbers in the range C3xx. Find the current test with + the highest allocated number and then pick the next value. + - Keep the test method code in the source file ordered based + on the C3xx value. + - List the new rule in the top level HACKING.rst file + - Add test cases for each new rule to cloudkitty/tests/test_hacking.py + +""" + +UNDERSCORE_IMPORT_FILES = [] + +_all_log_levels = {'debug', 'error', 'info', 'warning', + 'critical', 'exception'} +# Since _Lx have been removed, we just need to check _() +translated_logs = re.compile( + r"(.)*LOG\.(%(level)s)\(\s*_\(" % {'level': '|'.join(_all_log_levels)}) + +string_translation = re.compile(r"[^_]*_\(\s*('|\")") +underscore_import_check = re.compile(r"(.)*import _$") +underscore_import_check_multi = re.compile(r"(.)*import (.)*_, (.)*") +# We need this for cases where they have created their own _ function. +custom_underscore_check = re.compile(r"(.)*_\s*=\s*(.)*") +oslo_namespace_imports = re.compile(r"from[\s]*oslo[.](.*)") +dict_constructor_with_list_copy_re = re.compile(r".*\bdict\((\[)?(\(|\[)") +assert_no_xrange_re = re.compile(r"\s*xrange\s*\(") +assert_True = re.compile(r".*assertEqual\(True, .*\)") +assert_None = re.compile(r".*assertEqual\(None, .*\)") +no_log_warn = re.compile(r".*LOG.warn\(.*\)") + + +class BaseASTChecker(ast.NodeVisitor): + """Provides a simple framework for writing AST-based checks. + + Subclasses should implement visit_* methods like any other AST visitor + implementation. When they detect an error for a particular node the + method should call ``self.add_error(offending_node)``. Details about + where in the code the error occurred will be pulled from the node + object. + + Subclasses should also provide a class variable named CHECK_DESC to + be used for the human readable error message. + + """ + + CHECK_DESC = 'No check message specified' + + def __init__(self, tree, filename): + """This object is created automatically by pep8. + + :param tree: an AST tree + :param filename: name of the file being analyzed + (ignored by our checks) + """ + self._tree = tree + self._errors = [] + + def run(self): + """Called automatically by pep8.""" + self.visit(self._tree) + return self._errors + + def add_error(self, node, message=None): + """Add an error caused by a node to the list of errors for pep8.""" + message = message or self.CHECK_DESC + error = (node.lineno, node.col_offset, message, self.__class__) + self._errors.append(error) + + def _check_call_names(self, call_node, names): + if isinstance(call_node, ast.Call): + if isinstance(call_node.func, ast.Name): + if call_node.func.id in names: + return True + return False + + +@core.flake8ext +def no_translate_logs(logical_line, filename): + """Check for 'LOG.*(_(' + + Starting with the Pike series, OpenStack no longer supports log + translation. + + * This check assumes that 'LOG' is a logger. + * Use filename so we can start enforcing this in specific folders instead + of needing to do so all at once. + + C313 + """ + if translated_logs.match(logical_line): + yield(0, "C313 Don't translate logs") + + +class CheckLoggingFormatArgs(BaseASTChecker): + """Check for improper use of logging format arguments. + + LOG.debug("Volume %s caught fire and is at %d degrees C and climbing.", + ('volume1', 500)) + + The format arguments should not be a tuple as it is easy to miss. + + """ + + name = "check_logging_format_args" + version = "1.0" + + CHECK_DESC = 'C310 Log method arguments should not be a tuple.' + LOG_METHODS = [ + 'debug', 'info', + 'warn', 'warning', + 'error', 'exception', + 'critical', 'fatal', + 'trace', 'log' + ] + + def _find_name(self, node): + """Return the fully qualified name or a Name or Attribute.""" + if isinstance(node, ast.Name): + return node.id + elif (isinstance(node, ast.Attribute) + and isinstance(node.value, (ast.Name, ast.Attribute))): + method_name = node.attr + obj_name = self._find_name(node.value) + if obj_name is None: + return None + return obj_name + '.' + method_name + elif isinstance(node, str): + return node + else: # could be Subscript, Call or many more + return None + + def visit_Call(self, node): + """Look for the 'LOG.*' calls.""" + # extract the obj_name and method_name + if isinstance(node.func, ast.Attribute): + obj_name = self._find_name(node.func.value) + if isinstance(node.func.value, ast.Name): + method_name = node.func.attr + elif isinstance(node.func.value, ast.Attribute): + obj_name = self._find_name(node.func.value) + method_name = node.func.attr + else: # could be Subscript, Call or many more + return super(CheckLoggingFormatArgs, self).generic_visit(node) + + # obj must be a logger instance and method must be a log helper + if (obj_name != 'LOG' + or method_name not in self.LOG_METHODS): + return super(CheckLoggingFormatArgs, self).generic_visit(node) + + # the call must have arguments + if not len(node.args): + return super(CheckLoggingFormatArgs, self).generic_visit(node) + + # any argument should not be a tuple + for arg in node.args: + if isinstance(arg, ast.Tuple): + self.add_error(arg) + + return super(CheckLoggingFormatArgs, self).generic_visit(node) + + +@core.flake8ext +def check_explicit_underscore_import(logical_line, filename): + """Check for explicit import of the _ function + + We need to ensure that any files that are using the _() function + to translate logs are explicitly importing the _ function. We + can't trust unit test to catch whether the import has been + added so we need to check for it here. + """ + + # Build a list of the files that have _ imported. No further + # checking needed once it is found. + if filename in UNDERSCORE_IMPORT_FILES: + pass + elif (underscore_import_check.match(logical_line) or + underscore_import_check_multi.match(logical_line) or + custom_underscore_check.match(logical_line)): + UNDERSCORE_IMPORT_FILES.append(filename) + elif string_translation.match(logical_line): + yield(0, "C321: Found use of _() without explicit import of _ !") + + +class CheckForStrUnicodeExc(BaseASTChecker): + """Checks for the use of str() or unicode() on an exception. + + This currently only handles the case where str() or unicode() + is used in the scope of an exception handler. If the exception + is passed into a function, returned from an assertRaises, or + used on an exception created in the same scope, this does not + catch it. + """ + + name = "check_for_str_unicode_exc" + version = "1.0" + + CHECK_DESC = ('C314 str() and unicode() cannot be used on an ' + 'exception. Remove it.') + + def __init__(self, tree, filename): + super(CheckForStrUnicodeExc, self).__init__(tree, filename) + self.name = [] + self.already_checked = [] + + # Python 2 + def visit_TryExcept(self, node): + for handler in node.handlers: + if handler.name: + self.name.append(handler.name.id) + super(CheckForStrUnicodeExc, self).generic_visit(node) + self.name = self.name[:-1] + else: + super(CheckForStrUnicodeExc, self).generic_visit(node) + + # Python 3 + def visit_ExceptHandler(self, node): + if node.name: + self.name.append(node.name) + super(CheckForStrUnicodeExc, self).generic_visit(node) + self.name = self.name[:-1] + else: + super(CheckForStrUnicodeExc, self).generic_visit(node) + + def visit_Call(self, node): + if self._check_call_names(node, ['str', 'unicode']): + if node not in self.already_checked: + self.already_checked.append(node) + if isinstance(node.args[0], ast.Name): + if node.args[0].id in self.name: + self.add_error(node.args[0]) + super(CheckForStrUnicodeExc, self).generic_visit(node) + + +class CheckForTransAdd(BaseASTChecker): + """Checks for the use of concatenation on a translated string. + + Translations should not be concatenated with other strings, but + should instead include the string being added to the translated + string to give the translators the most information. + """ + + name = "check_for_trans_add" + version = "1.0" + + CHECK_DESC = ('C315 Translated messages cannot be concatenated. ' + 'String should be included in translated message.') + + TRANS_FUNC = ['_'] + + def visit_BinOp(self, node): + if isinstance(node.op, ast.Add): + if self._check_call_names(node.left, self.TRANS_FUNC): + self.add_error(node.left) + elif self._check_call_names(node.right, self.TRANS_FUNC): + self.add_error(node.right) + super(CheckForTransAdd, self).generic_visit(node) + + +@core.flake8ext +def check_oslo_namespace_imports(logical_line, noqa): + """'oslo_' should be used instead of 'oslo.' + + C317 + """ + if noqa: + return + if re.match(oslo_namespace_imports, logical_line): + msg = ("C317: '%s' must be used instead of '%s'.") % ( + logical_line.replace('oslo.', 'oslo_'), + logical_line) + yield(0, msg) + + +@core.flake8ext +def dict_constructor_with_list_copy(logical_line): + """Use a dict comprehension instead of a dict constructor + + C318 + """ + msg = ("C318: Must use a dict comprehension instead of a dict constructor" + " with a sequence of key-value pairs." + ) + if dict_constructor_with_list_copy_re.match(logical_line): + yield (0, msg) + + +@core.flake8ext +def no_xrange(logical_line): + """Ensure to not use xrange() + + C319 + """ + if assert_no_xrange_re.match(logical_line): + yield(0, "C319: Do not use xrange().") + + +@core.flake8ext +def validate_assertTrue(logical_line): + """Use assertTrue instead of assertEqual + + C312 + """ + if re.match(assert_True, logical_line): + msg = ("C312: Unit tests should use assertTrue(value) instead" + " of using assertEqual(True, value).") + yield(0, msg) + + +@core.flake8ext +def validate_assertIsNone(logical_line): + """Use assertIsNone instead of assertEqual + + C311 + """ + if re.match(assert_None, logical_line): + msg = ("C311: Unit tests should use assertIsNone(value) instead" + " of using assertEqual(None, value).") + yield(0, msg) + + +@core.flake8ext +def no_log_warn_check(logical_line): + """Disallow 'LOG.warn' + + C320 + """ + msg = ("C320: LOG.warn is deprecated, please use LOG.warning!") + if re.match(no_log_warn, logical_line): + yield(0, msg) diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/authentication.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/authentication.py new file mode 100644 index 0000000..c9ceb28 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/authentication.py @@ -0,0 +1,40 @@ +from datetime import datetime +from datetime import timedelta +from industrialai.pkg import const +import jwt +import os + +const.jwtKey = "jwt_key" +const.defaultKey = "SECRET_KEY" + + +class JWT(object): + def createToken(userId, roleId): + payload = { + 'exp': int((datetime.now() + timedelta(minutes=30)).timestamp()), + 'userId': userId, + 'roleId': roleId + } + key = os.getenv(const.jwtKey) or const.defaultKey + encodeJwt = jwt.encode(payload, key, algorithm='HS256') + + return encodeJwt + + def deToken(token): + data = None + secret = os.getenv(const.jwtKey) or const.defaultKey + try: + data = jwt.decode(token, secret, algorithms=['HS256']) + except Exception as e: + print(e) + # if not data is None: + # session = Session() + # if session.query(exists().where(Token.token==token)).scalar(): + # return None + return data + + def verifyToken(token): + data = JWT.deToken(token) + if data is None: + return False + return True diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/const.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/const.py new file mode 100644 index 0000000..74be40e --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/const.py @@ -0,0 +1,4 @@ +def __setattr__(self, name, value): + if name in self.__dict__: + raise self.ConstError(f"Can't rebind const ({name})") + self.__dict__[name] = value diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/page.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/page.py new file mode 100644 index 0000000..a12ca5f --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/pkg/page.py @@ -0,0 +1,12 @@ +import json + + +def get_page(page): + try: + page_str = str(page, 'utf-8') + page_json = json.loads(page_str) + page_num = page_json.get("page_num") or 1 + page_size = page_json.get("page_size") or 10 + except Exception: + return 1, 10 + return page_num, page_size diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/__init__.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/__init__.py new file mode 100644 index 0000000..bcdd1d7 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/__init__.py @@ -0,0 +1,17 @@ +import os +from pecan import set_config +from pecan.testing import load_test_app +from unittest import TestCase + +__all__ = ['FunctionalTest'] + + +class FunctionalTest(TestCase): + def setUp(self): + self.app = load_test_app(os.path.join( + os.path.dirname(__file__), + 'config.py' + )) + + def tearDown(self): + set_config({}, overwrite=True) diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/config.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/config.py new file mode 100644 index 0000000..6a28f1a --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/config.py @@ -0,0 +1,25 @@ +# Server Specific Configurations +server = { + 'port': '8080', + 'host': '0.0.0.0' +} + +# Pecan Application Configurations +app = { + 'root': 'industrialai.api.controllers.root.RootController', + 'modules': ['industrialai.api'], + 'static_root': '%(confdir)s/../../public', + 'template_path': '%(confdir)s/../templates', + 'debug': True, + 'errors': { + '404': '/error/404', + '__force_dict__': True + } +} + +# Custom Configurations must be in Python dictionary format:: +# +# foo = {'bar':'baz'} +# +# All configurations are accessible at:: +# pecan.conf diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/test_functional.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/test_functional.py new file mode 100644 index 0000000..d561f07 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/test_functional.py @@ -0,0 +1,16 @@ +from industrialai.tests import FunctionalTest + + +class TestRootController(FunctionalTest): + + def test_get(self): + response = self.app.get('/') + assert response.status_int == 200 + + def test_search(self): + response = self.app.post('/', params={'q': 'RestController'}) + assert response.status_int == 200 + + def test_get_not_found(self): + response = self.app.get('/a/bogus/url', expect_errors=True) + assert response.status_int == 404 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/test_units.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/test_units.py new file mode 100644 index 0000000..573fb68 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/tests/test_units.py @@ -0,0 +1,7 @@ +from unittest import TestCase + + +class TestUnits(TestCase): + + def test_units(self): + assert 5 * 5 == 25 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/lower-constraints.txt b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/lower-constraints.txt new file mode 100644 index 0000000..63353dc --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/lower-constraints.txt @@ -0,0 +1,22 @@ +# requirements +pecan==1.4.1 # BSD +WSME==0.11.0 # MIT +PyJWT==2.3.0 # MIT +SQLAlchemy==1.4.31 # MIT + +# test-requirements +coverage==5.3 # Apache-2.0 +kombu==5.0.2 # BSD +ddt==1.4.1 # MIT +gabbi==2.0.4 # Apache-2.0 +testscenarios==0.5.0 # Apache-2.0/BSD +stestr==3.0.1 # Apache-2.0 +sphinx==3.3.1 # BSD +openstackdocstheme==2.2.6 +oslotest==4.4.1 # Apache-2.0 +sphinxcontrib-pecanwsme==0.10.0 # Apache-2.0 +reno==3.2.0 +sphinxcontrib-httpdomain==1.7.0 # Apache-2.0 +doc8==0.8.1 # Apache-2.0 +Pygments==2.7.2 # BSD +os-api-ref==2.1.0 # Apache-2.0 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/requirements.txt b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/requirements.txt new file mode 100644 index 0000000..3be4005 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/requirements.txt @@ -0,0 +1,7 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. +pecan>=1.4.1 # BSD +PyJWT>=2.3.0 # MIT +WSME>=0.11.0 # MIT +SQLAlchemy>=1.4.31 # MIT diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/run.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/run.py new file mode 100644 index 0000000..292365c --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/run.py @@ -0,0 +1,20 @@ +import os +import sys +from pecan.deploy import deploy +from wsgiref import simple_server + +conf = os.path.join(os.path.dirname(__file__), 'config.py') +app = deploy(conf) + +if __name__ == '__main__': + host = '0.0.0.0' + port = 8080 + + if len(sys.argv) > 1: + host = sys.argv[1] + + if len(sys.argv) > 2: + port = int(sys.argv[2]) + + serve = simple_server.make_server(host, port, app) + serve.serve_forever() diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/setup.cfg b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/setup.cfg new file mode 100644 index 0000000..ab44f5c --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/setup.cfg @@ -0,0 +1,6 @@ +[nosetests] +match=^test +where=industrialai +nocapture=1 +cover-package=industrialai +cover-erase=1 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/setup.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/setup.py new file mode 100644 index 0000000..1c8bb6b --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/setup.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +try: + from setuptools import setup, find_packages +except ImportError: + from ez_setup import use_setuptools + use_setuptools() + from setuptools import setup, find_packages + +setup( + name='industrialai', + version='0.1.0', + description='Industrial AI API Server', + author='', + author_email='', + install_requires=[ + "pecan", + ], + test_suite='industrialai', + zip_safe=False, + include_package_data=True, + packages=find_packages(exclude=['ez_setup']) +) diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/test-requirements.txt b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/test-requirements.txt new file mode 100644 index 0000000..dcb19c6 --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/test-requirements.txt @@ -0,0 +1,16 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +# hacking should be first +hacking>=3.0.1,<3.1.0 # Apache-2.0 + +coverage>=5.3 # Apache-2.0 +kombu>=5.0.2 # BSD +ddt>=1.4.1 # MIT +gabbi>=2.0.4 # Apache-2.0 +testscenarios>=0.5.0 # Apache-2.0/BSD +stestr>=3.0.1 # Apache-2.0 +oslotest>=4.4.1 # Apache-2.0 +doc8>=0.8.1 # Apache-2.0 +bandit>=1.6.0 # Apache-2.0 diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/tox.ini b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/tox.ini new file mode 100644 index 0000000..deff8bc --- /dev/null +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/tox.ini @@ -0,0 +1,53 @@ +[tox] +envlist = py3,pep8 + +[testenv] +basepython = python3 +allowlist_externals = rm +setenv = VIRTUAL_ENV={envdir} + PYTHONWARNINGS=default::DeprecationWarning +usedevelop = True + +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands = + /usr/bin/find . -type f -name "*.py[co]" -delete + rm -f /tmp/industrialai.db + python datainit.py + stestr run {posargs} + +[testenv:debug] +commands = oslo_debug_helper {posargs} + +[testenv:pep8] +commands = + flake8 {posargs} industrialai + +[testenv:venv] +commands = {posargs} + +[flake8] +filename = *.py,app.wsgi +exclude = .git,.venv,.tox,dist,doc,*egg,build,.ropeproject,releasenotes + +[flake8:local-plugins] +extension = + C310 = checks:CheckLoggingFormatArgs + C311 = checks:validate_assertIsNone + C312 = checks:validate_assertTrue + C313 = checks:no_translate_logs + C314 = checks:CheckForStrUnicodeExc + C315 = checks:CheckForTransAdd + C317 = checks:check_oslo_namespace_imports + C318 = checks:dict_constructor_with_list_copy + C319 = checks:no_xrange + C320 = checks:no_log_warn_check + C321 = checks:check_explicit_underscore_import +paths = ./industrialai/hacking + +[testenv:lower-constraints] +deps = + -c{toxinidir}/lower-constraints.txt + -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt diff --git a/Desktop/pecan-swagger/examples/myapp.py b/Desktop/pecan-swagger/examples/myapp.py new file mode 100644 index 0000000..88dcc0b --- /dev/null +++ b/Desktop/pecan-swagger/examples/myapp.py @@ -0,0 +1,47 @@ +import pecan +from pecan_swagger import decorators as swagger + + +@swagger.path('messages', 'Messages', 'Root') +class MessagesController(object): + + @pecan.expose(generic=True, template='messages.html') + def index(self): + return list() + + @index.when(method='POST') + def index_post(self, **kw): + print(kw) + pecan.redirect('/messages') + + +@swagger.path('profile', 'Profile', 'Root') +class ProfileController(object): + + @pecan.expose(generic=True, template='profile.html') + def index(self): + return dict() + + @index.when(method='POST') + def index_post(self, **kw): + print(kw) + pecan.redirect('/profile') + + @pecan.expose(generic=True) + def image(self): + print('no image uploaded') + + @image.when(method='POST') + def image_post(self): + print('not supported') + + @pecan.expose() + def stats(self): + return dict() + + +@swagger.path('/', 'Root') +class RootController(object): + + profile = ProfileController() + messages = MessagesController() diff --git a/Desktop/pecan-swagger/myapp-doc.py b/Desktop/pecan-swagger/myapp-doc.py new file mode 100644 index 0000000..f48ef1b --- /dev/null +++ b/Desktop/pecan-swagger/myapp-doc.py @@ -0,0 +1,8 @@ +#!/bin/env python +import pprint + +from pecan_swagger import utils +import myapp + +pp = pprint.PrettyPrinter(indent=2) +pp.pprint(utils.swagger_build('myapp', '1.0')) diff --git a/Desktop/pecan-swagger/pecan-swagger b/Desktop/pecan-swagger/pecan-swagger new file mode 160000 index 0000000..cfcc2cd --- /dev/null +++ b/Desktop/pecan-swagger/pecan-swagger @@ -0,0 +1 @@ +Subproject commit cfcc2cd2901628c27326a2e11982396f018327ae diff --git a/Desktop/pecan-swagger/pecan_swagger/__init__.py b/Desktop/pecan-swagger/pecan_swagger/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/pecan_swagger/decorators.py b/Desktop/pecan-swagger/pecan_swagger/decorators.py new file mode 100644 index 0000000..5b8ef4a --- /dev/null +++ b/Desktop/pecan-swagger/pecan_swagger/decorators.py @@ -0,0 +1,38 @@ +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/Desktop/pecan-swagger/pecan_swagger/g.py b/Desktop/pecan-swagger/pecan_swagger/g.py new file mode 100644 index 0000000..383c2c6 --- /dev/null +++ b/Desktop/pecan-swagger/pecan_swagger/g.py @@ -0,0 +1,487 @@ +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/Desktop/pecan-swagger/pecan_swagger/utils.py b/Desktop/pecan-swagger/pecan_swagger/utils.py new file mode 100644 index 0000000..aa3d3b6 --- /dev/null +++ b/Desktop/pecan-swagger/pecan_swagger/utils.py @@ -0,0 +1,64 @@ +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 diff --git a/Desktop/pecan-swagger/setup.cfg b/Desktop/pecan-swagger/setup.cfg new file mode 100644 index 0000000..3c6e79c --- /dev/null +++ b/Desktop/pecan-swagger/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/Desktop/pecan-swagger/setup.py b/Desktop/pecan-swagger/setup.py new file mode 100644 index 0000000..7de524d --- /dev/null +++ b/Desktop/pecan-swagger/setup.py @@ -0,0 +1,32 @@ +import setuptools + +from codecs import open +from os import path + + +here = path.abspath(path.dirname(__file__)) +# Get the long description from the README file +with open(path.join(here, 'README.rst'), encoding='utf-8') as f: + long_description = f.read() + + +setuptools.setup( + name='pecan-swagger', + version='0.1.2', + description='A project to produce swagger from pecan', + long_description=long_description, + url='https://github.com/elmiko/pecan-swagger', + author='Michael McCune', + author_email='msm@opbstudios.com', + license='BSD', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python :: 3.6', + ], + keywords='pecan swagger development', + packages=[ + 'pecan_swagger', + ], +) diff --git a/Desktop/pecan-swagger/tests/__init__.py b/Desktop/pecan-swagger/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/tests/resources/__init__.py b/Desktop/pecan-swagger/tests/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Desktop/pecan-swagger/tests/resources/example_app.py b/Desktop/pecan-swagger/tests/resources/example_app.py new file mode 100644 index 0000000..9d76867 --- /dev/null +++ b/Desktop/pecan-swagger/tests/resources/example_app.py @@ -0,0 +1,57 @@ +import pecan +from pecan import rest +from pecan_swagger import decorators as swagger + + +@swagger.path('api', 'API', 'Root') +class APIController(rest.RestController): + + @pecan.expose() + def get(self): + pass + + +@swagger.path('messages', 'Messages', 'Root') +class MessagesController(object): + + @pecan.expose(generic=True, template='messages.html') + def index(self): + return list() + + @index.when(method='POST') + def index_post(self, **kw): + print(kw) + pecan.redirect('/messages') + + +@swagger.path('profile', 'Profile', 'Root') +class ProfileController(object): + + @pecan.expose(generic=True, template='profile.html') + def index(self): + return dict() + + @index.when(method='POST') + def index_post(self, **kw): + print(kw) + pecan.redirect('/profile') + + @pecan.expose(generic=True) + def image(self): + print('no image uploaded') + + @image.when(method='POST') + def image_post(self): + print('not supported') + + @pecan.expose() + def stats(self): + return dict() + + +@swagger.path('/', 'Root') +class RootController(object): + + profile = ProfileController() + messages = MessagesController() + api = APIController() diff --git a/Desktop/pecan-swagger/tests/resources/example_wsme_app.py b/Desktop/pecan-swagger/tests/resources/example_wsme_app.py new file mode 100644 index 0000000..c7c8100 --- /dev/null +++ b/Desktop/pecan-swagger/tests/resources/example_wsme_app.py @@ -0,0 +1,50 @@ +import pecan +from pecan import rest +from pecan_swagger import decorators as swagger +import wsme +from wsme import types as wtypes +from wsmeext.pecan import wsexpose + + +class MessageModel(object): + id = wtypes.StringType(min_length=1, max_length=255) + message = wtypes.StringType(min_length=1, max_length=255) + message_size = wtypes.IntegerType(minimum=1) + message_from = wtypes.Enum(str, '1.OSOMATSU', '2.KARAMATSU', + '3.CHOROMATSU', '4.ICHIMATSU', + '5.JUSHIMATSU', '6.TODOMATSU') + + +class MessageCollection(object): + messages = [MessageModel] + +@swagger.path('wsmemessages', 'WsmeMessages', 'WsmeRoot') +class MessagesController(rest.RestController): + + _custom_actions = { + 'detail': ['GET'], + } + + @wsexpose(MessageCollection) + def get_all(self): + return MessageCollection() + + @wsexpose(MessageModel, wtypes.text) + def get_one(self, id): + return MessageModel() + + @wsexpose(MessageModel, wtypes.text, status_code=201) + def post(self, message): + return MessageModel() + + @wsexpose(None, wtypes.text, status_code=204) + def delete(self, id): + pass + + @wsexpose(MessageModel, wtypes.text) + def detail(self, id): + return MessageModel() + +@swagger.path('/', 'WsmeRoot') +class RootController(rest.RestController): + pass diff --git a/Desktop/pecan-swagger/tests/test_utils.py b/Desktop/pecan-swagger/tests/test_utils.py new file mode 100644 index 0000000..692300f --- /dev/null +++ b/Desktop/pecan-swagger/tests/test_utils.py @@ -0,0 +1,216 @@ +import unittest + +from pecan_swagger import utils + + +class TestUtils(unittest.TestCase): + def test_swagger_build(self): + from .resources import example_app + + expected = { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": "example_app" + }, + "produces": [], + "consumes": [], + "definitions": {}, + "paths": { + "/api": { + "get": {} + }, + "/messages": { + "get": {}, + "post": {} + }, + "/profile": { + "get": {}, + "post": {} + }, + "/profile/image": { + "get": {}, + "post": {} + }, + "/profile/stats": { + "get": {} + } + } + } + + actual = utils.swagger_build('example_app', '1.0') + self.assertDictEqual(expected, actual) + + def test_swagger_build_wsme(self): + from .resources import example_wsme_app + + expected = \ + { + "consumes": [], + "definitions": { + "MessageCollection": { + "items": { + "$ref": "#/definitions/MessageModel" + }, + "type": "array" + }, + "MessageModel": { + "items": { + "properties": { + "id": { + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "message": { + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "message_from": { + "enum": [ + "1.OSOMATSU", + "2.KARAMATSU", + "3.CHOROMATSU", + "4.ICHIMATSU", + "5.JUSHIMATSU", + "6.TODOMATSU" + ], + "type": "string" + }, + "message_size": { + "minimum": 1, + "type": "integer" + } + } + }, + "type": "object" + } + }, + "info": { + "title": "example_wsme_app", + "version": "1.0" + }, + "paths": { + "/api": { + "get": {} + }, + "/messages": { + "get": {}, + "post": {} + }, + "/profile": { + "get": {}, + "post": {} + }, + "/profile/image": { + "get": {}, + "post": {} + }, + "/profile/stats": { + "get": {} + }, + "/wsmemessages": { + "get": { + "description": "", + "parameters": [], + "responses": { + 200: { + "description": "", + "schema": { + "$ref": "#/definitions/MessageCollection" + } + } + } + }, + "post": { + "description": "", + "parameters": [ + { + "in": "query", + "name": "message", + "required": True, + "type": "string" + } + ], + "responses": { + 201: { + "description": "", + "schema": { + "$ref": "#/definitions/MessageModel" + } + } + } + } + }, + "/wsmemessages/": { + "delete": { + "description": "", + "parameters": [ + { + "in": "query", + "name": "id", + "required": True, + "type": "string" + } + ], + "responses": { + 204: { + "description": "" + } + } + }, + "get": { + "description": "", + "parameters": [ + { + "in": "query", + "name": "id", + "required": True, + "type": "string" + } + ], + "responses": { + 200: { + "description": "", + "schema": { + "$ref": "#/definitions/MessageModel" + } + } + } + } + }, + "/wsmemessages/detail": { + "get": { + "description": "", + "parameters": [ + { + "in": "query", + "name": "id", + "required": True, + "type": "string" + } + ], + "responses": { + 200: { + "description": "", + "schema": { + "$ref": "#/definitions/MessageModel" + } + } + } + } + } + }, + "produces": [], + "swagger": "2.0" + } + + actual = utils.swagger_build('example_wsme_app', '1.0') + + import codecs, json + fout = codecs.open('example_wsme_app.json', 'w', 'utf_8') + json.dump(actual, fout, sort_keys=True, indent=2) + + self.maxDiff = None + self.assertDictEqual(expected, actual) diff --git a/Desktop/pecan-swagger/tox.ini b/Desktop/pecan-swagger/tox.ini new file mode 100644 index 0000000..7973619 --- /dev/null +++ b/Desktop/pecan-swagger/tox.ini @@ -0,0 +1,12 @@ +[tox] +envlist = py3 + +[testenv] +deps = + nose2 + pecan + WSME + flake8 +commands = + flake8 pecan_swagger + nose2 -v -- Gitee From 56bc1e78d7eb8c62d37baaa2b0e1aebdadeb1fc3 Mon Sep 17 00:00:00 2001 From: fengdelu <492607694@qq.com> Date: Wed, 30 Mar 2022 13:23:42 +0800 Subject: [PATCH 4/5] fix: tox error 1.remove unused function library 2.fix line too long 3.fix blank lines --- Desktop/pecan-swagger/myapp-doc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop/pecan-swagger/myapp-doc.py b/Desktop/pecan-swagger/myapp-doc.py index f48ef1b..d7bac65 100644 --- a/Desktop/pecan-swagger/myapp-doc.py +++ b/Desktop/pecan-swagger/myapp-doc.py @@ -2,7 +2,7 @@ import pprint from pecan_swagger import utils -import myapp + pp = pprint.PrettyPrinter(indent=2) pp.pprint(utils.swagger_build('myapp', '1.0')) -- Gitee From 8fd0237353e3aabea2b338cac142066011904aa2 Mon Sep 17 00:00:00 2001 From: fengdelu <492607694@qq.com> Date: Wed, 30 Mar 2022 16:50:23 +0800 Subject: [PATCH 5/5] fix:tox error 1.remove unused function library 2.fix line too long 3.fix blank lines ../../../../.local/ --- Desktop/pecan-swagger/README.md | 28 +++++++++++++++++++ .../industrial-ai-apiserver/README.md | 8 ++++++ .../industrial-ai-apiserver/datainit.py | 4 +++ .../industrialai/db/api.py | 10 ++++++- .../industrialai/db/model.py | 13 +++++++++ Desktop/pecan-swagger/tox.ini | 4 +++ 6 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Desktop/pecan-swagger/README.md b/Desktop/pecan-swagger/README.md index e69de29..cdff1a1 100644 --- a/Desktop/pecan-swagger/README.md +++ b/Desktop/pecan-swagger/README.md @@ -0,0 +1,28 @@ +<<<<<<< HEAD +fix:tox error + +1.remove unused function library +2.fix line too long +3.fix blank lines + +======= +# Pecan Swagger + +## 1. 项目背景 + +Pecan-Swagger 用于为 Pecan Web 服务生成 Swagger 文档。该项目源自 ,目前原 repo 已无人维护,[原示例链接](examples/README.rst)。 + +Pecan 是 OpenStack 诸多模块中比较受欢迎的一种 Restful API 框架,新兴的项目大多采用 Pecan,比如 Magnum / CloudKitty 等等。Pecan-Swagger 依然是有很大价值的,因此我们再这个 repo 继续更新 Pecan-Swagger。 + +## 2. 需要预先掌握的技能 + +1. [Python 三日课程](https://gitee.com/wu-wen-xiang/training-python/blob/master/doc/learning-python-in-3-days.md) +2. [Git 基本使用](https://gitee.com/wu-wen-xiang/training-python/blob/master/doc/learning-python-in-3-days.md#31-%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6) +3. [Markdown 基本语法](https://markdown.com.cn/basic-syntax/) +4. [Gitee 合作](https://gitee.com/help/articles/4346) +5. [Swagger](https://editor.swagger.io/) + +## 3. 预期目标 + +1. 对接 Pecan 最近的 Stable 版本,提供 Pecan-Swagger 模块,为 Pecan 项目提供在线的 Swagger 文档。 +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.md b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.md index 837b723..2e34627 100644 --- a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.md +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/README.md @@ -27,7 +27,11 @@ python run.py # python run.py 0.0.0.0 8080 # 尝试访问 +<<<<<<< HEAD curl http://localhost:8080/v1/users/ +======= +curl http://localhost:8080/users/ +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae ``` ## 测试 @@ -50,4 +54,8 @@ docker stop industrialai; docker rm industrialai docker run -d --name industrialai -p 8888:8080 industrialai curl http://localhost:8888/users/ +<<<<<<< HEAD ``` +======= +``` +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/datainit.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/datainit.py index e866187..6508a9b 100644 --- a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/datainit.py +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/datainit.py @@ -5,7 +5,11 @@ from sqlalchemy import create_engine def init_db(): DB_URL = os.getenv('db_url') or \ +<<<<<<< HEAD "sqlite:///C:\\Users\\fengdelu\\Desktop\\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" +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae engine = create_engine(DB_URL, echo=True) Base.metadata.drop_all(engine) Base.metadata.create_all(engine) diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/api.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/api.py index db8b8a2..4979abe 100644 --- a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/api.py +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/api.py @@ -5,7 +5,11 @@ import os from pecan import abort from sqlalchemy import create_engine, func from sqlalchemy.orm import exc +<<<<<<< HEAD +======= +from sqlalchemy.orm import joinedload +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae from sqlalchemy.orm import sessionmaker logger = logging.getLogger(__name__) @@ -22,8 +26,12 @@ def get_engine(): return _ENGINE DB_URL = ( os.getenv("db_url") or - "sqlite:///D:\\Users\\fengdelu\\Desktop\\pecan-swagger\\examples\\" +<<<<<<< HEAD + "sqlite:///C:\\Users\\fengdelu\\Desktop\\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" +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae ) _ENGINE = create_engine(DB_URL, echo=True) return _ENGINE diff --git a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/model.py b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/model.py index 3c7492c..aa8e2bd 100644 --- a/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/model.py +++ b/Desktop/pecan-swagger/examples/industrial-ai-apiserver/industrialai/db/model.py @@ -4,7 +4,13 @@ from sqlalchemy import DateTime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import ForeignKey from sqlalchemy import Integer +<<<<<<< HEAD from sqlalchemy import String +======= +from sqlalchemy.orm import relationship +from sqlalchemy import String +from sqlalchemy import Table +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae Base = declarative_base() @@ -12,7 +18,10 @@ Base = declarative_base() def init_db(_ENGINE): Base.metadata.create_all(_ENGINE) +<<<<<<< HEAD +======= +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae class User(Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) @@ -31,3 +40,7 @@ class Role(Base): __tablename__ = 'role' id = Column(Integer, primary_key=True) title = Column(String(32)) +<<<<<<< HEAD +======= + +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae diff --git a/Desktop/pecan-swagger/tox.ini b/Desktop/pecan-swagger/tox.ini index 7973619..d2a2465 100644 --- a/Desktop/pecan-swagger/tox.ini +++ b/Desktop/pecan-swagger/tox.ini @@ -9,4 +9,8 @@ deps = flake8 commands = flake8 pecan_swagger +<<<<<<< HEAD nose2 -v +======= + nose2 +>>>>>>> cfcc2cd2901628c27326a2e11982396f018327ae -- Gitee